Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/boltons/funcutils.py: 27%

419 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-11 06:58 +0000

1# -*- coding: utf-8 -*- 

2 

3# Copyright (c) 2013, Mahmoud Hashemi 

4# 

5# Redistribution and use in source and binary forms, with or without 

6# modification, are permitted provided that the following conditions are 

7# met: 

8# 

9# * Redistributions of source code must retain the above copyright 

10# notice, this list of conditions and the following disclaimer. 

11# 

12# * Redistributions in binary form must reproduce the above 

13# copyright notice, this list of conditions and the following 

14# disclaimer in the documentation and/or other materials provided 

15# with the distribution. 

16# 

17# * The names of the contributors may not be used to endorse or 

18# promote products derived from this software without specific 

19# prior written permission. 

20# 

21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 

22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 

23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 

24# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 

25# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 

26# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 

27# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 

28# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 

29# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 

30# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 

31# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

32 

33"""Python's built-in :mod:`functools` module builds several useful 

34utilities on top of Python's first-class function 

35support. ``funcutils`` generally stays in the same vein, adding to and 

36correcting Python's standard metaprogramming facilities. 

37""" 

38from __future__ import print_function 

39 

40import sys 

41import re 

42import inspect 

43import functools 

44import itertools 

45from types import MethodType, FunctionType 

46 

47try: 

48 xrange 

49 make_method = MethodType 

50except NameError: 

51 # Python 3 

52 make_method = lambda desc, obj, obj_type: MethodType(desc, obj) 

53 basestring = (str, bytes) # Python 3 compat 

54 _IS_PY2 = False 

55else: 

56 _IS_PY2 = True 

57 

58 

59try: 

60 _inspect_iscoroutinefunction = inspect.iscoroutinefunction 

61except AttributeError: 

62 # Python 3.4 

63 _inspect_iscoroutinefunction = lambda func: False 

64 

65 

66try: 

67 from .typeutils import make_sentinel 

68 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT') 

69except ImportError: 

70 NO_DEFAULT = object() 

71 

72try: 

73 from functools import partialmethod 

74except ImportError: 

75 partialmethod = None 

76 

77 

78_IS_PY35 = sys.version_info >= (3, 5) 

79if not _IS_PY35: 

80 # py35+ wants you to use signature instead, but 

81 # inspect_formatargspec is way simpler for what it is. Copied the 

82 # vendoring approach from alembic: 

83 # https://github.com/sqlalchemy/alembic/blob/4cdad6aec32b4b5573a2009cc356cb4b144bd359/alembic/util/compat.py#L92 

84 from inspect import formatargspec as inspect_formatargspec 

85else: 

86 from inspect import formatannotation 

87 

88 def inspect_formatargspec( 

89 args, varargs=None, varkw=None, defaults=None, 

90 kwonlyargs=(), kwonlydefaults={}, annotations={}, 

91 formatarg=str, 

92 formatvarargs=lambda name: '*' + name, 

93 formatvarkw=lambda name: '**' + name, 

94 formatvalue=lambda value: '=' + repr(value), 

95 formatreturns=lambda text: ' -> ' + text, 

96 formatannotation=formatannotation): 

97 """Copy formatargspec from python 3.7 standard library. 

98 Python 3 has deprecated formatargspec and requested that Signature 

99 be used instead, however this requires a full reimplementation 

100 of formatargspec() in terms of creating Parameter objects and such. 

101 Instead of introducing all the object-creation overhead and having 

102 to reinvent from scratch, just copy their compatibility routine. 

103 """ 

104 

105 def formatargandannotation(arg): 

106 result = formatarg(arg) 

107 if arg in annotations: 

108 result += ': ' + formatannotation(annotations[arg]) 

109 return result 

110 specs = [] 

111 if defaults: 

112 firstdefault = len(args) - len(defaults) 

113 for i, arg in enumerate(args): 

114 spec = formatargandannotation(arg) 

115 if defaults and i >= firstdefault: 

116 spec = spec + formatvalue(defaults[i - firstdefault]) 

117 specs.append(spec) 

118 if varargs is not None: 

119 specs.append(formatvarargs(formatargandannotation(varargs))) 

120 else: 

121 if kwonlyargs: 

122 specs.append('*') 

123 if kwonlyargs: 

124 for kwonlyarg in kwonlyargs: 

125 spec = formatargandannotation(kwonlyarg) 

126 if kwonlydefaults and kwonlyarg in kwonlydefaults: 

127 spec += formatvalue(kwonlydefaults[kwonlyarg]) 

128 specs.append(spec) 

129 if varkw is not None: 

130 specs.append(formatvarkw(formatargandannotation(varkw))) 

131 result = '(' + ', '.join(specs) + ')' 

132 if 'return' in annotations: 

133 result += formatreturns(formatannotation(annotations['return'])) 

134 return result 

135 

136 

137def get_module_callables(mod, ignore=None): 

138 """Returns two maps of (*types*, *funcs*) from *mod*, optionally 

139 ignoring based on the :class:`bool` return value of the *ignore* 

140 callable. *mod* can be a string name of a module in 

141 :data:`sys.modules` or the module instance itself. 

142 """ 

