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

417 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:13 +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 copy_dict: 

219 ret.__dict__.update(orig.__dict__) 

220 return ret 

221 

222 

223def partial_ordering(cls): 

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

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

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

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

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

229 additional comparison methods. 

230 

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

232 

233 >>> @partial_ordering 

234 ... class MySet(set): 

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

236 ... return self.issubset(other) 

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

238 ... return self.issuperset(other) 

239 ... 

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

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

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

243 >>> b < a 

244 True 

245 >>> b > a 

246 False 

247 >>> b < c 

248 True 

249 >>> a < c 

250 False 

251 >>> c > a 

252 False 

253 """ 

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

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

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

257 

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

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

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

261 

262 return cls 

263 

264 

265class InstancePartial(functools.partial): 

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

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

268 developers to curry arguments and incrementally create simpler 

269 callables for a variety of use cases. 

270 

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

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

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

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

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

276 descriptor protocol. There are no other differences in 

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

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

279 

280 """ 

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

282 @property 

283 def _partialmethod(self): 

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

285 

286 def __get__(self, obj, obj_type): 

287 return make_method(self, obj, obj_type) 

288 

289 

290 

291class CachedInstancePartial(functools.partial): 

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

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

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

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

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

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

298 

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

300 """ 

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

302 @property 

303 def _partialmethod(self): 

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

305 

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

307 def __set_name__(self, obj_type, name): 

308 self.__name__ = name 

309 

310 def __get__(self, obj, obj_type): 

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

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

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

314 self.__doc__ = self.func.__doc__ 

315 self.__module__ = self.func.__module__ 

316 

317 name = self.__name__ 

318 

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

320 if name is None: 

321 for k, v in mro_items(obj_type): 

322 if v is self: 

323 self.__name__ = name = k 

324 if obj is None: 

325 return make_method(self, obj, obj_type) 

326 try: 

327 # since this is a data descriptor, this block 

328 # is probably only hit once (per object) 

329 return obj.__dict__[name] 

330 except KeyError: 

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

332 return ret 

333 

334 

335partial = CachedInstancePartial 

336 

337 

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

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

340 a basic Python-style function call. 

341 

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

343 func(1, 2, c=3) 

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

345 a_func(1) 

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

347 kw_func(a=1, b=2) 

348 

349 """ 

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

351 if kw: 

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

353 kwargs = kwargs or {} 

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

355 if isinstance(kwargs, dict): 

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

357 else: 

358 kwarg_items = kwargs 

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

360 

361 all_args_text = a_text 

362 if all_args_text and kw_text: 

363 all_args_text += ', ' 

364 all_args_text += kw_text 

365 

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

367 

368 

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

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

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

372 

373 >>> class Flag(object): 

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

375 ... self.length = length 

376 ... self.width = width 

377 ... self.depth = depth 

378 ... 

379 

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

381 

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

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

384 Flag(5, 10) 

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

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

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

388 

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

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

391 

392 Args: 

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

394 attributes will be checked 

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

396 rendered as positional arguments in the output repr. 

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

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

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

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

401 the *opt_key* check. Defaults to None. 

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

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

404 ``None``-check. 

405 

406 """ 

407 cn = type(obj).__name__ 

408 req_names = req_names or [] 

409 opt_names = opt_names or [] 

410 uniq_names, all_names = set(), [] 

411 for name in req_names + opt_names: 

412 if name in uniq_names: 

413 continue 

414 uniq_names.add(name) 

415 all_names.append(name) 

416 

417 if opt_key is None: 

418 opt_key = lambda v: v is None 

419 assert callable(opt_key) 

420 

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

422 

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

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

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

426 

427 return format_invocation(cn, args, kw_items) 

428 

429 

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

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

432 

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

434 

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

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

437 roundtrip, like types and functions. 

438 

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

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

441 

442 >>> class Flag(object): 

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

444 ... self.length = length 

445 ... self.width = width 

446 ... self.depth = depth 

447 ... 

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

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

450 <Flag length=5 width=10> 

451 

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

453 built-in behavior. 

454 

455 >>> print(format_nonexp_repr(flag)) 

456 <Flag id=...> 

457 """ 

458 cn = obj.__class__.__name__ 

459 req_names = req_names or [] 

460 opt_names = opt_names or [] 

461 uniq_names, all_names = set(), [] 

462 for name in req_names + opt_names: 

463 if name in uniq_names: 

464 continue 

465 uniq_names.add(name) 

466 all_names.append(name) 

467 

468 if opt_key is None: 

469 opt_key = lambda v: v is None 

