Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/boltons/funcutils.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
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): | |
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 kwargs = kwargs or {} | |
314 a_text = ', '.join([repr(a) for a in args]) | |
315 if isinstance(kwargs, dict): | |
316 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)] | |
317 else: | |
318 kwarg_items = kwargs | |
319 kw_text = ', '.join(['%s=%r' % (k, v) for k, v in kwarg_items]) | |
320 | |
321 all_args_text = a_text | |
322 if all_args_text and kw_text: | |
323 all_args_text += ', ' | |
324 all_args_text += kw_text | |
325 | |
326 return '%s(%s)' % (name, all_args_text) | |
327 | |
328 | |
329 def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None): | |
330 """Render an expression-style repr of an object, based on attribute | |
331 names, which are assumed to line up with arguments to an initializer. | |
332 | |
333 >>> class Flag(object): | |
334 ... def __init__(self, length, width, depth=None): | |
335 ... self.length = length | |
336 ... self.width = width | |
337 ... self.depth = depth | |
338 ... | |
339 | |
340 That's our Flag object, here are some example reprs for it: | |
341 | |
342 >>> flag = Flag(5, 10) | |
343 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth'])) | |
344 Flag(5, 10) | |
345 >>> flag2 = Flag(5, 15, 2) | |
346 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth'])) | |
347 Flag(5, width=15, depth=2) | |
348 | |
349 By picking the pos_names, req_names, opt_names, and opt_key, you | |
350 can fine-tune how you want the repr to look. | |
351 | |
352 Args: | |
353 obj (object): The object whose type name will be used and | |
354 attributes will be checked | |
355 pos_names (list): Required list of attribute names which will be | |
356 rendered as positional arguments in the output repr. | |
357 req_names (list): List of attribute names which will always | |
358 appear in the keyword arguments in the output repr. Defaults to None. | |
359 opt_names (list): List of attribute names which may appear in | |
360 the keyword arguments in the output repr, provided they pass | |
361 the *opt_key* check. Defaults to None. | |
362 opt_key (callable): A function or callable which checks whether | |
363 an opt_name should be in the repr. Defaults to a | |
364 ``None``-check. | |
365 | |
366 """ | |
367 cn = obj.__class__.__name__ | |
368 req_names = req_names or [] | |
369 opt_names = opt_names or [] | |
370 uniq_names, all_names = set(), [] | |
371 for name in req_names + opt_names: | |
372 if name in uniq_names: | |
373 continue | |
374 uniq_names.add(name) | |
375 all_names.append(name) | |
376 | |
377 if opt_key is None: | |
378 opt_key = lambda v: v is None | |
379 assert callable(opt_key) | |
380 | |
381 args = [getattr(obj, name, None) for name in pos_names] | |
382 | |
383 kw_items = [(name, getattr(obj, name, None)) for name in all_names] | |
384 kw_items = [(name, val) for name, val in kw_items | |
385 if not (name in opt_names and opt_key(val))] | |
386 | |
387 return format_invocation(cn, args, kw_items) | |
388 | |
389 | |
390 def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None): | |
391 """Format a non-expression-style repr | |
392 | |
393 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]). | |
394 | |
395 This makes sense for smaller, lower-level objects whose state | |
396 roundtrips. But a lot of objects contain values that don't | |
397 roundtrip, like types and functions. | |
398 | |
399 For those objects, there is the non-expression style repr, which | |
400 mimic's Python's default style to make a repr like so: | |
401 | |
402 >>> class Flag(object): | |
403 ... def __init__(self, length, width, depth=None): | |
404 ... self.length = length | |
405 ... self.width = width | |
406 ... self.depth = depth | |
407 ... | |
408 >>> flag = Flag(5, 10) | |
409 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth'])) | |
410 <Flag length=5 width=10> | |
411 | |
412 If no attributes are specified or set, utilizes the id, not unlike Python's | |
413 built-in behavior. | |
414 | |
415 >>> print(format_nonexp_repr(flag)) | |
416 <Flag id=...> | |
417 """ | |
418 cn = obj.__class__.__name__ | |
419 req_names = req_names or [] | |
420 opt_names = opt_names or [] | |
421 uniq_names, all_names = set(), [] | |
422 for name in req_names + opt_names: | |
423 if name in uniq_names: | |
424 continue | |
425 uniq_names.add(name) | |
426 all_names.append(name) | |
427 | |
428 if opt_key is None: | |
429 opt_key = lambda v: v is None | |
430 assert callable(opt_key) | |
431 | |
432 items = [(name, getattr(obj, name, None)) for name in all_names] | |
433 labels = ['%s=%r' % (name, val) for name, val in items | |
434 if not (name in opt_names and opt_key(val))] | |
435 if not labels: | |
436 labels = ['id=%s' % id(obj)] | |
437 ret = '<%s %s>' % (cn, ' '.join(labels)) | |
438 return ret | |
439 | |
440 | |
441 | |
442 # # # | |
443 # # # Function builder | |
444 # # # | |
445 | |
446 | |
447 def wraps(func, injected=None, expected=None, **kw): | |
448 """Decorator factory to apply update_wrapper() to a wrapper function. | |
449 | |
450 Modeled after built-in :func:`functools.wraps`. Returns a decorator | |
451 that invokes update_wrapper() with the decorated function as the wrapper | |
452 argument and the arguments to wraps() as the remaining arguments. | |
453 Default arguments are as for update_wrapper(). This is a convenience | |
454 function to simplify applying partial() to update_wrapper(). | |
455 | |
456 Same example as in update_wrapper's doc but with wraps: | |
457 | |
458 >>> from boltons.funcutils import wraps | |
459 >>> | |
460 >>> def print_return(func): | |
461 ... @wraps(func) | |
462 ... def wrapper(*args, **kwargs): | |
463 ... ret = func(*args, **kwargs) | |
464 ... print(ret) | |
465 ... return ret | |
466 ... return wrapper | |
467 ... | |
468 >>> @print_return | |
469 ... def example(): | |
470 ... '''docstring''' | |
471 ... return 'example return value' | |
472 >>> | |
473 >>> val = example() | |
474 example return value | |
475 >>> example.__name__ | |
476 'example' | |
477 >>> example.__doc__ | |
478 'docstring' | |
479 """ | |
480 return partial(update_wrapper, func=func, build_from=None, | |
481 injected=injected, expected=expected, **kw) | |
482 | |
483 | |
484 def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw): | |
485 """Modeled after the built-in :func:`functools.update_wrapper`, | |
486 this function is used to make your wrapper function reflect the | |
487 wrapped function's: | |
488 | |
489 * Name | |
490 * Documentation | |
491 * Module | |
492 * Signature | |
493 | |
494 The built-in :func:`functools.update_wrapper` copies the first three, but | |
495 does not copy the signature. This version of ``update_wrapper`` can copy | |
496 the inner function's signature exactly, allowing seamless usage | |
497 and :mod:`introspection <inspect>`. Usage is identical to the | |
498 built-in version:: | |
499 | |
500 >>> from boltons.funcutils import update_wrapper | |
501 >>> | |
502 >>> def print_return(func): | |
503 ... def wrapper(*args, **kwargs): | |
504 ... ret = func(*args, **kwargs) | |
505 ... print(ret) | |
506 ... return ret | |
507 ... return update_wrapper(wrapper, func) | |
508 ... | |
509 >>> @print_return | |
510 ... def example(): | |
511 ... '''docstring''' | |
512 ... return 'example return value' | |
513 >>> | |
514 >>> val = example() | |
515 example return value | |
516 >>> example.__name__ | |
517 'example' | |
518 >>> example.__doc__ | |
519 'docstring' | |
520 | |
521 In addition, the boltons version of update_wrapper supports | |
522 modifying the outer signature. By passing a list of | |
523 *injected* argument names, those arguments will be removed from | |
524 the outer wrapper's signature, allowing your decorator to provide | |
525 arguments that aren't passed in. | |
526 | |
527 Args: | |
528 | |
529 wrapper (function) : The callable to which the attributes of | |
530 *func* are to be copied. | |
531 func (function): The callable whose attributes are to be copied. | |
532 injected (list): An optional list of argument names which | |
533 should not appear in the new wrapper's signature. | |
534 expected (list): An optional list of argument names (or (name, | |
535 default) pairs) representing new arguments introduced by | |
536 the wrapper (the opposite of *injected*). See | |
537 :meth:`FunctionBuilder.add_arg()` for more details. | |
538 build_from (function): The callable from which the new wrapper | |
539 is built. Defaults to *func*, unless *wrapper* is partial object | |
540 built from *func*, in which case it defaults to *wrapper*. | |
541 Useful in some specific cases where *wrapper* and *func* have the | |
542 same arguments but differ on which are keyword-only and positional-only. | |
543 update_dict (bool): Whether to copy other, non-standard | |
544 attributes of *func* over to the wrapper. Defaults to True. | |
545 inject_to_varkw (bool): Ignore missing arguments when a | |
546 ``**kwargs``-type catch-all is present. Defaults to True. | |
547 hide_wrapped (bool): Remove reference to the wrapped function(s) | |
548 in the updated function. | |
549 | |
550 In opposition to the built-in :func:`functools.update_wrapper` bolton's | |
551 version returns a copy of the function and does not modifiy anything in place. | |
552 For more in-depth wrapping of functions, see the | |
553 :class:`FunctionBuilder` type, on which update_wrapper was built. | |
554 """ | |
555 if injected is None: | |
556 injected = [] | |
557 elif isinstance(injected, basestring): | |
558 injected = [injected] | |
559 else: | |
560 injected = list(injected) | |
561 | |
562 expected_items = _parse_wraps_expected(expected) | |
563 | |
564 if isinstance(func, (classmethod, staticmethod)): | |
565 raise TypeError('wraps does not support wrapping classmethods and' | |
566 ' staticmethods, change the order of wrapping to' | |
567 ' wrap the underlying function: %r' | |
568 % (getattr(func, '__func__', None),)) | |
569 | |
570 update_dict = kw.pop('update_dict', True) | |
571 inject_to_varkw = kw.pop('inject_to_varkw', True) | |
572 hide_wrapped = kw.pop('hide_wrapped', False) | |
573 if kw: | |
574 raise TypeError('unexpected kwargs: %r' % kw.keys()) | |
575 | |
576 if isinstance(wrapper, functools.partial) and func is wrapper.func: | |
577 build_from = build_from or wrapper | |
578 | |
579 fb = FunctionBuilder.from_func(build_from or func) | |
580 | |
581 for arg in injected: | |
582 try: | |
583 fb.remove_arg(arg) | |
584 except MissingArgument: | |
585 if inject_to_varkw and fb.varkw is not None: | |
586 continue # keyword arg will be caught by the varkw | |
587 raise | |
588 | |
589 for arg, default in expected_items: | |
590 fb.add_arg(arg, default) # may raise ExistingArgument | |
591 | |
592 if fb.is_async: | |
593 fb.body = 'return await _call(%s)' % fb.get_invocation_str() | |
594 else: | |
595 fb.body = 'return _call(%s)' % fb.get_invocation_str() | |
596 | |
597 execdict = dict(_call=wrapper, _func=func) | |
598 fully_wrapped = fb.get_func(execdict, with_dict=update_dict) | |
599 | |
600 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'): | |
601 del fully_wrapped.__dict__['__wrapped__'] | |
602 elif not hide_wrapped: | |
603 fully_wrapped.__wrapped__ = func # ref to the original function (#115) | |
604 | |
605 return fully_wrapped | |
606 | |
607 | |
608 def _parse_wraps_expected(expected): | |
609 # expected takes a pretty powerful argument, it's processed | |
610 # here. admittedly this would be less trouble if I relied on | |
611 # OrderedDict (there's an impl of that in the commit history if | |
612 # you look | |
613 if expected is None: | |
614 expected = [] | |
615 elif isinstance(expected, basestring): | |
616 expected = [(expected, NO_DEFAULT)] | |
617 | |
618 expected_items = [] | |
619 try: | |
620 expected_iter = iter(expected) | |
621 except TypeError as e: | |
622 raise ValueError('"expected" takes string name, sequence of string names,' | |
623 ' iterable of (name, default) pairs, or a mapping of ' | |
624 ' {name: default}, not %r (got: %r)' % (expected, e)) | |
625 for argname in expected_iter: | |
626 if isinstance(argname, basestring): | |
627 # dict keys and bare strings | |
628 try: | |
629 default = expected[argname] | |
630 except TypeError: | |
631 default = NO_DEFAULT | |
632 else: | |
633 # pairs | |
634 try: | |
635 argname, default = argname | |
636 except (TypeError, ValueError): | |
637 raise ValueError('"expected" takes string name, sequence of string names,' | |
638 ' iterable of (name, default) pairs, or a mapping of ' | |
639 ' {name: default}, not %r') | |
640 if not isinstance(argname, basestring): | |
641 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,)) | |
642 | |
643 expected_items.append((argname, default)) | |
644 | |
645 return expected_items | |
646 | |
647 | |
648 class FunctionBuilder(object): | |
649 """The FunctionBuilder type provides an interface for programmatically | |
650 creating new functions, either based on existing functions or from | |
651 scratch. | |
652 | |
653 Values are passed in at construction or set as attributes on the | |
654 instance. For creating a new function based of an existing one, | |
655 see the :meth:`~FunctionBuilder.from_func` classmethod. At any | |
656 point, :meth:`~FunctionBuilder.get_func` can be called to get a | |
657 newly compiled function, based on the values configured. | |
658 | |
659 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5', | |
660 ... body='return 5') | |
661 >>> f = fb.get_func() | |
662 >>> f() | |
663 5 | |
664 >>> fb.varkw = 'kw' | |
665 >>> f_kw = fb.get_func() | |
666 >>> f_kw(ignored_arg='ignored_val') | |
667 5 | |
668 | |
669 Note that function signatures themselves changed quite a bit in | |
670 Python 3, so several arguments are only applicable to | |
671 FunctionBuilder in Python 3. Except for *name*, all arguments to | |
672 the constructor are keyword arguments. | |
673 | |
674 Args: | |
675 name (str): Name of the function. | |
676 doc (str): `Docstring`_ for the function, defaults to empty. | |
677 module (str): Name of the module from which this function was | |
678 imported. Defaults to None. | |
679 body (str): String version of the code representing the body | |
680 of the function. Defaults to ``'pass'``, which will result | |
681 in a function which does nothing and returns ``None``. | |
682 args (list): List of argument names, defaults to empty list, | |
683 denoting no arguments. | |
684 varargs (str): Name of the catch-all variable for positional | |
685 arguments. E.g., "args" if the resultant function is to have | |
686 ``*args`` in the signature. Defaults to None. | |
687 varkw (str): Name of the catch-all variable for keyword | |
688 arguments. E.g., "kwargs" if the resultant function is to have | |
689 ``**kwargs`` in the signature. Defaults to None. | |
690 defaults (tuple): A tuple containing default argument values for | |
691 those arguments that have defaults. | |
692 kwonlyargs (list): Argument names which are only valid as | |
693 keyword arguments. **Python 3 only.** | |
694 kwonlydefaults (dict): A mapping, same as normal *defaults*, | |
695 but only for the *kwonlyargs*. **Python 3 only.** | |
696 annotations (dict): Mapping of type hints and so | |
697 forth. **Python 3 only.** | |
698 filename (str): The filename that will appear in | |
699 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder". | |
700 indent (int): Number of spaces with which to indent the | |
701 function *body*. Values less than 1 will result in an error. | |
702 dict (dict): Any other attributes which should be added to the | |
703 functions compiled with this FunctionBuilder. | |
704 | |
705 All of these arguments are also made available as attributes which | |
706 can be mutated as necessary. | |
707 | |
708 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python | |
709 | |
710 """ | |
711 | |
712 if _IS_PY2: | |
713 _argspec_defaults = {'args': list, | |
714 'varargs': lambda: None, | |
715 'varkw': lambda: None, | |
716 'defaults': lambda: None} | |
717 | |
718 @classmethod | |
719 def _argspec_to_dict(cls, f): | |
720 args, varargs, varkw, defaults = inspect.getargspec(f) | |
721 return {'args': args, | |
722 'varargs': varargs, | |
723 'varkw': varkw, | |
724 'defaults': defaults} | |
725 | |
726 else: | |
727 _argspec_defaults = {'args': list, | |
728 'varargs': lambda: None, | |
729 'varkw': lambda: None, | |
730 'defaults': lambda: None, | |
731 'kwonlyargs': list, | |
732 'kwonlydefaults': dict, | |
733 'annotations': dict} | |
734 | |
735 @classmethod | |
736 def _argspec_to_dict(cls, f): | |
737 argspec = inspect.getfullargspec(f) | |
738 return dict((attr, getattr(argspec, attr)) | |
739 for attr in cls._argspec_defaults) | |
740 | |
741 _defaults = {'doc': str, | |
742 'dict': dict, | |
743 'is_async': lambda: False, | |
744 'module': lambda: None, | |
745 'body': lambda: 'pass', | |
746 'indent': lambda: 4, | |
747 "annotations": dict, | |
748 'filename': lambda: 'boltons.funcutils.FunctionBuilder'} | |
749 | |
750 _defaults.update(_argspec_defaults) | |
751 | |
752 _compile_count = itertools.count() | |
753 | |
754 def __init__(self, name, **kw): | |
755 self.name = name | |
756 for a, default_factory in self._defaults.items(): | |
757 val = kw.pop(a, None) | |
758 if val is None: | |
759 val = default_factory() | |
760 setattr(self, a, val) | |
761 | |
762 if kw: | |
763 raise TypeError('unexpected kwargs: %r' % kw.keys()) | |
764 return | |
765 | |
766 # def get_argspec(self): # TODO | |
767 | |
768 if _IS_PY2: | |
769 def get_sig_str(self, with_annotations=True): | |
770 """Return function signature as a string. | |
771 | |
772 with_annotations is ignored on Python 2. On Python 3 signature | |
773 will omit annotations if it is set to False. | |
774 """ | |
775 return inspect_formatargspec(self.args, self.varargs, | |
776 self.varkw, []) | |
777 | |
778 def get_invocation_str(self): | |
779 return inspect_formatargspec(self.args, self.varargs, | |
780 self.varkw, [])[1:-1] | |
781 else: | |
782 def get_sig_str(self, with_annotations=True): | |
783 """Return function signature as a string. | |
784 | |
785 with_annotations is ignored on Python 2. On Python 3 signature | |
786 will omit annotations if it is set to False. | |
787 """ | |
788 if with_annotations: | |
789 annotations = self.annotations | |
790 else: | |
791 annotations = {} | |
792 | |
793 return inspect_formatargspec(self.args, | |
794 self.varargs, | |
795 self.varkw, | |
796 [], | |
797 self.kwonlyargs, | |
798 {}, | |
799 annotations) | |
800 | |
801 _KWONLY_MARKER = re.compile(r""" | |
802 \* # a star | |
803 \s* # followed by any amount of whitespace | |
804 , # followed by a comma | |
805 \s* # followed by any amount of whitespace | |
806 """, re.VERBOSE) | |
807 | |
808 def get_invocation_str(self): | |
809 kwonly_pairs = None | |
810 formatters = {} | |
811 if self.kwonlyargs: | |
812 kwonly_pairs = dict((arg, arg) | |
813 for arg in self.kwonlyargs) | |
814 formatters['formatvalue'] = lambda value: '=' + value | |
815 | |
816 sig = inspect_formatargspec(self.args, | |
817 self.varargs, | |
818 self.varkw, | |
819 [], | |
820 kwonly_pairs, | |
821 kwonly_pairs, | |
822 {}, | |
823 **formatters) | |
824 sig = self._KWONLY_MARKER.sub('', sig) | |
825 return sig[1:-1] | |
826 | |
827 @classmethod | |
828 def from_func(cls, func): | |
829 """Create a new FunctionBuilder instance based on an existing | |
830 function. The original function will not be stored or | |
831 modified. | |
832 """ | |
833 # TODO: copy_body? gonna need a good signature regex. | |
834 # TODO: might worry about __closure__? | |
835 if not callable(func): | |
836 raise TypeError('expected callable object, not %r' % (func,)) | |
837 | |
838 if isinstance(func, functools.partial): | |
839 if _IS_PY2: | |
840 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.') | |
841 kwargs = {'name': func.func.__name__, | |
842 'doc': func.func.__doc__, | |
843 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor | |
844 'annotations': getattr(func.func, "__annotations__", {}), | |
845 'dict': getattr(func.func, '__dict__', {})} | |
846 else: | |
847 kwargs = {'name': func.__name__, | |
848 'doc': func.__doc__, | |
849 'module': getattr(func, '__module__', None), # e.g., method_descriptor | |
850 'annotations': getattr(func, "__annotations__", {}), | |
851 'dict': getattr(func, '__dict__', {})} | |
852 | |
853 kwargs.update(cls._argspec_to_dict(func)) | |
854 | |
855 if _inspect_iscoroutinefunction(func): | |
856 kwargs['is_async'] = True | |
857 | |
858 return cls(**kwargs) | |
859 | |
860 def get_func(self, execdict=None, add_source=True, with_dict=True): | |
861 """Compile and return a new function based on the current values of | |
862 the FunctionBuilder. | |
863 | |
864 Args: | |
865 execdict (dict): The dictionary representing the scope in | |
866 which the compilation should take place. Defaults to an empty | |
867 dict. | |
868 add_source (bool): Whether to add the source used to a | |
869 special ``__source__`` attribute on the resulting | |
870 function. Defaults to True. | |
871 with_dict (bool): Add any custom attributes, if | |
872 applicable. Defaults to True. | |
873 | |
874 To see an example of usage, see the implementation of | |
875 :func:`~boltons.funcutils.wraps`. | |
876 """ | |
877 execdict = execdict or {} | |
878 body = self.body or self._default_body | |
879 | |
880 tmpl = 'def {name}{sig_str}:' | |
881 tmpl += '\n{body}' | |
882 | |
883 if self.is_async: | |
884 tmpl = 'async ' + tmpl | |
885 | |
886 body = _indent(self.body, ' ' * self.indent) | |
887 | |
888 name = self.name.replace('<', '_').replace('>', '_') # lambdas | |
889 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False), | |
890 doc=self.doc, body=body) | |
891 self._compile(src, execdict) | |
892 func = execdict[name] | |
893 | |
894 func.__name__ = self.name | |
895 func.__doc__ = self.doc | |
896 func.__defaults__ = self.defaults | |
897 if not _IS_PY2: | |
898 func.__kwdefaults__ = self.kwonlydefaults | |
899 func.__annotations__ = self.annotations | |
900 | |
901 if with_dict: | |
902 func.__dict__.update(self.dict) | |
903 func.__module__ = self.module | |
904 # TODO: caller module fallback? | |
905 | |
906 if add_source: | |
907 func.__source__ = src | |
908 | |
909 return func | |
910 | |
911 def get_defaults_dict(self): | |
912 """Get a dictionary of function arguments with defaults and the | |
913 respective values. | |
914 """ | |
915 ret = dict(reversed(list(zip(reversed(self.args), | |
916 reversed(self.defaults or []))))) | |
917 kwonlydefaults = getattr(self, 'kwonlydefaults', None) | |
918 if kwonlydefaults: | |
919 ret.update(kwonlydefaults) | |
920 return ret | |
921 | |
922 def get_arg_names(self, only_required=False): | |
923 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ())) | |
924 if only_required: | |
925 defaults_dict = self.get_defaults_dict() | |
926 arg_names = tuple([an for an in arg_names if an not in defaults_dict]) | |
927 return arg_names | |
928 | |
929 if _IS_PY2: | |
930 def add_arg(self, arg_name, default=NO_DEFAULT): | |
931 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)." | |
932 if arg_name in self.args: | |
933 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) | |
934 self.args.append(arg_name) | |
935 if default is not NO_DEFAULT: | |
936 self.defaults = (self.defaults or ()) + (default,) | |
937 return | |
938 else: | |
939 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False): | |
940 """Add an argument with optional *default* (defaults to | |
941 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a | |
942 keyword-only argument | |
943 """ | |
944 if arg_name in self.args: | |
945 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) | |
946 if arg_name in self.kwonlyargs: | |
947 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name)) | |
948 if not kwonly: | |
949 self.args.append(arg_name) | |
950 if default is not NO_DEFAULT: | |
951 self.defaults = (self.defaults or ()) + (default,) | |
952 else: | |
953 self.kwonlyargs.append(arg_name) | |
954 if default is not NO_DEFAULT: | |
955 self.kwonlydefaults[arg_name] = default | |
956 return | |
957 | |
958 def remove_arg(self, arg_name): | |
959 """Remove an argument from this FunctionBuilder's argument list. The | |
960 resulting function will have one less argument per call to | |
961 this function. | |
962 | |
963 Args: | |
964 arg_name (str): The name of the argument to remove. | |
965 | |
966 Raises a :exc:`ValueError` if the argument is not present. | |
967 | |
968 """ | |
969 args = self.args | |
970 d_dict = self.get_defaults_dict() | |
971 try: | |
972 args.remove(arg_name) | |
973 except ValueError: | |
974 try: | |
975 self.kwonlyargs.remove(arg_name) | |
976 except (AttributeError, ValueError): | |
977 # py2, or py3 and missing from both | |
978 exc = MissingArgument('arg %r not found in %s argument list:' | |
979 ' %r' % (arg_name, self.name, args)) | |
980 exc.arg_name = arg_name | |
981 raise exc | |
982 else: | |
983 self.kwonlydefaults.pop(arg_name, None) | |
984 else: | |
985 d_dict.pop(arg_name, None) | |
986 self.defaults = tuple([d_dict[a] for a in args if a in d_dict]) | |
987 return | |
988 | |
989 def _compile(self, src, execdict): | |
990 | |
991 filename = ('<%s-%d>' | |
992 % (self.filename, next(self._compile_count),)) | |
993 try: | |
994 code = compile(src, filename, 'single') | |
995 exec(code, execdict) | |
996 except Exception: | |
997 raise | |
998 return execdict | |
999 | |
1000 | |
1001 class MissingArgument(ValueError): | |
1002 pass | |
1003 | |
1004 | |
1005 class ExistingArgument(ValueError): | |
1006 pass | |
1007 | |
1008 | |
1009 def _indent(text, margin, newline='\n', key=bool): | |
1010 "based on boltons.strutils.indent" | |
1011 indented_lines = [(margin + line if key(line) else line) | |
1012 for line in text.splitlines()] | |
1013 return newline.join(indented_lines) | |
1014 | |
1015 | |
1016 try: | |
1017 from functools import total_ordering # 2.7+ | |
1018 except ImportError: | |
1019 # python 2.6 | |
1020 def total_ordering(cls): | |
1021 """Class decorator that fills in missing comparators/ordering | |
1022 methods. Backport of :func:`functools.total_ordering` to work | |
1023 with Python 2.6. | |
1024 | |
1025 Code from http://code.activestate.com/recipes/576685/ | |
1026 """ | |
1027 convert = { | |
1028 '__lt__': [ | |
1029 ('__gt__', | |
1030 lambda self, other: not (self < other or self == other)), | |
1031 ('__le__', | |
1032 lambda self, other: self < other or self == other), | |
1033 ('__ge__', | |
1034 lambda self, other: not self < other)], | |
1035 '__le__': [ | |
1036 ('__ge__', | |
1037 lambda self, other: not self <= other or self == other), | |
1038 ('__lt__', | |
1039 lambda self, other: self <= other and not self == other), | |
1040 ('__gt__', | |
1041 lambda self, other: not self <= other)], | |
1042 '__gt__': [ | |
1043 ('__lt__', | |
1044 lambda self, other: not (self > other or self == other)), | |
1045 ('__ge__', | |
1046 lambda self, other: self > other or self == other), | |
1047 ('__le__', | |
1048 lambda self, other: not self > other)], | |
1049 '__ge__': [ | |
1050 ('__le__', | |
1051 lambda self, other: (not self >= other) or self == other), | |
1052 ('__gt__', | |
1053 lambda self, other: self >= other and not self == other), | |
1054 ('__lt__', | |
1055 lambda self, other: not self >= other)] | |
1056 } | |
1057 roots = set(dir(cls)) & set(convert) | |
1058 if not roots: | |
1059 raise ValueError('must define at least one ordering operation:' | |
1060 ' < > <= >=') | |
1061 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__ | |
1062 for opname, opfunc in convert[root]: | |
1063 if opname not in roots: | |
1064 opfunc.__name__ = opname | |
1065 opfunc.__doc__ = getattr(int, opname).__doc__ | |
1066 setattr(cls, opname, opfunc) | |
1067 return cls | |
1068 | |
1069 # end funcutils.py |