143 if isinstance(mod, basestring): 

144 mod = sys.modules[mod] 

145 types, funcs = {}, {} 

146 for attr_name in dir(mod): 

147 if ignore and ignore(attr_name): 

148 continue 

149 try: 

150 attr = getattr(mod, attr_name) 

151 except Exception: 

152 continue 

153 try: 

154 attr_mod_name = attr.__module__ 

155 except AttributeError: 

156 continue 

157 if attr_mod_name != mod.__name__: 

158 continue 

159 if isinstance(attr, type): 

160 types[attr_name] = attr 

161 elif callable(attr): 

162 funcs[attr_name] = attr 

163 return types, funcs 

164 

165 

166def mro_items(type_obj): 

167 """Takes a type and returns an iterator over all class variables 

168 throughout the type hierarchy (respecting the MRO). 

169 

170 >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)])) 

171 ['denominator', 'imag', 'numerator', 'real'] 

172 """ 

173 # TODO: handle slots? 

174 return itertools.chain.from_iterable(ct.__dict__.items() 

175 for ct in type_obj.__mro__) 

176 

177 

178def dir_dict(obj, raise_exc=False): 

179 """Return a dictionary of attribute names to values for a given 

180 object. Unlike ``obj.__dict__``, this function returns all 

181 attributes on the object, including ones on parent classes. 

182 """ 

183 # TODO: separate function for handling descriptors on types? 

184 ret = {} 

185 for k in dir(obj): 

186 try: 

187 ret[k] = getattr(obj, k) 

188 except Exception: 

189 if raise_exc: 

190 raise 

191 return ret 

192 

193 

194def copy_function(orig, copy_dict=True): 

195 """Returns a shallow copy of the function, including code object, 

196 globals, closure, etc. 

197 

198 >>> func = lambda: func 

199 >>> func() is func 

200 True 

201 >>> func_copy = copy_function(func) 

202 >>> func_copy() is func 

203 True 

204 >>> func_copy is not func 

205 True 

206 

207 Args: 

208 orig (function): The function to be copied. Must be a 

209 function, not just any method or callable. 

210 copy_dict (bool): Also copy any attributes set on the function 

211 instance. Defaults to ``True``. 

212 """ 

213 ret = FunctionType(orig.__code__, 

214 orig.__globals__, 

215 name=orig.__name__, 

216 argdefs=getattr(orig, "__defaults__", None), 

217 closure=getattr(orig, "__closure__", None)) 

218 if hasattr(orig, "__kwdefaults__"): 

219 ret.__kwdefaults__ = orig.__kwdefaults__ 

220 if copy_dict: 

221 ret.__dict__.update(orig.__dict__) 

222 return ret 

223 

224 

225def partial_ordering(cls): 

226 """Class decorator, similar to :func:`functools.total_ordering`, 

227 except it is used to define `partial orderings`_ (i.e., it is 

228 possible that *x* is neither greater than, equal to, or less than 

229 *y*). It assumes the presence of the ``__le__()`` and ``__ge__()`` 

230 method, but nothing else. It will not override any existing 

231 additional comparison methods. 

232 

233 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set 

234 

235 >>> @partial_ordering 

236 ... class MySet(set): 

237 ... def __le__(self, other): 

238 ... return self.issubset(other) 

239 ... def __ge__(self, other): 

240 ... return self.issuperset(other) 

241 ... 

242 >>> a = MySet([1,2,3]) 

243 >>> b = MySet([1,2]) 

244 >>> c = MySet([1,2,4]) 

245 >>> b < a 

246 True 

247 >>> b > a 

248 False 

249 >>> b < c 

250 True 

251 >>> a < c 

252 False 

253 >>> c > a 

254 False 

255 """ 

256 def __lt__(self, other): return self <= other and not self >= other 

257 def __gt__(self, other): return self >= other and not self <= other 

258 def __eq__(self, other): return self >= other and self <= other 

259 

260 if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__ 

261 if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__ 

262 if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__ 

263 

264 return cls 

265 

266 

267class InstancePartial(functools.partial): 

268 """:class:`functools.partial` is a huge convenience for anyone 

269 working with Python's great first-class functions. It allows 

270 developers to curry arguments and incrementally create simpler 

271 callables for a variety of use cases. 

272 

273 Unfortunately there's one big gap in its usefulness: 

274 methods. Partials just don't get bound as methods and 

275 automatically handed a reference to ``self``. The 

276 ``InstancePartial`` type remedies this by inheriting from 

277 :class:`functools.partial` and implementing the necessary 

278 descriptor protocol. There are no other differences in 

279 implementation or usage. :class:`CachedInstancePartial`, below, 

280 has the same ability, but is slightly more efficient. 

281 

282 """ 

283 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244 

284 @property 

285 def _partialmethod(self): 

286 return partialmethod(self.func, *self.args, **self.keywords) 

287 

288 def __get__(self, obj, obj_type): 