470 assert callable(opt_key) 

471 

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

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

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

475 if not labels: 

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

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

478 return ret 

479 

480 

481 

482# # # 

483# # # Function builder 

484# # # 

485 

486 

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

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

489 

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

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

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

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

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

495 

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

497 

498 >>> from boltons.funcutils import wraps 

499 >>> 

500 >>> def print_return(func): 

501 ... @wraps(func) 

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

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

504 ... print(ret) 

505 ... return ret 

506 ... return wrapper 

507 ... 

508 >>> @print_return 

509 ... def example(): 

510 ... '''docstring''' 

511 ... return 'example return value' 

512 >>> 

513 >>> val = example() 

514 example return value 

515 >>> example.__name__ 

516 'example' 

517 >>> example.__doc__ 

518 'docstring' 

519 """ 

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

521 injected=injected, expected=expected, **kw) 

522 

523 

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

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

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

527 wrapped function's: 

528 

529 * Name 

530 * Documentation 

531 * Module 

532 * Signature 

533 

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

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

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

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

538 built-in version:: 

539 

540 >>> from boltons.funcutils import update_wrapper 

541 >>> 

542 >>> def print_return(func): 

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

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

545 ... print(ret) 

546 ... return ret 

547 ... return update_wrapper(wrapper, func) 

548 ... 

549 >>> @print_return 

550 ... def example(): 

551 ... '''docstring''' 

552 ... return 'example return value' 

553 >>> 

554 >>> val = example() 

555 example return value 

556 >>> example.__name__ 

557 'example' 

558 >>> example.__doc__ 

559 'docstring' 

560 

561 In addition, the boltons version of update_wrapper supports 

562 modifying the outer signature. By passing a list of 

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

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

565 arguments that aren't passed in. 

566 

567 Args: 

568 

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

570 *func* are to be copied. 

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

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

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

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

575 default) pairs) representing new arguments introduced by 

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

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

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

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

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

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

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

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

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

585 inject_to_varkw (bool): Ignore missing arguments when a 

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

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

588 in the updated function. 

589 

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

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

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

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

594 """ 

595 if injected is None: 

596 injected = [] 

597 elif isinstance(injected, basestring): 

598 injected = [injected] 

599 else: 

600 injected = list(injected) 

601 

602 expected_items = _parse_wraps_expected(expected) 

603 

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

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

606 ' staticmethods, change the order of wrapping to' 

607 ' wrap the underlying function: %r' 

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

609 

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

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

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

613 if kw: 

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

615 

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

617 build_from = build_from or wrapper 

618 

619 fb = FunctionBuilder.from_func(build_from or func) 

620 

621 for arg in injected: 

622 try: 

623 fb.remove_arg(arg) 

624 except MissingArgument: 

625 if inject_to_varkw and fb.varkw is not None: 

626 continue # keyword arg will be caught by the varkw 

627 raise 

628 

629 for arg, default in expected_items: 

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

631 

632 if fb.is_async: 

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

634 else: 

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

636 

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

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

639 

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

641 del fully_wrapped.__dict__['__wrapped__'] 

642 elif not hide_wrapped: 

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

644 

645 return fully_wrapped 

646 

647 

648def _parse_wraps_expected(expected): 

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

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

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

652 # you look 

653 if expected is None: 

654 expected = [] 

655 elif isinstance(expected, basestring): 

656 expected = [(expected, NO_DEFAULT)] 

657 

658 expected_items = [] 

659 try: 

660 expected_iter = iter(expected) 

661 except TypeError as e: 

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

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

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

665 for argname in expected_iter: 

666 if isinstance(argname, basestring): 

667 # dict keys and bare strings 

668 try: 

669 default = expected[argname] 

670 except TypeError: 

671 default = NO_DEFAULT 

672 else: 

673 # pairs 

674 try: 

675 argname, default = argname 

676 except (TypeError, ValueError): 

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

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

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

680 if not isinstance(argname, basestring): 

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

682 

683 expected_items.append((argname, default)) 

684 

685 return expected_items 

686 

687 

688class FunctionBuilder(object): 

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

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

691 scratch. 

692 

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

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

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

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

697 newly compiled function, based on the values configured. 

698 

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

700 ... body='return 5') 

701 >>> f = fb.get_func() 

702 >>> f() 

703 5 

704 >>> fb.varkw = 'kw' 

705 >>> f_kw = fb.get_func() 

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

707 5 

708 

709 Note that function signatures themselves changed quite a bit in 

710 Python 3, so several arguments are only applicable to 

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

712 the constructor are keyword arguments. 

