Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/boltons/funcutils.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4f3585e2f14b |
---|---|
1 # -*- coding: utf-8 -*- | |
2 """Python's built-in :mod:`functools` module builds several useful | |
3 utilities on top of Python's first-class function | |
4 support. ``funcutils`` generally stays in the same vein, adding to and | |
5 correcting Python's standard metaprogramming facilities. | |
6 """ | |
7 from __future__ import print_function | |
8 | |
9 import sys | |
10 import re | |
11 import inspect | |
12 import functools | |
13 import itertools | |
14 from types import MethodType, FunctionType | |
15 | |
16 try: | |
17 xrange | |
18 make_method = MethodType | |
19 except NameError: | |
20 # Python 3 | |
21 make_method = lambda desc, obj, obj_type: MethodType(desc, obj) | |
22 basestring = (str, bytes) # Python 3 compat | |
23 _IS_PY2 = False | |
24 else: | |
25 _IS_PY2 = True | |
26 | |
27 | |
28 try: | |
29 _inspect_iscoroutinefunction = inspect.iscoroutinefunction | |
30 except AttributeError: | |
31 # Python 3.4 | |
32 _inspect_iscoroutinefunction = lambda func: False | |
33 | |
34 | |
35 try: | |
36 from boltons.typeutils import make_sentinel | |
37 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT') | |
38 except ImportError: | |
39 NO_DEFAULT = object() | |
40 | |
41 try: | |
42 from functools import partialmethod | |
43 except ImportError: | |
44 partialmethod = None | |
45 | |
46 | |
47 _IS_PY35 = sys.version_info >= (3, 5) | |
48 if not _IS_PY35: | |
49 # py35+ wants you to use signature instead, but | |
50 # inspect_formatargspec is way simpler for what it is. Copied the | |
51 # vendoring approach from alembic: | |
52 # https://github.com/sqlalchemy/alembic/blob/4cdad6aec32b4b5573a2009cc356cb4b144bd359/alembic/util/compat.py#L92 | |
53 from inspect import formatargspec as inspect_formatargspec | |
54 else: | |
55 from inspect import formatannotation | |
56 | |
57 def inspect_formatargspec( | |
58 args, varargs=None, varkw=None, defaults=None, | |
59 kwonlyargs=(), kwonlydefaults={}, annotations={}, | |
60 formatarg=str, | |
61 formatvarargs=lambda name: '*' + name, | |
62 formatvarkw=lambda name: '**' + name, | |
63 formatvalue=lambda value: '=' + repr(value), | |
64 formatreturns=lambda text: ' -> ' + text, | |
65 formatannotation=formatannotation): | |
66 """Copy formatargspec from python 3.7 standard library. | |
67 Python 3 has deprecated formatargspec and requested that Signature | |
68 be used instead, however this requires a full reimplementation | |
69 of formatargspec() in terms of creating Parameter objects and such. | |
70 Instead of introducing all the object-creation overhead and having | |
71 to reinvent from scratch, just copy their compatibility routine. | |
72 """ | |
73 | |
74 def formatargandannotation(arg): | |
75 result = formatarg(arg) | |
76 if arg in annotations: | |
77 result += ': ' + formatannotation(annotations[arg]) | |
78 return result | |
79 specs = [] | |
80 if defaults: | |
81 firstdefault = len(args) - len(defaults) | |
82 for i, arg in enumerate(args): | |
83 spec = formatargandannotation(arg) | |
84 if defaults and i >= firstdefault: | |
85 spec = spec + formatvalue(defaults[i - firstdefault]) | |
86 specs.append(spec) | |
87 if varargs is not None: | |
88 specs.append(formatvarargs(formatargandannotation(varargs))) | |
89 else: | |
90 if kwonlyargs: | |
91 specs.append('*') | |
92 if kwonlyargs: | |
93 for kwonlyarg in kwonlyargs: | |
94 spec = formatargandannotation(kwonlyarg) | |
95 if kwonlydefaults and kwonlyarg in kwonlydefaults: | |
96 spec += formatvalue(kwonlydefaults[kwonlyarg]) | |
97 specs.append(spec) | |
98 if varkw is not None: | |
99 specs.append(formatvarkw(formatargandannotation(varkw))) | |
100 result = '(' + ', '.join(specs) + ')' | |
101 if 'return' in annotations: | |
102 result += formatreturns(formatannotation(annotations['return'])) | |
103 return result | |
104 | |
105 | |
106 def get_module_callables(mod, ignore=None): | |
107 """Returns two maps of (*types*, *funcs*) from *mod*, optionally | |
108 ignoring based on the :class:`bool` return value of the *ignore* | |
109 callable. *mod* can be a string name of a module in | |
110 :data:`sys.modules` or the module instance itself. | |
111 """ | |
112 if isinstance(mod, basestring): | |
113 mod = sys.modules[mod] | |
114 types, funcs = {}, {} | |
115 for attr_name in dir(mod): | |
116 if ignore and ignore(attr_name): | |
117 continue | |
118 try: | |
119 attr = getattr(mod, attr_name) | |
120 except Exception: | |
121 continue | |
122 try: | |
123 attr_mod_name = attr.__module__ | |
124 except AttributeError: | |
125 continue | |
126 if attr_mod_name != mod.__name__: | |
127 continue | |
128 if isinstance(attr, type): | |
129 types[attr_name] = attr | |
130 elif callable(attr): | |
131 funcs[attr_name] = attr | |
132 return types, funcs | |
133 | |
134 | |
135 def mro_items(type_obj): | |
136 """Takes a type and returns an iterator over all class variables | |
137 throughout the type hierarchy (respecting the MRO). | |
138 | |
139 >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)])) | |
140 ['denominator', 'imag', 'numerator', 'real'] | |
141 """ | |
142 # TODO: handle slots? | |
143 return itertools.chain.from_iterable(ct.__dict__.items() | |
144 for ct in type_obj.__mro__) | |
145 | |
146 | |
147 def dir_dict(obj, raise_exc=False): | |
148 """Return a dictionary of attribute names to values for a given | |
149 object. Unlike ``obj.__dict__``, this function returns all | |
150 attributes on the object, including ones on parent classes. | |
151 """ | |
152 # TODO: separate function for handling descriptors on types? | |
153 ret = {} | |
154 for k in dir(obj): | |
155 try: | |
156 ret[k] = getattr(obj, k) | |
157 except Exception: | |
158 if raise_exc: | |
159 raise | |
160 return ret | |
161 | |
162 | |
163 def copy_function(orig, copy_dict=True): | |
164 """Returns a shallow copy of the function, including code object, | |
165 globals, closure, etc. | |
166 | |
167 >>> func = lambda: func | |
168 >>> func() is func | |
169 True | |
170 >>> func_copy = copy_function(func) | |
171 >>> func_copy() is func | |
172 True | |
173 >>> func_copy is not func | |
174 True | |
175 | |
176 Args: | |
177 orig (function): The function to be copied. Must be a | |
178 function, not just any method or callable. | |
179 copy_dict (bool): Also copy any attributes set on the function | |
180 instance. Defaults to ``True``. | |
181 """ | |
182 ret = FunctionType(orig.__code__, | |
183 orig.__globals__, | |
184 name=orig.__name__, | |
185 argdefs=getattr(orig, "__defaults__", None), | |
186 closure=getattr(orig, "__closure__", None)) | |
187 if copy_dict: | |
188 ret.__dict__.update(orig.__dict__) | |
189 return ret | |
190 | |
191 | |
192 def partial_ordering(cls): | |
193 """Class decorator, similar to :func:`functools.total_ordering`, | |
194 except it is used to define `partial orderings`_ (i.e., it is | |
195 possible that *x* is neither greater than, equal to, or less than | |
196 *y*). It assumes the presence of the ``__le__()`` and ``__ge__()`` | |
197 method, but nothing else. It will not override any existing | |
198 additional comparison methods. | |
199 | |
200 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set | |
201 | |
202 >>> @partial_ordering | |
203 ... class MySet(set): | |
204 ... def __le__(self, other): | |
205 ... return self.issubset(other) | |
206 ... def __ge__(self, other): | |
207 ... return self.issuperset(other) | |
208 ... | |
209 >>> a = MySet([1,2,3]) | |
210 >>> b = MySet([1,2]) | |
211 >>> c = MySet([1,2,4]) | |
212 >>> b < a | |
213 True | |
214 >>> b > a | |
215 False | |
216 >>> b < c | |
217 True | |
218 >>> a < c | |
219 False | |
220 >>> c > a | |
221 False | |
222 """ | |
223 def __lt__(self, other): return self <= other and not self >= other | |
224 def __gt__(self, other): return self >= other and not self <= other | |
225 def __eq__(self, other): return self >= other and self <= other | |
226 | |
227 if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__ | |
228 if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__ | |
229 if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__ | |
230 | |
231 return cls | |
232 | |
233 | |
234 class InstancePartial(functools.partial): | |
235 """:class:`functools.partial` is a huge convenience for anyone | |
236 working with Python's great first-class functions. It allows | |
237 developers to curry arguments and incrementally create simpler | |
238 callables for a variety of use cases. | |
239 | |
240 Unfortunately there's one big gap in its usefulness: | |
241 methods. Partials just don't get bound as methods and | |
242 automatically handed a reference to ``self``. The | |
243 ``InstancePartial`` type remedies this by inheriting from | |
244 :class:`functools.partial` and implementing the necessary | |
245 descriptor protocol. There are no other differences in | |
246 implementation or usage. :class:`CachedInstancePartial`, below, | |
247 has the same ability, but is slightly more efficient. | |
248 | |
249 """ | |
250 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244 | |
251 @property | |
252 def _partialmethod(self): | |
253 return partialmethod(self.func, *self.args, **self.keywords) | |
254 | |
255 def __get__(self, obj, obj_type): | |
256 return make_method(self, obj, obj_type) | |
257 | |
258 | |
259 | |
260 class CachedInstancePartial(functools.partial): | |
261 """The ``CachedInstancePartial`` is virtually the same as | |
262 :class:`InstancePartial`, adding support for method-usage to | |
263 :class:`functools.partial`, except that upon first access, it | |
264 caches the bound method on the associated object, speeding it up | |
265 for future accesses, and bringing the method call overhead to | |
266 about the same as non-``partial`` methods. | |
267 | |
268 See the :class:`InstancePartial` docstring for more details. | |
269 """ | |
270 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244 | |
271 @property | |
272 def _partialmethod(self): | |
273 return partialmethod(self.func, *self.args, **self.keywords) | |
274 | |
275 def __get__(self, obj, obj_type): | |
276 # These assignments could've been in __init__, but there was | |
277 # no simple way to do it without breaking one of PyPy or Py3. | |
278 self.__name__ = None | |
279 self.__doc__ = self.func.__doc__ | |
280 self.__module__ = self.func.__module__ | |
281 | |
282 name = self.__name__ | |
283 if name is None: | |
284 for k, v in mro_items(obj_type): | |
285 if v is self: | |
286 self.__name__ = name = k | |
287 if obj is None: | |
288 return make_method(self, obj, obj_type) | |
289 try: | |
290 # since this is a data descriptor, this block | |
291 # is probably only hit once (per object) | |
292 return obj.__dict__[name] | |
293 except KeyError: | |
294 obj.__dict__[name] = ret = make_method(self, obj, obj_type) | |
295 return ret | |
296 | |
297 | |
298 partial = CachedInstancePartial | |
299 | |
300 | |
301 def format_invocation(name='', args=(), kwargs=None, **kw): | |
302 """Given a name, positional arguments, and keyword arguments, format | |
303 a basic Python-style function call. | |
304 | |
305 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3})) | |
306 func(1, 2, c=3) | |
307 >>> print(format_invocation('a_func', args=(1,))) | |
308 a_func(1) | |
309 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)])) | |
310 kw_func(a=1, b=2) | |
311 | |
312 """ | |
313 _repr = kw.pop('repr', repr) | |
314 if kw: | |
315 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys())) | |
316 kwargs = kwargs or {} | |
317 a_text = ', '.join([_repr(a) for a in args]) | |
318 if isinstance(kwargs, dict): | |
319 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)] | |
320 else: | |
321 kwarg_items = kwargs | |
322 kw_text = ', '.join(['%s=%s' % (k, _repr(v)) for k, v in kwarg_items]) | |
323 | |
324 all_args_text = a_text | |
325 if all_args_text and kw_text: | |
326 all_args_text += ', ' | |
327 all_args_text += kw_text | |
328 | |
329 return '%s(%s)' % (name, all_args_text) | |
330 | |
331 | |
332 def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None): | |
333 """Render an expression-style repr of an object, based on attribute | |
334 names, which are assumed to line up with arguments to an initializer. | |
335 | |
336 >>> class Flag(object): | |
337 ... def __init__(self, length, width, depth=None): | |
338 ... self.length = length | |
339 ... self.width = width | |
340 ... self.depth = depth | |
341 ... | |
342 | |
343 That's our Flag object, here are some example reprs for it: | |
344 | |
345 >>> flag = Flag(5, 10) | |
346 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth'])) | |
347 Flag(5, 10) | |
348 >>> flag2 = Flag(5, 15, 2) | |
349 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth'])) | |
350 Flag(5, width=15, depth=2) | |
351 | |
352 By picking the pos_names, req_names, opt_names, and opt_key, you | |
353 can fine-tune how you want the repr to look. | |
354 | |
355 Args: | |
356 obj (object): The object whose type name will be used and | |
357 attributes will be checked | |
358 pos_names (list): Required list of attribute names which will be | |
359 rendered as positional arguments in the output repr. | |
360 req_names (list): List of attribute names which will always | |
361 appear in the keyword arguments in the output repr. Defaults to None. | |
362 opt_names (list): List of attribute names which may appear in | |
363 the keyword arguments in the output repr, provided they pass | |
364 the *opt_key* check. Defaults to None. | |
365 opt_key (callable): A function or callable which checks whether | |
366 an opt_name should be in the repr. Defaults to a | |
367 ``None``-check. | |
368 | |
369 """ | |
370 cn = obj.__class__.__name__ | |
371 req_names = req_names or [] | |
372 opt_names = opt_names or [] | |
373 uniq_names, all_names = set(), [] | |
374 for name in req_names + opt_names: | |
375 if name in uniq_names: | |
376 continue | |
377 uniq_names.add(name) | |
378 all_names.append(name) | |
379 | |
380 if opt_key is None: | |
381 opt_key = lambda v: v is None | |
382 assert callable(opt_key) | |
383 | |
384 args = [getattr(obj, name, None) for name in pos_names] | |
385 | |
386 kw_items = [(name, getattr(obj, name, None)) for name in all_names] | |
387 kw_items = [(name, val) for name, val in kw_items | |
388 if not (name in opt_names and opt_key(val))] | |
389 | |
390 return format_invocation(cn, args, kw_items) | |
391 | |
392 | |
393 def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None): | |
394 """Format a non-expression-style repr | |
395 | |
396 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]). | |
397 | |
398 This makes sense for smaller, lower-level objects whose state | |
399 roundtrips. But a lot of objects contain values that don't | |
400 roundtrip, like types and functions. | |
401 | |
402 For those objects, there is the non-expression style repr, which | |
403 mimic's Python's default style to make a repr like so: | |
404 | |
405 >>> class Flag(object): | |
406 ... def __init__(self, length, width, depth=None): | |
407 ... self.length = length | |
408 ... self.width = width | |
409 ... self.depth = depth | |
410 ... | |
411 >>> flag = Flag(5, 10) | |
412 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth'])) | |
413 <Flag length=5 width=10> | |
414 | |
415 If no attributes are specified or set, utilizes the id, not unlike Python's | |
416 built-in behavior. | |
417 | |
418 >>> print(format_nonexp_repr(flag)) | |
419 <Flag id=...> | |
420 """ | |
421 cn = obj.__class__.__name__ | |
422 req_names = req_names or [] | |
423 opt_names = opt_names or [] | |
424 uniq_names, all_names = set(), [] | |
425 for name in req_names + opt_names: | |
426 if name in uniq_names: | |
427 continue | |
428 uniq_names.add(name) | |
429 all_names.append(name) | |
430 | |
431 if opt_key is None: | |
432 opt_key = lambda v: v is None | |
433 assert callable(opt_key) | |
434 | |
435 items = [(name, getattr(obj, name, None)) for name in all_names] | |
436 labels = ['%s=%r' % (name, val) for name, val in items | |
437 if not (name in opt_names and opt_key(val))] | |
438 if not labels: | |
439 labels = ['id=%s' % id(obj)] | |
440 ret = '<%s %s>' % (cn, ' '.join(labels)) | |
441 return ret | |
442 | |
443 | |
444 | |
445 # # # | |
446 # # # Function builder | |
447 # # # | |
448 | |
449 | |
450 def wraps(func, injected=None, expected=None, **kw): | |
451 """Decorator factory to apply update_wrapper() to a wrapper function. | |
452 | |
453 Modeled after built-in :func:`functools.wraps`. Returns a decorator | |
454 that invokes update_wrapper() with the decorated function as the wrapper | |
455 argument and the arguments to wraps() as the remaining arguments. | |
456 Default arguments are as for update_wrapper(). This is a convenience | |
457 function to simplify applying partial() to update_wrapper(). | |
458 | |
459 Same example as in update_wrapper's doc but with wraps: | |
460 | |
461 >>> from boltons.funcutils import wraps | |
462 >>> | |
463 >>> def print_return(func): | |
464 ... @wraps(func) | |
465 ... def wrapper(*args, **kwargs): | |
466 ... ret = func(*args, **kwargs) | |
467 ... print(ret) | |
468 ... return ret | |
469 ... return wrapper | |
470 ... | |
471 >>> @print_return | |
472 ... def example(): | |
473 ... '''docstring''' | |
474 ... return 'example return value' | |
475 >>> | |
476 >>> val = example() | |
477 example return value | |
478 >>> example.__name__ | |
479 'example' | |
480 >>> example.__doc__ | |
481 'docstring' | |
482 """ | |
483 return partial(update_wrapper, func=func, build_from=None, | |
484 injected=injected, expected=expected, **kw) | |
485 | |
486 | |
487 def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw): | |
488 """Modeled after the built-in :func:`functools.update_wrapper`, | |
489 this function is used to make your wrapper function reflect the | |
490 wrapped function's: | |
491 | |
492 * Name | |
493 * Documentation | |
494 * Module | |
495 * Signature | |
496 | |
497 The built-in :func:`functools.update_wrapper` copies the first three, but | |
498 does not copy the signature. This version of ``update_wrapper`` can copy | |
499 the inner function's signature exactly, allowing seamless usage | |
500 and :mod:`introspection <inspect>`. Usage is identical to the | |
501 built-in version:: | |
502 | |
503 >>> from boltons.funcutils import update_wrapper | |
504 >>> | |
505 >>> def print_return(func): | |
506 ... def wrapper(*args, **kwargs): | |
507 ... ret = func(*args, **kwargs) | |
508 ... print(ret) | |
509 ... return ret | |
510 ... return update_wrapper(wrapper, func) | |
511 ... | |
512 >>> @print_return | |
513 ... def example(): | |
514 ... '''docstring''' | |
515 ... return 'example return value' | |
516 >>> | |
517 >>> val = example() | |
518 example return value | |
519 >>> example.__name__ | |
520 'example' | |
521 >>> example.__doc__ | |
522 'docstring' | |
523 | |
524 In addition, the boltons version of update_wrapper supports | |
525 modifying the outer signature. By passing a list of | |
526 *injected* argument names, those arguments will be removed from | |
527 the outer wrapper's signature, allowing your decorator to provide | |
528 arguments that aren't passed in. | |
529 | |
530 Args: | |
531 | |
532 wrapper (function) : The callable to which the attributes of | |
533 *func* are to be copied. | |
534 func (function): The callable whose attributes are to be copied. | |
535 injected (list): An optional list of argument names which | |
536 should not appear in the new wrapper's signature. | |
537 expected (list): An optional list of argument names (or (name, | |
538 default) pairs) representing new arguments introduced by | |
539 the wrapper (the opposite of *injected*). See | |
540 :meth:`FunctionBuilder.add_arg()` for more details. | |
541 build_from (function): The callable from which the new wrapper | |
542 is built. Defaults to *func*, unless *wrapper* is partial object | |
543 built from *func*, in which case it defaults to *wrapper*. | |
544 Useful in some specific cases where *wrapper* and *func* have the | |
545 same arguments but differ on which are keyword-only and positional-only. | |
546 update_dict (bool): Whether to copy other, non-standard | |
547 attributes of *func* over to the wrapper. Defaults to True. | |
548 inject_to_varkw (bool): Ignore missing arguments when a | |
549 ``**kwargs``-type catch-all is present. Defaults to True. | |
550 hide_wrapped (bool): Remove reference to the wrapped function(s) | |
551 in the updated function. | |
552 | |
553 In opposition to the built-in :func:`functools.update_wrapper` bolton's | |
554 version returns a copy of the function and does not modifiy anything in place. | |
555 For more in-depth wrapping of functions, see the | |
556 :class:`FunctionBuilder` type, on which update_wrapper was built. | |
557 """ | |
558 if injected is None: | |
559 injected = [] | |
560 elif isinstance(injected, basestring): | |
561 injected = [injected] | |
562 else: | |
563 injected = list(injected) | |
564 | |
565 expected_items = _parse_wraps_expected(expected) | |
566 | |
567 if isinstance(func, (classmethod, staticmethod)): | |
568 raise TypeError('wraps does not support wrapping classmethods and' | |
569 ' staticmethods, change the order of wrapping to' | |
570 ' wrap the underlying function: %r' | |
571 % (getattr(func, '__func__', None),)) | |
572 | |
573 update_dict = kw.pop('update_dict', True) | |
574 inject_to_varkw = kw.pop('inject_to_varkw', True) | |
575 hide_wrapped = kw.pop('hide_wrapped', False) | |
576 if kw: | |
577 raise TypeError('unexpected kwargs: %r' % kw.keys()) | |
578 | |
579 if isinstance(wrapper, functools.partial) and func is wrapper.func: | |
580 build_from = build_from or wrapper | |
581 | |
582 fb = FunctionBuilder.from_func(build_from or func) | |
583 | |
584 for arg in injected: | |
585 try: | |
586 fb.remove_arg(arg) | |
587 except MissingArgument: | |
588 if inject_to_varkw and fb.varkw is not None: | |
589 continue # keyword arg will be caught by the varkw | |
590 raise | |
591 | |
592 for arg, default in expected_items: | |
593 fb.add_arg(arg, default) # may raise ExistingArgument | |
594 | |
595 if fb.is_async: | |
596 fb.body = 'return await _call(%s)' % fb.get_invocation_str() | |
597 else: | |
598 fb.body = 'return _call(%s)' % fb.get_invocation_str() | |
599 | |
600 execdict = dict(_call=wrapper, _func=func) | |
601 fully_wrapped = fb.get_func(execdict, with_dict=update_dict) | |
602 | |
603 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'): | |
604 del fully_wrapped.__dict__['__wrapped__'] | |
605 elif not hide_wrapped: | |
606 fully_wrapped.__wrapped__ = func # ref to the original function (#115) | |
607 | |
608 return fully_wrapped | |
609 | |
610 | |
611 def _parse_wraps_expected(expected): | |
612 # expected takes a pretty powerful argument, it's processed | |
613 # here. admittedly this would be less trouble if I relied on | |
614 # OrderedDict (there's an impl of that in the commit history if | |
615 # you look | |
616 if expected is None: | |
617 expected = [] | |
618 elif isinstance(expected, basestring): | |
619 expected = [(expected, NO_DEFAULT)] | |
620 | |
621 expected_items = [] | |
622 try: | |
623 expected_iter = iter(expected) | |
624 except TypeError as e: | |
625 raise ValueError('"expected" takes string name, sequence of string names,' | |
626 ' iterable of (name, default) pairs, or a mapping of ' | |
627 ' {name: default}, not %r (got: %r)' % (expected, e)) | |
628 for argname in expected_iter: | |
629 if isinstance(argname, basestring): | |
630 # dict keys and bare strings | |
631 try: | |
632 default = expected[argname] | |
633 except TypeError: | |
634 default = NO_DEFAULT | |
635 else: | |
636 # pairs | |
637 try: | |
638 argname, default = argname | |
639 except (TypeError, ValueError): | |
640 raise ValueError('"expected" takes string name, sequence of string names,' | |
641 ' iterable of (name, default) pairs, or a mapping of ' | |
642 ' {name: default}, not %r') | |
643 if not isinstance(argname, basestring): | |
644 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,)) | |
645 | |
646 expected_items.append((argname, default)) | |
647 | |
648 return expected_items | |
649 | |
650 | |
651 class FunctionBuilder(object): | |
652 """The FunctionBuilder type provides an interface for programmatically | |
653 creating new functions, either based on existing functions or from | |
654 scratch. | |
655 | |
656 Values are passed in at construction or set as attributes on the | |
657 instance. For creating a new function based of an existing one, | |
658 see the :meth:`~FunctionBuilder.from_func` classmethod. At any | |
659 point, :meth:`~FunctionBuilder.get_func` can be called to get a | |
660 newly compiled function, based on the values configured. | |
661 | |
662 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5', | |
663 ... body='return 5') | |
664 >>> f = fb.get_func() | |
665 >>> f() | |
666 5 | |
667 >>> fb.varkw = 'kw' | |
668 >>> f_kw = fb.get_func() | |
669 >>> f_kw(ignored_arg='ignored_val') | |
670 5 | |
671 | |
672 Note that function signatures themselves changed quite a bit in | |
673 Python 3, so several arguments are only applicable to | |
674 FunctionBuilder in Python 3. Except for *name*, all arguments to | |
675 the constructor are keyword arguments. | |
676 | |
677 Args: | |
678 name (str): Name of the function. | |
679 doc (str): `Docstring`_ for the function, defaults to empty. | |
680 module (str): Name of the module from which this function was | |
681 imported. Defaults to None. | |
682 body (str): String version of the code representing the body | |
683 of the function. Defaults to ``'pass'``, which will result | |
684 in a function which does nothing and returns ``None``. | |
685 args (list): List of argument names, defaults to empty list, | |
686 denoting no arguments. | |
687 varargs (str): Name of the catch-all variable for positional | |
688 arguments. E.g., "args" if the resultant function is to have | |
689 ``*args`` in the signature. Defaults to None. | |
690 varkw (str): Name of the catch-all variable for keyword | |
691 arguments. E.g., "kwargs" if the resultant function is to have | |
692 ``**kwargs`` in the signature. Defaults to None. | |
693 defaults (tuple): A tuple containing default argument values for | |
694 those arguments that have defaults. | |
695 kwonlyargs (list): Argument names which are only valid as | |
696 keyword arguments. **Python 3 only.** | |
697 kwonlydefaults (dict): A mapping, same as normal *defaults*, | |
698 but only for the *kwonlyargs*. **Python 3 only.** | |
699 annotations (dict): Mapping of type hints and so | |
700 forth. **Python 3 only.** | |
701 filename (str): The filename that will appear in | |
702 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder". | |
703 indent (int): Number of spaces with which to indent the | |
704 function *body*. Values less than 1 will result in an error. | |
705 dict (dict): Any other attributes which should be added to the | |
706 functions compiled with this FunctionBuilder. | |
707 | |
708 All of these arguments are also made available as attributes which | |
709 can be mutated as necessary. | |
710 | |
711 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python | |
712 | |
713 """ | |
714 | |
715 if _IS_PY2: | |
716 _argspec_defaults = {'args': list, | |
717 'varargs': lambda: None, | |
718 'varkw': lambda: None, | |
719 'defaults': lambda: None} | |
720 | |
721 @classmethod | |
722 def _argspec_to_dict(cls, f): | |
723 args, varargs, varkw, defaults = inspect.getargspec(f) | |
724 return {'args': args, | |
725 'varargs': varargs, | |
726 'varkw': varkw, | |
727 'defaults': defaults} | |
728 | |
729 else: | |
730 _argspec_defaults = {'args': list, | |
731 'varargs': lambda: None, | |
732 'varkw': lambda: None, | |
733 'defaults': lambda: None, | |
734 'kwonlyargs': list, | |
735 'kwonlydefaults': dict, | |
736 'annotations': dict} | |
737 | |
738 @classmethod | |
739 def _argspec_to_dict(cls, f): | |
740 argspec = inspect.getfullargspec(f) | |
741 return dict((attr, getattr(argspec, attr)) | |
742 for attr in cls._argspec_defaults) | |
743 | |
744 _defaults = {'doc': str, | |
745 'dict': dict, | |
746 'is_async': lambda: False, | |
747 'module': lambda: None, | |
748 'body': lambda: 'pass', | |
749 'indent': lambda: 4, | |
750 "annotations": dict, | |
751 'filename': lambda: 'boltons.funcutils.FunctionBuilder'} | |
752 | |
753 _defaults.update(_argspec_defaults) | |
754 | |
755 _compile_count = itertools.count() | |
756 | |
757 def __init__(self, name, **kw): | |
758 self.name = name | |
759 for a, default_factory in self._defaults.items(): | |
760 val = kw.pop(a, None) | |
761 if val is None: | |
762 val = default_factory() | |
763 setattr(self, a, val) | |
764 | |
765 if kw: | |
766 raise TypeError('unexpected kwargs: %r' % kw.keys()) | |
767 return | |
768 | |
769 # def get_argspec(self): # TODO | |
770 | |
771 if _IS_PY2: | |
772 def get_sig_str(self, with_annotations=True): | |
773 """Return function signature as a string. | |
774 | |
775 with_annotations is ignored on Python 2. On Python 3 signature | |
776 will omit annotations if it is set to False. | |
777 """ | |
778 return inspect_formatargspec(self.args, self.varargs, | |
779 self.varkw, []) | |
780 | |
781 def get_invocation_str(self): | |
782 return inspect_formatargspec(self.args, self.varargs, | |
783 self.varkw, [])[1:-1] | |
784 else: | |
785 def get_sig_str(self, with_annotations=True): | |
786 """Return function signature as a string. | |
787 | |
788 with_annotations is ignored on Python 2. On Python 3 signature | |
789 will omit annotations if it is set to False. | |
790 """ | |
791 if with_annotations: | |
792 annotations = self.annotations | |
793 else: | |
794 annotations = {} | |
795 | |
796 return inspect_formatargspec(self.args, | |
797 self.varargs, | |
798 self.varkw, | |
799 [], | |
800 self.kwonlyargs, | |
801 {}, | |
802 annotations) | |
803 | |
804 _KWONLY_MARKER = re.compile(r""" | |
805 \* # a star | |
806 \s* # followed by any amount of whitespace | |
807 , # followed by a comma | |
808 \s* # followed by any amount of whitespace | |
809 """, re.VERBOSE) | |
810 | |
811 def get_invocation_str(self): | |
812 kwonly_pairs = None | |
813 formatters = {} | |
814 if self.kwonlyargs: | |
815 kwonly_pairs = dict((arg, arg) | |
816 for arg in self.kwonlyargs) | |
817 formatters['formatvalue'] = lambda value: '=' + value | |
818 | |
819 sig = inspect_formatargspec(self.args, | |
820 self.varargs, | |
821 self.varkw, | |
822 [], | |
823 kwonly_pairs, | |
824 kwonly_pairs, | |
825 {}, | |
826 **formatters) | |
827 sig = self._KWONLY_MARKER.sub('', sig) | |
828 return sig[1:-1] | |
829 | |
830 @classmethod | |
831 def from_func(cls, func): | |
832 """Create a new FunctionBuilder instance based on an existing | |
833 function. The original function will not be stored or | |
834 modified. | |
835 """ | |
836 # TODO: copy_body? gonna need a good signature regex. | |
837 # TODO: might worry about __closure__? | |
838 if not callable(func): | |
839 raise TypeError('expected callable object, not %r' % (func,)) | |
840 | |
841 if isinstance(func, functools.partial): | |
842 if _IS_PY2: | |
843 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.') | |
844 kwargs = {'name': func.func.__name__, | |
845 'doc': func.func.__doc__, | |
846 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor | |
847 'annotations': getattr(func.func, "__annotations__", {}), | |
848 'dict': getattr(func.func, '__dict__', {})} | |
849 else: | |
850 kwargs = {'name': func.__name__, | |
851 'doc': func.__doc__, | |
852 'module': getattr(func, '__module__', None), # e.g., method_descriptor | |
853 'annotations': getattr(func, "__annotations__", {}), | |
854 'dict': getattr(func, '__dict__', {})} | |
855 | |
856 kwargs.update(cls._argspec_to_dict(func)) | |
857 | |
858 if _inspect_iscoroutinefunction(func): | |
859 kwargs['is_async'] = True | |
860 | |
861 return cls(**kwargs) | |
862 | |
863 def get_func(self, execdict=None, add_source=True, with_dict=True): | |
864 """Compile and return a new function based on the current values of | |
865 the FunctionBuilder. | |
866 | |
867 Args: | |
868 execdict (dict): The dictionary representing the scope in | |
869 which the compilation should take place. Defaults to an empty | |
870 dict. | |
871 add_source (bool): Whether to add the source used to a | |
872 special ``__source__`` attribute on the resulting | |
873 function. Defaults to True. | |
874 with_dict (bool): Add any custom attributes, if | |
875 applicable. Defaults to True. | |
876 | |
877 To see an example of usage, see the implementation of | |
878 :func:`~boltons.funcutils.wraps`. | |
879 """ | |
880 execdict = execdict or {} | |
881 body = self.body or self._default_body | |
882 | |
883 tmpl = 'def {name}{sig_str}:' | |
884 tmpl += '\n{body}' | |
885 | |
886 if self.is_async: | |
887 tmpl = 'async ' + tmpl | |
888 | |
889 body = _indent(self.body, ' ' * self.indent) | |
890 | |
891 name = self.name.replace('<', '_').replace('>', '_') # lambdas | |
892 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False), | |
893 doc=self.doc, body=body) | |
894 self._compile(src, execdict) | |
895 func = execdict[name] | |
896 | |
897 func.__name__ = self.name | |
898 func.__doc__ = self.doc | |
899 func.__defaults__ = self.defaults | |
900 if not _IS_PY2: | |
901 func.__kwdefaults__ = self.kwonlydefaults | |
902 func.__annotations__ = self.annotations | |
903 | |
904 if with_dict: | |
905 func.__dict__.update(self.dict) | |
906 func.__module__ = self.module | |
907 # TODO: caller module fallback? | |
908 | |
909 if add_source: | |
910 func.__source__ = src | |
911 | |
912 return func | |
913 | |
914 def get_defaults_dict(self): | |
915 """Get a dictionary of function arguments with defaults and the | |
916 respective values. | |
917 """ | |
918 ret = dict(reversed(list(zip(reversed(self.args), | |
919 reversed(self.defaults or []))))) | |
920 kwonlydefaults = getattr(self, 'kwonlydefaults', None) | |
921 if kwonlydefaults: | |
922 ret.update(kwonlydefaults) | |
923 return ret | |
924 | |
925 def get_arg_names(self, only_required=False): | |
926 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ())) | |
927 if only_required: | |
928 defaults_dict = self.get_defaults_dict() | |
929 arg_names = tuple([an for an in arg_names if an not in defaults_dict]) | |
930 return arg_names | |
931 | |
932 if _IS_PY2: | |
933 def add_arg(self, arg_name, default=NO_DEFAULT): | |
934 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)." | |
935 if arg_name in self.args: | |
936 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) | |
937 self.args.append(arg_name) | |
938 if default is not NO_DEFAULT: | |
939 self.defaults = (self.defaults or ()) + (default,) | |
940 return | |
941 else: | |
942 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False): | |
943 """Add an argument with optional *default* (defaults to | |
944 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a | |
945 keyword-only argument | |
946 """ | |
947 if arg_name in self.args: | |
948 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) | |
949 if arg_name in self.kwonlyargs: | |
950 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name)) | |
951 if not kwonly: | |
952 self.args.append(arg_name) | |
953 if default is not NO_DEFAULT: | |
954 self.defaults = (self.defaults or ()) + (default,) | |
955 else: | |
956 self.kwonlyargs.append(arg_name) | |
957 if default is not NO_DEFAULT: | |
958 self.kwonlydefaults[arg_name] = default | |
959 return | |
960 | |
961 def remove_arg(self, arg_name): | |
962 """Remove an argument from this FunctionBuilder's argument list. The | |
963 resulting function will have one less argument per call to | |
964 this function. | |
965 | |
966 Args: | |
967 arg_name (str): The name of the argument to remove. | |
968 | |
969 Raises a :exc:`ValueError` if the argument is not present. | |
970 | |
971 """ | |
972 args = self.args | |
973 d_dict = self.get_defaults_dict() | |
974 try: | |
975 args.remove(arg_name) | |
976 except ValueError: | |
977 try: | |
978 self.kwonlyargs.remove(arg_name) | |
979 except (AttributeError, ValueError): | |
980 # py2, or py3 and missing from both | |
981 exc = MissingArgument('arg %r not found in %s argument list:' | |
982 ' %r' % (arg_name, self.name, args)) | |
983 exc.arg_name = arg_name | |
984 raise exc | |
985 else: | |
986 self.kwonlydefaults.pop(arg_name, None) | |
987 else: | |
988 d_dict.pop(arg_name, None) | |
989 self.defaults = tuple([d_dict[a] for a in args if a in d_dict]) | |
990 return | |
991 | |
992 def _compile(self, src, execdict): | |
993 | |
994 filename = ('<%s-%d>' | |
995 % (self.filename, next(self._compile_count),)) | |
996 try: | |
997 code = compile(src, filename, 'single') | |
998 exec(code, execdict) | |
999 except Exception: | |
1000 raise | |
1001 return execdict | |
1002 | |
1003 | |
1004 class MissingArgument(ValueError): | |
1005 pass | |
1006 | |
1007 | |
1008 class ExistingArgument(ValueError): | |
1009 pass | |
1010 | |
1011 | |
1012 def _indent(text, margin, newline='\n', key=bool): | |
1013 "based on boltons.strutils.indent" | |
1014 indented_lines = [(margin + line if key(line) else line) | |
1015 for line in text.splitlines()] | |
1016 return newline.join(indented_lines) | |
1017 | |
1018 | |
1019 try: | |
1020 from functools import total_ordering # 2.7+ | |
1021 except ImportError: | |
1022 # python 2.6 | |
1023 def total_ordering(cls): | |
1024 """Class decorator that fills in missing comparators/ordering | |
1025 methods. Backport of :func:`functools.total_ordering` to work | |
1026 with Python 2.6. | |
1027 | |
1028 Code from http://code.activestate.com/recipes/576685/ | |
1029 """ | |
1030 convert = { | |
1031 '__lt__': [ | |
1032 ('__gt__', | |
1033 lambda self, other: not (self < other or self == other)), | |
1034 ('__le__', | |
1035 lambda self, other: self < other or self == other), | |
1036 ('__ge__', | |
1037 lambda self, other: not self < other)], | |
1038 '__le__': [ | |
1039 ('__ge__', | |
1040 lambda self, other: not self <= other or self == other), | |
1041 ('__lt__', | |
1042 lambda self, other: self <= other and not self == other), | |
1043 ('__gt__', | |
1044 lambda self, other: not self <= other)], | |
1045 '__gt__': [ | |
1046 ('__lt__', | |
1047 lambda self, other: not (self > other or self == other)), | |
1048 ('__ge__', | |
1049 lambda self, other: self > other or self == other), | |
1050 ('__le__', | |
1051 lambda self, other: not self > other)], | |
1052 '__ge__': [ | |
1053 ('__le__', | |
1054 lambda self, other: (not self >= other) or self == other), | |
1055 ('__gt__', | |
1056 lambda self, other: self >= other and not self == other), | |
1057 ('__lt__', | |
1058 lambda self, other: not self >= other)] | |
1059 } | |
1060 roots = set(dir(cls)) & set(convert) | |
1061 if not roots: | |
1062 raise ValueError('must define at least one ordering operation:' | |
1063 ' < > <= >=') | |
1064 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__ | |
1065 for opname, opfunc in convert[root]: | |
1066 if opname not in roots: | |
1067 opfunc.__name__ = opname | |
1068 opfunc.__doc__ = getattr(int, opname).__doc__ | |
1069 setattr(cls, opname, opfunc) | |
1070 return cls | |
1071 | |
1072 # end funcutils.py |