289 return make_method(self, obj, obj_type) 

290 

291 

292 

293class CachedInstancePartial(functools.partial): 

294 """The ``CachedInstancePartial`` is virtually the same as 

295 :class:`InstancePartial`, adding support for method-usage to 

296 :class:`functools.partial`, except that upon first access, it 

297 caches the bound method on the associated object, speeding it up 

298 for future accesses, and bringing the method call overhead to 

299 about the same as non-``partial`` methods. 

300 

301 See the :class:`InstancePartial` docstring for more details. 

302 """ 

303 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244 

304 @property 

305 def _partialmethod(self): 

306 return partialmethod(self.func, *self.args, **self.keywords) 

307 

308 if sys.version_info >= (3, 6): 

309 def __set_name__(self, obj_type, name): 

310 self.__name__ = name 

311 

312 def __get__(self, obj, obj_type): 

313 # These assignments could've been in __init__, but there was 

314 # no simple way to do it without breaking one of PyPy or Py3. 

315 self.__name__ = getattr(self, "__name__", None) 

316 self.__doc__ = self.func.__doc__ 

317 self.__module__ = self.func.__module__ 

318 

319 name = self.__name__ 

320 

321 # if you're on python 3.6+, name will never be `None` bc `__set_name__` sets it when descriptor getting assigned 

322 if name is None: 

323 for k, v in mro_items(obj_type): 

324 if v is self: 

325 self.__name__ = name = k 

326 if obj is None: 

327 return make_method(self, obj, obj_type) 

328 try: 

329 # since this is a data descriptor, this block 

330 # is probably only hit once (per object) 

331 return obj.__dict__[name] 

332 except KeyError: 

333 obj.__dict__[name] = ret = make_method(self, obj, obj_type) 

334 return ret 

335 

336 

337partial = CachedInstancePartial 

338 

339 

340def format_invocation(name='', args=(), kwargs=None, **kw): 

341 """Given a name, positional arguments, and keyword arguments, format 

342 a basic Python-style function call. 

343 

344 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3})) 

345 func(1, 2, c=3) 

346 >>> print(format_invocation('a_func', args=(1,))) 

347 a_func(1) 

348 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)])) 

349 kw_func(a=1, b=2) 

350 

351 """ 

352 _repr = kw.pop('repr', repr) 

353 if kw: 

354 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys())) 

355 kwargs = kwargs or {} 

356 a_text = ', '.join([_repr(a) for a in args]) 

357 if isinstance(kwargs, dict): 

358 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)] 

359 else: 

360 kwarg_items = kwargs 

361 kw_text = ', '.join(['%s=%s' % (k, _repr(v)) for k, v in kwarg_items]) 

362 

363 all_args_text = a_text 

364 if all_args_text and kw_text: 

365 all_args_text += ', ' 

366 all_args_text += kw_text 

367 

368 return '%s(%s)' % (name, all_args_text) 

369 

370 

371def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None): 

372 """Render an expression-style repr of an object, based on attribute 

373 names, which are assumed to line up with arguments to an initializer. 

374 

375 >>> class Flag(object): 

376 ... def __init__(self, length, width, depth=None): 

377 ... self.length = length 

378 ... self.width = width 

379 ... self.depth = depth 

380 ... 

381 

382 That's our Flag object, here are some example reprs for it: 

383 

384 >>> flag = Flag(5, 10) 

385 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth'])) 

386 Flag(5, 10) 

387 >>> flag2 = Flag(5, 15, 2) 

388 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth'])) 

389 Flag(5, width=15, depth=2) 

390 

391 By picking the pos_names, req_names, opt_names, and opt_key, you 

392 can fine-tune how you want the repr to look. 

393 

394 Args: 

395 obj (object): The object whose type name will be used and 

396 attributes will be checked 

397 pos_names (list): Required list of attribute names which will be 

398 rendered as positional arguments in the output repr. 

399 req_names (list): List of attribute names which will always 

400 appear in the keyword arguments in the output repr. Defaults to None. 

401 opt_names (list): List of attribute names which may appear in 

402 the keyword arguments in the output repr, provided they pass 

403 the *opt_key* check. Defaults to None. 

404 opt_key (callable): A function or callable which checks whether 

405 an opt_name should be in the repr. Defaults to a 

406 ``None``-check. 

407 

408 """ 

409 cn = type(obj).__name__ 

410 req_names = req_names or [] 

411 opt_names = opt_names or [] 

412 uniq_names, all_names = set(), [] 

413 for name in req_names + opt_names: 

414 if name in uniq_names: 

415 continue 

416 uniq_names.add(name) 

417 all_names.append(name) 

418 

419 if opt_key is None: 

420 opt_key = lambda v: v is None 

421 assert callable(opt_key) 

422 

423 args = [getattr(obj, name, None) for name in pos_names] 

424 

425 kw_items = [(name, getattr(obj, name, None)) for name in all_names] 

426 kw_items = [(name, val) for name, val in kw_items 

427 if not (name in opt_names and opt_key(val))] 

428 

429 return format_invocation(cn, args, kw_items) 