713 

714 Args: 

715 name (str): Name of the function. 

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

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

718 imported. Defaults to None. 

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

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

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

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

723 denoting no arguments. 

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

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

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

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

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

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

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

731 those arguments that have defaults. 

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

733 keyword arguments. **Python 3 only.** 

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

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

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

737 forth. **Python 3 only.** 

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

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

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

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

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

743 functions compiled with this FunctionBuilder. 

744 

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

746 can be mutated as necessary. 

747 

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

749 

750 """ 

751 

752 if _IS_PY2: 

753 _argspec_defaults = {'args': list, 

754 'varargs': lambda: None, 

755 'varkw': lambda: None, 

756 'defaults': lambda: None} 

757 

758 @classmethod 

759 def _argspec_to_dict(cls, f): 

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

761 return {'args': args, 

762 'varargs': varargs, 

763 'varkw': varkw, 

764 'defaults': defaults} 

765 

766 else: 

767 _argspec_defaults = {'args': list, 

768 'varargs': lambda: None, 

769 'varkw': lambda: None, 

770 'defaults': lambda: None, 

771 'kwonlyargs': list, 

772 'kwonlydefaults': dict, 

773 'annotations': dict} 

774 

775 @classmethod 

776 def _argspec_to_dict(cls, f): 

777 argspec = inspect.getfullargspec(f) 

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

779 for attr in cls._argspec_defaults) 

780 

781 _defaults = {'doc': str, 

782 'dict': dict, 

783 'is_async': lambda: False, 

784 'module': lambda: None, 

785 'body': lambda: 'pass', 

786 'indent': lambda: 4, 

787 "annotations": dict, 

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

789 

790 _defaults.update(_argspec_defaults) 

791 

792 _compile_count = itertools.count() 

793 

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

795 self.name = name 

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

797 val = kw.pop(a, None) 

798 if val is None: 

799 val = default_factory() 

800 setattr(self, a, val) 

801 

802 if kw: 

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

804 return 

805 

806 # def get_argspec(self): # TODO 

807 

808 if _IS_PY2: 

809 def get_sig_str(self, with_annotations=True): 

810 """Return function signature as a string. 

811 

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

813 will omit annotations if it is set to False. 

814 """ 

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

816 self.varkw, []) 

817 

818 def get_invocation_str(self): 

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

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

821 else: 

822 def get_sig_str(self, with_annotations=True): 

823 """Return function signature as a string. 

824 

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

826 will omit annotations if it is set to False. 

827 """ 

828 if with_annotations: 

829 annotations = self.annotations 

830 else: 

831 annotations = {} 

832 

833 return inspect_formatargspec(self.args, 

834 self.varargs, 

835 self.varkw, 

836 [], 

837 self.kwonlyargs, 

838 {}, 

839 annotations) 

840 

841 _KWONLY_MARKER = re.compile(r""" 

842 \* # a star 

843 \s* # followed by any amount of whitespace 

844 , # followed by a comma 

845 \s* # followed by any amount of whitespace 

846 """, re.VERBOSE) 

847 

848 def get_invocation_str(self): 

849 kwonly_pairs = None 

850 formatters = {} 

851 if self.kwonlyargs: 

852 kwonly_pairs = dict((arg, arg) 

853 for arg in self.kwonlyargs) 

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

855 

856 sig = inspect_formatargspec(self.args, 

857 self.varargs, 

858 self.varkw, 

859 [], 

860 kwonly_pairs, 

861 kwonly_pairs, 

862 {}, 

863 **formatters) 

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

865 return sig[1:-1] 

866 

867 @classmethod 

868 def from_func(cls, func): 

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

870 function. The original function will not be stored or 

871 modified. 

872 """ 

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

874 # TODO: might worry about __closure__? 

875 if not callable(func): 

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

877 

878 if isinstance(func, functools.partial): 

879 if _IS_PY2: 

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

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

882 'doc': func.func.__doc__, 

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

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

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

886 else: 

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

888 'doc': func.__doc__, 

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

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

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

892 

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

894 

895 if _inspect_iscoroutinefunction(func): 

896 kwargs['is_async'] = True 

897 

898 return cls(**kwargs) 

899 

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

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

902 the FunctionBuilder. 

903 

904 Args: 

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

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

907 dict. 

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

909 special ``__source__`` attribute on the resulting 

910 function. Defaults to True. 

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

912 applicable. Defaults to True. 

913 

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

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

