Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/boltons/funcutils.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler | 
|---|---|
| date | Fri, 31 Jul 2020 00:18:57 -0400 | 
| parents | |
| children | 
   comparison
  equal
  deleted
  inserted
  replaced
| -1:000000000000 | 0:d30785e31577 | 
|---|---|
| 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 | 