430 

431 

432def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None): 

433 """Format a non-expression-style repr 

434 

435 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]). 

436 

437 This makes sense for smaller, lower-level objects whose state 

438 roundtrips. But a lot of objects contain values that don't 

439 roundtrip, like types and functions. 

440 

441 For those objects, there is the non-expression style repr, which 

442 mimic's Python's default style to make a repr like so: 

443 

444 >>> class Flag(object): 

445 ... def __init__(self, length, width, depth=None): 

446 ... self.length = length 

447 ... self.width = width 

448 ... self.depth = depth 

449 ... 

450 >>> flag = Flag(5, 10) 

451 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth'])) 

452 <Flag length=5 width=10> 

453 

454 If no attributes are specified or set, utilizes the id, not unlike Python's 

455 built-in behavior. 

456 

457 >>> print(format_nonexp_repr(flag)) 

458 <Flag id=...> 

459 """ 

460 cn = obj.__class__.__name__ 

461 req_names = req_names or [] 

462 opt_names = opt_names or [] 

463 uniq_names, all_names = set(), [] 

464 for name in req_names + opt_names: 

465 if name in uniq_names: 

466 continue 

467 uniq_names.add(name) 

468 all_names.append(name) 

469 

470 if opt_key is None: 

471 opt_key = lambda v: v is None 

472 assert callable(opt_key) 

473 

474 items = [(name, getattr(obj, name, None)) for name in all_names] 

475 labels = ['%s=%r' % (name, val) for name, val in items 

476 if not (name in opt_names and opt_key(val))] 

477 if not labels: 

478 labels = ['id=%s' % id(obj)] 

479 ret = '<%s %s>' % (cn, ' '.join(labels)) 

480 return ret 

481 

482 

483 

484# # # 

485# # # Function builder 

486# # # 

487 

488 

489def wraps(func, injected=None, expected=None, **kw): 

490 """Decorator factory to apply update_wrapper() to a wrapper function. 

491 

492 Modeled after built-in :func:`functools.wraps`. Returns a decorator 

493 that invokes update_wrapper() with the decorated function as the wrapper 

494 argument and the arguments to wraps() as the remaining arguments. 

495 Default arguments are as for update_wrapper(). This is a convenience 

496 function to simplify applying partial() to update_wrapper(). 

497 

498 Same example as in update_wrapper's doc but with wraps: 

499 

500 >>> from boltons.funcutils import wraps 

501 >>> 

502 >>> def print_return(func): 

503 ... @wraps(func) 

504 ... def wrapper(*args, **kwargs): 

505 ... ret = func(*args, **kwargs) 

506 ... print(ret) 

507 ... return ret 

508 ... return wrapper 

509 ... 

510 >>> @print_return 

511 ... def example(): 

512 ... '''docstring''' 

513 ... return 'example return value' 

514 >>> 

515 >>> val = example() 

516 example return value 

517 >>> example.__name__ 

518 'example' 

519 >>> example.__doc__ 

520 'docstring' 

521 """ 

522 return partial(update_wrapper, func=func, build_from=None, 

523 injected=injected, expected=expected, **kw) 

524 

525 

526def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw): 

527 """Modeled after the built-in :func:`functools.update_wrapper`, 

528 this function is used to make your wrapper function reflect the 

529 wrapped function's: 

530 

531 * Name 

532 * Documentation 

533 * Module 

534 * Signature 

535 

536 The built-in :func:`functools.update_wrapper` copies the first three, but 

537 does not copy the signature. This version of ``update_wrapper`` can copy 

538 the inner function's signature exactly, allowing seamless usage 

539 and :mod:`introspection <inspect>`. Usage is identical to the 

540 built-in version:: 

541 

542 >>> from boltons.funcutils import update_wrapper 

543 >>> 

544 >>> def print_return(func): 

545 ... def wrapper(*args, **kwargs): 

546 ... ret = func(*args, **kwargs) 

547 ... print(ret) 

548 ... return ret 

549 ... return update_wrapper(wrapper, func) 

550 ... 

551 >>> @print_return 

552 ... def example(): 

553 ... '''docstring''' 

554 ... return 'example return value' 

555 >>> 

556 >>> val = example() 

557 example return value 

558 >>> example.__name__ 

559 'example' 

560 >>> example.__doc__ 

561 'docstring' 

562 

563 In addition, the boltons version of update_wrapper supports 

564 modifying the outer signature. By passing a list of 

565 *injected* argument names, those arguments will be removed from 

566 the outer wrapper's signature, allowing your decorator to provide 

567 arguments that aren't passed in. 

568 

569 Args: 

570 

571 wrapper (function) : The callable to which the attributes of 

572 *func* are to be copied. 

573 func (function): The callable whose attributes are to be copied. 

574 injected (list): An optional list of argument names which 

575 should not appear in the new wrapper's signature. 

576 expected (list): An optional list of argument names (or (name, 

577 default) pairs) representing new arguments introduced by 

578 the wrapper (the opposite of *injected*). See 

579 :meth:`FunctionBuilder.add_arg()` for more details. 

580 build_from (function): The callable from which the new wrapper 

581 is built. Defaults to *func*, unless *wrapper* is partial object 

582 built from *func*, in which case it defaults to *wrapper*. 

583 Useful in some specific cases where *wrapper* and *func* have the 

584 same arguments but differ on which are keyword-only and positional-only. 

585 update_dict (bool): Whether to copy other, non-standard 

586 attributes of *func* over to the wrapper. Defaults to True. 

587 inject_to_varkw (bool): Ignore missing arguments when a 

588 ``**kwargs``-type catch-all is present. Defaults to True. 

589 hide_wrapped (bool): Remove reference to the wrapped function(s) 

590 in the updated function. 

591 

592 In opposition to the built-in :func:`functools.update_wrapper` bolton's 

593 version returns a copy of the function and does not modify anything in place. 

594 For more in-depth wrapping of functions, see the 

595 :class:`FunctionBuilder` type, on which update_wrapper was built. 

596 """ 