916 """ 

917 execdict = execdict or {} 

918 body = self.body or self._default_body 

919 

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

921 tmpl += '\n{body}' 

922 

923 if self.is_async: 

924 tmpl = 'async ' + tmpl 

925 

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

927 

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

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

930 doc=self.doc, body=body) 

931 self._compile(src, execdict) 

932 func = execdict[name] 

933 

934 func.__name__ = self.name 

935 func.__doc__ = self.doc 

936 func.__defaults__ = self.defaults 

937 if not _IS_PY2: 

938 func.__kwdefaults__ = self.kwonlydefaults 

939 func.__annotations__ = self.annotations 

940 

941 if with_dict: 

942 func.__dict__.update(self.dict) 

943 func.__module__ = self.module 

944 # TODO: caller module fallback? 

945 

946 if add_source: 

947 func.__source__ = src 

948 

949 return func 

950 

951 def get_defaults_dict(self): 

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

953 respective values. 

954 """ 

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

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

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

958 if kwonlydefaults: 

959 ret.update(kwonlydefaults) 

960 return ret 

961 

962 def get_arg_names(self, only_required=False): 

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

964 if only_required: 

965 defaults_dict = self.get_defaults_dict() 

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

967 return arg_names 

968 

969 if _IS_PY2: 

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

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

972 if arg_name in self.args: 

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

974 self.args.append(arg_name) 

975 if default is not NO_DEFAULT: 

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

977 return 

978 else: 

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

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

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

982 keyword-only argument 

983 """ 

984 if arg_name in self.args: 

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

986 if arg_name in self.kwonlyargs: 

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

988 if not kwonly: 

989 self.args.append(arg_name) 

990 if default is not NO_DEFAULT: 

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

992 else: 

993 self.kwonlyargs.append(arg_name) 

994 if default is not NO_DEFAULT: 

995 self.kwonlydefaults[arg_name] = default 

996 return 

997 

998 def remove_arg(self, arg_name): 

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

1000 resulting function will have one less argument per call to 

1001 this function. 

1002 

1003 Args: 

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

1005 

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

1007 

1008 """ 

1009 args = self.args 

1010 d_dict = self.get_defaults_dict() 

1011 try: 

1012 args.remove(arg_name) 

1013 except ValueError: 

1014 try: 

1015 self.kwonlyargs.remove(arg_name) 

1016 except (AttributeError, ValueError): 

1017 # py2, or py3 and missing from both 

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

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

1020 exc.arg_name = arg_name 

1021 raise exc 

1022 else: 

1023 self.kwonlydefaults.pop(arg_name, None) 

1024 else: 

1025 d_dict.pop(arg_name, None) 

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

1027 return 

1028 

1029 def _compile(self, src, execdict): 

1030 

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

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

1033 try: 

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

1035 exec(code, execdict) 

1036 except Exception: 

1037 raise 

1038 return execdict 

1039 

1040 

1041class MissingArgument(ValueError): 

1042 pass 

1043 

1044 

1045class ExistingArgument(ValueError): 

1046 pass 

1047 

1048 

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

1050 "based on boltons.strutils.indent" 

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

1052 for line in text.splitlines()] 

1053 return newline.join(indented_lines) 

1054 

1055 

1056try: 

1057 from functools import total_ordering # 2.7+ 

1058except ImportError: 

1059 # python 2.6 

1060 def total_ordering(cls): 

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

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

1063 with Python 2.6. 

1064 

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

1066 """ 

1067 convert = { 

1068 '__lt__': [ 

1069 ('__gt__', 

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

1071 ('__le__', 

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

1073 ('__ge__', 

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

1075 '__le__': [ 

1076 ('__ge__', 

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

1078 ('__lt__', 

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

1080 ('__gt__', 

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

1082 '__gt__': [ 

1083 ('__lt__', 

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

1085 ('__ge__', 

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

1087 ('__le__', 

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

1089 '__ge__': [ 

1090 ('__le__', 

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

1092 ('__gt__', 

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

1094 ('__lt__', 

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

1096 } 

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

1098 if not roots: 

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

1100 ' < > <= >=') 

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

1102 for opname, opfunc in convert[root]: 

1103 if opname not in roots: 

1104 opfunc.__name__ = opname 

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

1106 setattr(cls, opname, opfunc) 

1107 return cls 

1108 

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

1110 """ 

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

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

1113 

1114 e.g. 

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

1116 if pre_func: 

1117 pre_func() 

1118 func() 

1119 if post_func: 

1120 post_func() 

1121 

1122 vs 

1123 

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

1125 pre_func() 

1126 func() 

1127 post_func() 

1128 """ 

1129 return None 

1130 

1131# end funcutils.py