597 if injected is None: 

598 injected = [] 

599 elif isinstance(injected, basestring): 

600 injected = [injected] 

601 else: 

602 injected = list(injected) 

603 

604 expected_items = _parse_wraps_expected(expected) 

605 

606 if isinstance(func, (classmethod, staticmethod)): 

607 raise TypeError('wraps does not support wrapping classmethods and' 

608 ' staticmethods, change the order of wrapping to' 

609 ' wrap the underlying function: %r' 

610 % (getattr(func, '__func__', None),)) 

611 

612 update_dict = kw.pop('update_dict', True) 

613 inject_to_varkw = kw.pop('inject_to_varkw', True) 

614 hide_wrapped = kw.pop('hide_wrapped', False) 

615 if kw: 

616 raise TypeError('unexpected kwargs: %r' % kw.keys()) 

617 

618 if isinstance(wrapper, functools.partial) and func is wrapper.func: 

619 build_from = build_from or wrapper 

620 

621 fb = FunctionBuilder.from_func(build_from or func) 

622 

623 for arg in injected: 

624 try: 

625 fb.remove_arg(arg) 

626 except MissingArgument: 

627 if inject_to_varkw and fb.varkw is not None: 

628 continue # keyword arg will be caught by the varkw 

629 raise 

630 

631 for arg, default in expected_items: 

632 fb.add_arg(arg, default) # may raise ExistingArgument 

633 

634 if fb.is_async: 

635 fb.body = 'return await _call(%s)' % fb.get_invocation_str() 

636 else: 

637 fb.body = 'return _call(%s)' % fb.get_invocation_str() 

638 

639 execdict = dict(_call=wrapper, _func=func) 

640 fully_wrapped = fb.get_func(execdict, with_dict=update_dict) 

641 

642 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'): 

643 del fully_wrapped.__dict__['__wrapped__'] 

644 elif not hide_wrapped: 

645 fully_wrapped.__wrapped__ = func # ref to the original function (#115) 

646 

647 return fully_wrapped 

648 

649 

650def _parse_wraps_expected(expected): 

651 # expected takes a pretty powerful argument, it's processed 

652 # here. admittedly this would be less trouble if I relied on 

653 # OrderedDict (there's an impl of that in the commit history if 

654 # you look 

655 if expected is None: 

656 expected = [] 

657 elif isinstance(expected, basestring): 

658 expected = [(expected, NO_DEFAULT)] 

659 

660 expected_items = [] 

661 try: 

662 expected_iter = iter(expected) 

663 except TypeError as e: 

664 raise ValueError('"expected" takes string name, sequence of string names,' 

665 ' iterable of (name, default) pairs, or a mapping of ' 

666 ' {name: default}, not %r (got: %r)' % (expected, e)) 

667 for argname in expected_iter: 

668 if isinstance(argname, basestring): 

669 # dict keys and bare strings 

670 try: 

671 default = expected[argname] 

672 except TypeError: 

673 default = NO_DEFAULT 

674 else: 

675 # pairs 

676 try: 

677 argname, default = argname 

678 except (TypeError, ValueError): 

679 raise ValueError('"expected" takes string name, sequence of string names,' 

680 ' iterable of (name, default) pairs, or a mapping of ' 

681 ' {name: default}, not %r') 

682 if not isinstance(argname, basestring): 

683 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,)) 

684 

685 expected_items.append((argname, default)) 

686 

687 return expected_items 

688 

689 

690class FunctionBuilder(object): 

691 """The FunctionBuilder type provides an interface for programmatically 

692 creating new functions, either based on existing functions or from 

693 scratch. 

694 

695 Values are passed in at construction or set as attributes on the 

696 instance. For creating a new function based of an existing one, 

697 see the :meth:`~FunctionBuilder.from_func` classmethod. At any 

698 point, :meth:`~FunctionBuilder.get_func` can be called to get a 

699 newly compiled function, based on the values configured. 

700 

701 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5', 

702 ... body='return 5') 

703 >>> f = fb.get_func() 

704 >>> f() 

705 5 

706 >>> fb.varkw = 'kw' 

707 >>> f_kw = fb.get_func() 

708 >>> f_kw(ignored_arg='ignored_val') 

709 5 

710 

711 Note that function signatures themselves changed quite a bit in 

712 Python 3, so several arguments are only applicable to 

713 FunctionBuilder in Python 3. Except for *name*, all arguments to 

714 the constructor are keyword arguments. 

715 

716 Args: 

717 name (str): Name of the function. 

718 doc (str): `Docstring`_ for the function, defaults to empty. 

719 module (str): Name of the module from which this function was 

720 imported. Defaults to None. 

721 body (str): String version of the code representing the body 

722 of the function. Defaults to ``'pass'``, which will result 

723 in a function which does nothing and returns ``None``. 

724 args (list): List of argument names, defaults to empty list, 

725 denoting no arguments. 

726 varargs (str): Name of the catch-all variable for positional 

727 arguments. E.g., "args" if the resultant function is to have 

728 ``*args`` in the signature. Defaults to None. 

729 varkw (str): Name of the catch-all variable for keyword 

730 arguments. E.g., "kwargs" if the resultant function is to have 

731 ``**kwargs`` in the signature. Defaults to None. 

732 defaults (tuple): A tuple containing default argument values for 

733 those arguments that have defaults. 

734 kwonlyargs (list): Argument names which are only valid as 

735 keyword arguments. **Python 3 only.** 

736 kwonlydefaults (dict): A mapping, same as normal *defaults*, 

737 but only for the *kwonlyargs*. **Python 3 only.** 

738 annotations (dict): Mapping of type hints and so 

739 forth. **Python 3 only.** 

740 filename (str): The filename that will appear in 

741 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder". 

742 indent (int): Number of spaces with which to indent the 

743 function *body*. Values less than 1 will result in an error. 

744 dict (dict): Any other attributes which should be added to the 

745 functions compiled with this FunctionBuilder. 

746 

747 All of these arguments are also made available as attributes which 

748 can be mutated as necessary. 

749 

750 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python 

751 

752 """ 

753 

754 if _IS_PY2: 

755 _argspec_defaults = {'args': list, 

756 'varargs': lambda: None, 

757 'varkw': lambda: None, 

758 'defaults': lambda: None} 

759 

760 @classmethod 

761 def _argspec_to_dict(cls, f): 

762 args, varargs, varkw, defaults = inspect.getargspec(f) 

763 return {'args': args, 

764 'varargs': varargs, 

765 'varkw': varkw, 

766 'defaults': defaults} 

767 

768 else: 

769 _argspec_defaults = {'args': list, 

770 'varargs': lambda: None, 

771 'varkw': lambda: None, 

772 'defaults': lambda: None, 

773 'kwonlyargs': list, 

774 'kwonlydefaults': dict, 

775 'annotations': dict} 

776 

777 @classmethod 

778 def _argspec_to_dict(cls, f): 

779 argspec = inspect.getfullargspec(f) 

780 return dict((attr, getattr(argspec, attr)) 

781 for attr in cls._argspec_defaults) 

782 

783 _defaults = {'doc': str, 

784 'dict': dict, 

785 'is_async': lambda: False, 

786 'module': lambda: None, 

787 'body': lambda: 'pass', 

788 'indent': lambda: 4, 

789 "annotations": dict, 

790 'filename': lambda: 'boltons.funcutils.FunctionBuilder'} 

791 

792 _defaults.update(_argspec_defaults) 

793 

794 _compile_count = itertools.count() 

795 

796 def __init__(self, name, **kw): 

797 self.name = name 

798 for a, default_factory in self._defaults.items(): 

799 val = kw.pop(a, None) 

800 if val is None: 

801 val = default_factory() 

802 setattr(self, a, val) 

803 

804 if kw: 

805 raise TypeError('unexpected kwargs: %r' % kw.keys()) 

806 return 

807 

808 # def get_argspec(self): # TODO 

809 

810 if _IS_PY2: 

811 def get_sig_str(self, with_annotations=True): 

812 """Return function signature as a string. 

813 

814 with_annotations is ignored on Python 2. On Python 3 signature 

815 will omit annotations if it is set to False. 

816 """ 

817 return inspect_formatargspec(self.args, self.varargs, 

818 self.varkw, []) 

819 

820 def get_invocation_str(self): 

821 return inspect_formatargspec(self.args, self.varargs, 

822 self.varkw, [])[1:-1] 

823 else: 

824 def get_sig_str(self, with_annotations=True): 

825 """Return function signature as a string. 

826 

827 with_annotations is ignored on Python 2. On Python 3 signature 

828 will omit annotations if it is set to False. 

829 """ 

830 if with_annotations: 

831 annotations = self.annotations 

832 else: 

833 annotations = {} 

834 

835 return inspect_formatargspec(self.args, 

836 self.varargs, 

837 self.varkw, 

838 [], 

839 self.kwonlyargs, 

840 {}, 

841 annotations) 

842 

843 _KWONLY_MARKER = re.compile(r""" 

844 \* # a star 

845 \s* # followed by any amount of whitespace 

846 , # followed by a comma 

847 \s* # followed by any amount of whitespace 

848 """, re.VERBOSE) 

849 

850 def get_invocation_str(self): 

851 kwonly_pairs = None 

852 formatters = {} 

853 if self.kwonlyargs: 

854 kwonly_pairs = dict((arg, arg) 

855 for arg in self.kwonlyargs) 

856 formatters['formatvalue'] = lambda value: '=' + value 

857 

858 sig = inspect_formatargspec(self.args, 

859 self.varargs, 

860 self.varkw, 

861 [], 

862 kwonly_pairs, 

863 kwonly_pairs, 

864 {}, 

865 **formatters) 

866 sig = self._KWONLY_MARKER.sub('', sig) 

867 return sig[1:-1] 

868 

869 @classmethod 

870 def from_func(cls, func): 

871 """Create a new FunctionBuilder instance based on an existing 

872 function. The original function will not be stored or 

873 modified. 

874 """ 

875 # TODO: copy_body? gonna need a good signature regex. 

876 # TODO: might worry about __closure__? 

877 if not callable(func): 

878 raise TypeError('expected callable object, not %r' % (func,)) 

879 

880 if isinstance(func, functools.partial): 

881 if _IS_PY2: 

882 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.') 

883 kwargs = {'name': func.func.__name__, 

884 'doc': func.func.__doc__, 

885 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor 

886 'annotations': getattr(func.func, "__annotations__", {}), 

887 'dict': getattr(func.func, '__dict__', {})} 

888 else: 

889 kwargs = {'name': func.__name__, 

890 'doc': func.__doc__, 

891 'module': getattr(func, '__module__', None), # e.g., method_descriptor 

892 'annotations': getattr(func, "__annotations__", {}), 

893 'dict': getattr(func, '__dict__', {})} 

894 

895 kwargs.update(cls._argspec_to_dict(func)) 

896 

897 if _inspect_iscoroutinefunction(func): 

898 kwargs['is_async'] = True 

899 

900 return cls(**kwargs) 

901 

902 def get_func(self, execdict=None, add_source=True, with_dict=True): 

903 """Compile and return a new function based on the current values of 

904 the FunctionBuilder. 

905 

906 Args: 

907 execdict (dict): The dictionary representing the scope in 

908 which the compilation should take place. Defaults to an empty 

909 dict. 

910 add_source (bool): Whether to add the source used to a 

911 special ``__source__`` attribute on the resulting 

912 function. Defaults to True. 

913 with_dict (bool): Add any custom attributes, if 

914 applicable. Defaults to True. 

915 

916 To see an example of usage, see the implementation of 

917 :func:`~boltons.funcutils.wraps`. 

918 """ 

919 execdict = execdict or {} 

920 body = self.body or self._default_body 

921 

922 tmpl = 'def {name}{sig_str}:' 

923 tmpl += '\n{body}' 

924 

925 if self.is_async: 

926 tmpl = 'async ' + tmpl 

927 

928 body = _indent(self.body, ' ' * self.indent) 

929 

930 name = self.name.replace('<', '_').replace('>', '_') # lambdas 

931 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False), 

932 doc=self.doc, body=body) 

933 self._compile(src, execdict) 

934 func = execdict[name] 

935 

936 func.__name__ = self.name 

937 func.__doc__ = self.doc 

938 func.__defaults__ = self.defaults 

939 if not _IS_PY2: 

940 func.__kwdefaults__ = self.kwonlydefaults 

941 func.__annotations__ = self.annotations 

942 

943 if with_dict: 

944 func.__dict__.update(self.dict) 

945 func.__module__ = self.module 

946 # TODO: caller module fallback? 

947 

948 if add_source: 

949 func.__source__ = src 

950 

951 return func 

952 

953 def get_defaults_dict(self): 

954 """Get a dictionary of function arguments with defaults and the 

955 respective values. 

956 """ 

957 ret = dict(reversed(list(zip(reversed(self.args), 

958 reversed(self.defaults or []))))) 

959 kwonlydefaults = getattr(self, 'kwonlydefaults', None) 

960 if kwonlydefaults: 

961 ret.update(kwonlydefaults) 

962 return ret 

963 

964 def get_arg_names(self, only_required=False): 

965 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ())) 

966 if only_required: 

967 defaults_dict = self.get_defaults_dict() 

968 arg_names = tuple([an for an in arg_names if an not in defaults_dict]) 

969 return arg_names 

970 

971 if _IS_PY2: 

972 def add_arg(self, arg_name, default=NO_DEFAULT): 

973 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)." 

974 if arg_name in self.args: 

975 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) 

976 self.args.append(arg_name) 

977 if default is not NO_DEFAULT: 

978 self.defaults = (self.defaults or ()) + (default,) 

979 return 

980 else: 

981 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False): 

982 """Add an argument with optional *default* (defaults to 

983 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a 

984 keyword-only argument 

985 """ 

986 if arg_name in self.args: 

987 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) 

988 if arg_name in self.kwonlyargs: 

989 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name)) 

990 if not kwonly: 

991 self.args.append(arg_name) 

992 if default is not NO_DEFAULT: 

993 self.defaults = (self.defaults or ()) + (default,) 

994 else: 

995 self.kwonlyargs.append(arg_name) 

996 if default is not NO_DEFAULT: 

997 self.kwonlydefaults[arg_name] = default 

998 return 

999 

1000 def remove_arg(self, arg_name): 

1001 """Remove an argument from this FunctionBuilder's argument list. The 

1002 resulting function will have one less argument per call to 

1003 this function. 

1004 

1005 Args: 

1006 arg_name (str): The name of the argument to remove. 

1007 

1008 Raises a :exc:`ValueError` if the argument is not present. 

1009 

1010 """ 

1011 args = self.args 

1012 d_dict = self.get_defaults_dict() 

1013 try: 

1014 args.remove(arg_name) 

1015 except ValueError: 

1016 try: 

1017 self.kwonlyargs.remove(arg_name) 

1018 except (AttributeError, ValueError): 

1019 # py2, or py3 and missing from both 

1020 exc = MissingArgument('arg %r not found in %s argument list:' 

1021 ' %r' % (arg_name, self.name, args)) 

1022 exc.arg_name = arg_name 

1023 raise exc 

1024 else: 

1025 self.kwonlydefaults.pop(arg_name, None) 

1026 else: 

1027 d_dict.pop(arg_name, None) 

1028 self.defaults = tuple([d_dict[a] for a in args if a in d_dict]) 

1029 return 

1030 

1031 def _compile(self, src, execdict): 

1032 

1033 filename = ('<%s-%d>' 

1034 % (self.filename, next(self._compile_count),)) 

1035 try: 

1036 code = compile(src, filename, 'single') 

1037 exec(code, execdict) 

1038 except Exception: 

1039 raise 

1040 return execdict 

1041 

1042 

1043class MissingArgument(ValueError): 

1044 pass 

1045 

1046 

1047class ExistingArgument(ValueError): 

1048 pass 

1049 

1050 

1051def _indent(text, margin, newline='\n', key=bool): 

1052 "based on boltons.strutils.indent" 

1053 indented_lines = [(margin + line if key(line) else line) 

1054 for line in text.splitlines()] 

1055 return newline.join(indented_lines) 

1056 

1057 

1058try: 

1059 from functools import total_ordering # 2.7+ 

1060except ImportError: 

1061 # python 2.6 

1062 def total_ordering(cls): 

1063 """Class decorator that fills in missing comparators/ordering 

1064 methods. Backport of :func:`functools.total_ordering` to work 

1065 with Python 2.6. 

1066 

1067 Code from http://code.activestate.com/recipes/576685/ 

1068 """ 

1069 convert = { 

1070 '__lt__': [ 

1071 ('__gt__', 

1072 lambda self, other: not (self < other or self == other)), 

1073 ('__le__', 

1074 lambda self, other: self < other or self == other), 

1075 ('__ge__', 

1076 lambda self, other: not self < other)], 

1077 '__le__': [ 

1078 ('__ge__', 

1079 lambda self, other: not self <= other or self == other), 

1080 ('__lt__', 

1081 lambda self, other: self <= other and not self == other), 

1082 ('__gt__', 

1083 lambda self, other: not self <= other)], 

1084 '__gt__': [ 

1085 ('__lt__', 

1086 lambda self, other: not (self > other or self == other)), 

1087 ('__ge__', 

1088 lambda self, other: self > other or self == other), 

1089 ('__le__', 

1090 lambda self, other: not self > other)], 

1091 '__ge__': [ 

1092 ('__le__', 

1093 lambda self, other: (not self >= other) or self == other), 

1094 ('__gt__', 

1095 lambda self, other: self >= other and not self == other), 

1096 ('__lt__', 

1097 lambda self, other: not self >= other)] 

1098 } 

1099 roots = set(dir(cls)) & set(convert) 

1100 if not roots: 

1101 raise ValueError('must define at least one ordering operation:' 

1102 ' < > <= >=') 

1103 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__ 

1104 for opname, opfunc in convert[root]: 

1105 if opname not in roots: 

1106 opfunc.__name__ = opname 

1107 opfunc.__doc__ = getattr(int, opname).__doc__ 

1108 setattr(cls, opname, opfunc) 

1109 return cls 

1110 

1111def noop(*args, **kwargs): 

1112 """ 

1113 Simple function that should be used when no effect is desired. 

1114 An alternative to checking for an optional function type parameter. 

1115 

1116 e.g. 

1117 def decorate(func, pre_func=None, post_func=None): 

1118 if pre_func: 

1119 pre_func() 

1120 func() 

1121 if post_func: 

1122 post_func() 

1123 

1124 vs 

1125 

1126 def decorate(func, pre_func=noop, post_func=noop): 

1127 pre_func() 

1128 func() 

1129 post_func() 

1130 """ 

1131 return None 

1132 

1133# end funcutils.py