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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

395 statements  

1# Copyright (c) 2013, Mahmoud Hashemi 

2# 

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

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

5# met: 

6# 

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

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

9# 

10# * Redistributions in binary form must reproduce the above 

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

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

13# with the distribution. 

14# 

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

16# promote products derived from this software without specific 

17# prior written permission. 

18# 

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

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

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

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

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

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

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

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

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

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

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

30 

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

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

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

34correcting Python's standard metaprogramming facilities. 

35""" 

36 

37import sys 

38import inspect 

39import functools 

40import itertools 

41import threading 

42from inspect import formatannotation 

43from types import FunctionType, MethodType 

44 

45# For legacy compatibility. 

46# boltons used to offer an implementation of total_ordering for Python <2.7 

47from functools import total_ordering as total_ordering 

48 

49try: 

50 from .typeutils import make_sentinel 

51 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT') 

52except ImportError: 

53 NO_DEFAULT = object() 

54 

55 

56def inspect_formatargspec( 

57 args, varargs=None, varkw=None, defaults=None, 

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

59 formatarg=str, 

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

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

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

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

64 formatannotation=formatannotation): 

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

66 Python 3 has deprecated formatargspec and requested that Signature 

67 be used instead, however this requires a full reimplementation 

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

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

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

71 """ 

72 

73 def formatargandannotation(arg): 

74 result = formatarg(arg) 

75 if arg in annotations: 

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

77 return result 

78 specs = [] 

79 if defaults: 

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

81 for i, arg in enumerate(args): 

82 spec = formatargandannotation(arg) 

83 if defaults and i >= firstdefault: 

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

85 specs.append(spec) 

86 if varargs is not None: 

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

88 else: 

89 if kwonlyargs: 

90 specs.append('*') 

91 if kwonlyargs: 

92 for kwonlyarg in kwonlyargs: 

93 spec = formatargandannotation(kwonlyarg) 

94 if kwonlydefaults and kwonlyarg in kwonlydefaults: 

95 spec += formatvalue(kwonlydefaults[kwonlyarg]) 

96 specs.append(spec) 

97 if varkw is not None: 

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

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

100 if 'return' in annotations: 

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

102 return result 

103 

104 

105def get_module_callables(mod, ignore=None): 

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

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

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

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

110 """ 

111 if isinstance(mod, str): 

112 mod = sys.modules[mod] 

113 types, funcs = {}, {} 

114 for attr_name in dir(mod): 

115 if ignore and ignore(attr_name): 

116 continue 

117 try: 

118 attr = getattr(mod, attr_name) 

119 except Exception: 

120 continue 

121 try: 

122 attr_mod_name = attr.__module__ 

123 except AttributeError: 

124 continue 

125 if attr_mod_name != mod.__name__: 

126 continue 

127 if isinstance(attr, type): 

128 types[attr_name] = attr 

129 elif callable(attr): 

130 funcs[attr_name] = attr 

131 return types, funcs 

132 

133 

134def mro_items(type_obj): 

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

136 throughout the type hierarchy (respecting the MRO). 

137 

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

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

140 """ 

141 # TODO: handle slots? 

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

143 for ct in type_obj.__mro__) 

144 

145 

146def dir_dict(obj, raise_exc=False): 

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

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

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

150 """ 

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

152 ret = {} 

153 for k in dir(obj): 

154 try: 

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

156 except Exception: 

157 if raise_exc: 

158 raise 

159 return ret 

160 

161 

162def copy_function(orig, copy_dict=True): 

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

164 globals, closure, etc. 

165 

166 >>> func = lambda: func 

167 >>> func() is func 

168 True 

169 >>> func_copy = copy_function(func) 

170 >>> func_copy() is func 

171 True 

172 >>> func_copy is not func 

173 True 

174 

175 Args: 

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

177 function, not just any method or callable. 

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

179 instance. Defaults to ``True``. 

180 """ 

181 ret = FunctionType(orig.__code__, 

182 orig.__globals__, 

183 name=orig.__name__, 

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

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

186 if hasattr(orig, "__kwdefaults__"): 

187 ret.__kwdefaults__ = orig.__kwdefaults__ 

188 if copy_dict: 

189 ret.__dict__.update(orig.__dict__) 

190 return ret 

191 

192 

193def partial_ordering(cls): 

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

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

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

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

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

199 additional comparison methods. 

200 

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

202 

203 >>> @partial_ordering 

204 ... class MySet(set): 

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

206 ... return self.issubset(other) 

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

208 ... return self.issuperset(other) 

209 ... 

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

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

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

213 >>> b < a 

214 True 

215 >>> b > a 

216 False 

217 >>> b < c 

218 True 

219 >>> a < c 

220 False 

221 >>> c > a 

222 False 

223 """ 

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

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

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

227 

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

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

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

231 

232 return cls 

233 

234 

235class InstancePartial(functools.partial): 

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

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

238 developers to curry arguments and incrementally create simpler 

239 callables for a variety of use cases. 

240 

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

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

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

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

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

246 descriptor protocol. There are no other differences in 

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

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

249 

250 """ 

251 @property 

252 def _partialmethod(self): 

253 # py3.13 switched from _partialmethod to __partialmethod__, this is kept for backwards compat <=py3.12 

254 return self.__partialmethod__ 

255 

256 @property 

257 def __partialmethod__(self): 

258 return functools.partialmethod(self.func, *self.args, **self.keywords) 

259 

260 def __get__(self, obj, obj_type): 

261 return MethodType(self, obj) 

262 

263 

264 

265class CachedInstancePartial(functools.partial): 

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

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

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

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

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

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

272 

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

274 """ 

275 @property 

276 def _partialmethod(self): 

277 # py3.13 switched from _partialmethod to __partialmethod__, this is kept for backwards compat <=py3.12 

278 return self.__partialmethod__ 

279 

280 @property 

281 def __partialmethod__(self): 

282 return functools.partialmethod(self.func, *self.args, **self.keywords) 

283 

284 def __set_name__(self, obj_type, name): 

285 self.__name__ = name 

286 

287 def __get__(self, obj, obj_type): 

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

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

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

291 self.__doc__ = self.func.__doc__ 

292 self.__module__ = self.func.__module__ 

293 

294 name = self.__name__ 

295 

296 if obj is None: 

297 return MethodType(self, obj) 

298 try: 

299 # since this is a data descriptor, this block 

300 # is probably only hit once (per object) 

301 return obj.__dict__[name] 

302 except KeyError: 

303 obj.__dict__[name] = ret = MethodType(self, obj) 

304 return ret 

305 

306 

307partial = CachedInstancePartial 

308 

309 

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

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

312 a basic Python-style function call. 

313 

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

315 func(1, 2, c=3) 

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

317 a_func(1) 

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

319 kw_func(a=1, b=2) 

320 

321 """ 

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

323 if kw: 

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

325 kwargs = kwargs or {} 

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

327 if isinstance(kwargs, dict): 

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

329 else: 

330 kwarg_items = kwargs 

331 kw_text = ', '.join([f'{k}={_repr(v)}' for k, v in kwarg_items]) 

332 

333 all_args_text = a_text 

334 if all_args_text and kw_text: 

335 all_args_text += ', ' 

336 all_args_text += kw_text 

337 

338 return f'{name}({all_args_text})' 

339 

340 

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

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

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

344 

345 >>> class Flag(object): 

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

347 ... self.length = length 

348 ... self.width = width 

349 ... self.depth = depth 

350 ... 

351 

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

353 

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

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

356 Flag(5, 10) 

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

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

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

360 

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

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

363 

364 Args: 

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

366 attributes will be checked 

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

368 rendered as positional arguments in the output repr. 

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

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

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

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

373 the *opt_key* check. Defaults to None. 

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

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

376 ``None``-check. 

377 

378 """ 

379 cn = type(obj).__name__ 

380 req_names = req_names or [] 

381 opt_names = opt_names or [] 

382 uniq_names, all_names = set(), [] 

383 for name in req_names + opt_names: 

384 if name in uniq_names: 

385 continue 

386 uniq_names.add(name) 

387 all_names.append(name) 

388 

389 if opt_key is None: 

390 opt_key = lambda v: v is None 

391 assert callable(opt_key) 

392 

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

394 

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

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

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

398 

399 return format_invocation(cn, args, kw_items) 

400 

401 

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

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

404 

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

406 

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

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

409 roundtrip, like types and functions. 

410 

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

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

413 

414 >>> class Flag(object): 

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

416 ... self.length = length 

417 ... self.width = width 

418 ... self.depth = depth 

419 ... 

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

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

422 <Flag length=5 width=10> 

423 

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

425 built-in behavior. 

426 

427 >>> print(format_nonexp_repr(flag)) 

428 <Flag id=...> 

429 """ 

430 cn = obj.__class__.__name__ 

431 req_names = req_names or [] 

432 opt_names = opt_names or [] 

433 uniq_names, all_names = set(), [] 

434 for name in req_names + opt_names: 

435 if name in uniq_names: 

436 continue 

437 uniq_names.add(name) 

438 all_names.append(name) 

439 

440 if opt_key is None: 

441 opt_key = lambda v: v is None 

442 assert callable(opt_key) 

443 

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

445 labels = [f'{name}={val!r}' for name, val in items 

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

447 if not labels: 

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

449 ret = '<{} {}>'.format(cn, ' '.join(labels)) 

450 return ret 

451 

452 

453 

454# # # 

455# # # Function builder 

456# # # 

457 

458 

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

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

461 

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

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

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

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

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

467 

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

469 

470 >>> from boltons.funcutils import wraps 

471 >>> 

472 >>> def print_return(func): 

473 ... @wraps(func) 

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

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

476 ... print(ret) 

477 ... return ret 

478 ... return wrapper 

479 ... 

480 >>> @print_return 

481 ... def example(): 

482 ... '''docstring''' 

483 ... return 'example return value' 

484 >>> 

485 >>> val = example() 

486 example return value 

487 >>> example.__name__ 

488 'example' 

489 >>> example.__doc__ 

490 'docstring' 

491 """ 

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

493 injected=injected, expected=expected, **kw) 

494 

495 

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

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

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

499 wrapped function's: 

500 

501 * Name 

502 * Documentation 

503 * Module 

504 * Signature 

505 

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

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

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

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

510 built-in version:: 

511 

512 >>> from boltons.funcutils import update_wrapper 

513 >>> 

514 >>> def print_return(func): 

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

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

517 ... print(ret) 

518 ... return ret 

519 ... return update_wrapper(wrapper, func) 

520 ... 

521 >>> @print_return 

522 ... def example(): 

523 ... '''docstring''' 

524 ... return 'example return value' 

525 >>> 

526 >>> val = example() 

527 example return value 

528 >>> example.__name__ 

529 'example' 

530 >>> example.__doc__ 

531 'docstring' 

532 

533 In addition, the boltons version of update_wrapper supports 

534 modifying the outer signature. By passing a list of 

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

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

537 arguments that aren't passed in. 

538 

539 Args: 

540 

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

542 *func* are to be copied. 

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

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

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

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

547 default) pairs) representing new arguments introduced by 

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

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

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

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

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

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

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

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

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

557 inject_to_varkw (bool): Ignore missing arguments when a 

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

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

560 in the updated function. 

561 

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

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

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

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

566 """ 

567 if injected is None: 

568 injected = [] 

569 elif isinstance(injected, str): 

570 injected = [injected] 

571 else: 

572 injected = list(injected) 

573 

574 expected_items = _parse_wraps_expected(expected) 

575 

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

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

578 ' staticmethods, change the order of wrapping to' 

579 ' wrap the underlying function: %r' 

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

581 

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

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

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

585 if kw: 

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

587 

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

589 build_from = build_from or wrapper 

590 

591 fb = FunctionBuilder.from_func(build_from or func) 

592 

593 for arg in injected: 

594 try: 

595 fb.remove_arg(arg) 

596 except MissingArgument: 

597 if inject_to_varkw and fb.varkw is not None: 

598 continue # keyword arg will be caught by the varkw 

599 raise 

600 

601 for arg, default in expected_items: 

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

603 

604 invocation_str = fb.get_invocation_str(target=wrapper) 

605 if fb.is_async: 

606 fb.body = 'return await _call(%s)' % invocation_str 

607 else: 

608 fb.body = 'return _call(%s)' % invocation_str 

609 

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

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

612 

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

614 del fully_wrapped.__dict__['__wrapped__'] 

615 elif not hide_wrapped: 

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

617 

618 return fully_wrapped 

619 

620 

621def _parse_wraps_expected(expected): 

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

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

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

625 # you look 

626 if expected is None: 

627 expected = [] 

628 elif isinstance(expected, str): 

629 expected = [(expected, NO_DEFAULT)] 

630 

631 expected_items = [] 

632 try: 

633 expected_iter = iter(expected) 

634 except TypeError as e: 

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

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

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

638 for argname in expected_iter: 

639 if isinstance(argname, str): 

640 # dict keys and bare strings 

641 try: 

642 default = expected[argname] 

643 except TypeError: 

644 default = NO_DEFAULT 

645 else: 

646 # pairs 

647 try: 

648 argname, default = argname 

649 except (TypeError, ValueError): 

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

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

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

653 if not isinstance(argname, str): 

654 raise ValueError(f'all "expected" argnames must be strings, not {argname!r}') 

655 

656 expected_items.append((argname, default)) 

657 

658 return expected_items 

659 

660 

661class FunctionBuilder: 

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

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

664 scratch. 

665 

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

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

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

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

670 newly compiled function, based on the values configured. 

671 

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

673 ... body='return 5') 

674 >>> f = fb.get_func() 

675 >>> f() 

676 5 

677 >>> fb.varkw = 'kw' 

678 >>> f_kw = fb.get_func() 

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

680 5 

681 

682 Note that function signatures themselves changed quite a bit in 

683 Python 3, so several arguments are only applicable to 

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

685 the constructor are keyword arguments. 

686 

687 Args: 

688 name (str): Name of the function. 

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

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

691 imported. Defaults to None. 

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

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

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

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

696 denoting no arguments. 

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

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

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

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

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

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

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

704 those arguments that have defaults. 

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

706 keyword arguments. **Python 3 only.** 

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

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

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

710 forth. **Python 3 only.** 

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

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

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

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

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

716 functions compiled with this FunctionBuilder. 

717 

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

719 can be mutated as necessary. 

720 

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

722 

723 """ 

724 

725 _argspec_defaults = {'args': list, 

726 'varargs': lambda: None, 

727 'varkw': lambda: None, 

728 'defaults': lambda: None, 

729 'kwonlyargs': list, 

730 'kwonlydefaults': dict, 

731 'annotations': dict} 

732 

733 @classmethod 

734 def _argspec_to_dict(cls, f): 

735 argspec = inspect.getfullargspec(f) 

736 return {attr: getattr(argspec, attr) 

737 for attr in cls._argspec_defaults} 

738 

739 _defaults = {'doc': str, 

740 'dict': dict, 

741 'is_async': lambda: False, 

742 'module': lambda: None, 

743 'body': lambda: 'pass', 

744 'indent': lambda: 4, 

745 "annotations": dict, 

746 'posonlyargs': list, 

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

748 

749 _defaults.update(_argspec_defaults) 

750 

751 _compile_count = itertools.count() 

752 

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

754 self.name = name 

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

756 val = kw.pop(a, None) 

757 if val is None: 

758 val = default_factory() 

759 setattr(self, a, val) 

760 

761 if kw: 

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

763 return 

764 

765 # def get_argspec(self): # TODO 

766 

767 def get_sig_str(self, with_annotations=True): 

768 """Return function signature as a string. 

769 

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

771 will omit annotations if it is set to False. 

772 """ 

773 if with_annotations: 

774 annotations = self.annotations 

775 else: 

776 annotations = {} 

777 

778 return inspect_formatargspec(self.args, 

779 self.varargs, 

780 self.varkw, 

781 [], 

782 self.kwonlyargs, 

783 {}, 

784 annotations) 

785 

786 def get_invocation_str(self, target=None): 

787 # Regular args with defaults (and keyword-only args) are forwarded 

788 # as keywords (name=name), so values callers pass by keyword reach 

789 # the wrapper's **kwargs instead of being silently flattened into 

790 # *args (#343). Two classes must stay positional: positional-only 

791 # params (keywords are rejected outright), and every regular arg 

792 # when *varargs is present (a keyword-forwarded arg that precedes 

793 # *varargs collides with non-empty varargs). 

794 # 

795 # When *target* -- the callable the generated invocation will 

796 # actually call -- is provided, any argument that target only 

797 # accepts as a keyword is forwarded as a keyword regardless of 

798 # the rules above, since positional forwarding could never bind 

799 # it (#261). Targets without introspectable signatures fall 

800 # back to signature-based forwarding alone. 

801 target_kwonly = frozenset() 

802 if target is not None: 

803 try: 

804 target_params = inspect.signature(target).parameters 

805 except (ValueError, TypeError): 

806 pass 

807 else: 

808 target_kwonly = frozenset( 

809 p.name for p in target_params.values() 

810 if p.kind is inspect.Parameter.KEYWORD_ONLY) 

811 defaults = self.defaults or () 

812 args = list(self.args or ()) 

813 n_positional = len(args) - len(defaults) 

814 parts, kw_parts = [], [] 

815 for i, arg in enumerate(args): 

816 if arg in target_kwonly: 

817 kw_parts.append(arg + '=' + arg) 

818 elif (i >= n_positional and not self.varargs 

819 and arg not in self.posonlyargs): 

820 kw_parts.append(arg + '=' + arg) 

821 else: 

822 parts.append(arg) 

823 if self.varargs: 

824 parts.append('*' + self.varargs) 

825 parts += kw_parts 

826 parts += [arg + '=' + arg for arg in self.kwonlyargs or ()] 

827 if self.varkw: 

828 parts.append('**' + self.varkw) 

829 return ', '.join(parts) 

830 

831 @classmethod 

832 def from_func(cls, func): 

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

834 function. The original function will not be stored or 

835 modified. 

836 """ 

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

838 # TODO: might worry about __closure__? 

839 if not callable(func): 

840 raise TypeError(f'expected callable object, not {func!r}') 

841 

842 if isinstance(func, functools.partial): 

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

844 'doc': func.func.__doc__, 

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

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

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

848 else: 

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

850 'doc': func.__doc__, 

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

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

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

854 

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

856 

857 # getfullargspec merges positional-only params into args; recover 

858 # them from the code object so they are never keyword-forwarded. 

859 # (functools.partial objects have no __code__; posonly detection 

860 # is skipped for them, preserving today's behavior.) 

861 n_posonly = getattr(getattr(func, '__code__', None), 

862 'co_posonlyargcount', 0) 

863 if n_posonly: 

864 kwargs['posonlyargs'] = list(kwargs['args'][:n_posonly]) 

865 

866 if inspect.iscoroutinefunction(func): 

867 kwargs['is_async'] = True 

868 

869 return cls(**kwargs) 

870 

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

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

873 the FunctionBuilder. 

874 

875 Args: 

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

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

878 dict. 

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

880 special ``__source__`` attribute on the resulting 

881 function. Defaults to True. 

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

883 applicable. Defaults to True. 

884 

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

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

887 """ 

888 execdict = execdict or {} 

889 body = self.body or self._default_body 

890 

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

892 tmpl += '\n{body}' 

893 

894 if self.is_async: 

895 tmpl = 'async ' + tmpl 

896 

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

898 

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

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

901 doc=self.doc, body=body) 

902 self._compile(src, execdict) 

903 func = execdict[name] 

904 

905 func.__name__ = self.name 

906 func.__doc__ = self.doc 

907 func.__defaults__ = self.defaults 

908 func.__kwdefaults__ = self.kwonlydefaults 

909 func.__annotations__ = self.annotations 

910 

911 if with_dict: 

912 func.__dict__.update(self.dict) 

913 func.__module__ = self.module 

914 # TODO: caller module fallback? 

915 

916 if add_source: 

917 func.__source__ = src 

918 

919 return func 

920 

921 def get_defaults_dict(self): 

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

923 respective values. 

924 """ 

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

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

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

928 if kwonlydefaults: 

929 ret.update(kwonlydefaults) 

930 return ret 

931 

932 def get_arg_names(self, only_required=False): 

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

934 if only_required: 

935 defaults_dict = self.get_defaults_dict() 

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

937 return arg_names 

938 

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(f'arg {arg_name!r} already in func {self.name} arg list') 

946 if arg_name in self.kwonlyargs: 

947 raise ExistingArgument(f'arg {arg_name!r} already in func {self.name} kwonly arg list') 

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 

957 def remove_arg(self, arg_name): 

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

959 resulting function will have one less argument per call to 

960 this function. 

961 

962 Args: 

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

964 

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

966 

967 """ 

968 args = self.args 

969 d_dict = self.get_defaults_dict() 

970 try: 

971 args.remove(arg_name) 

972 except ValueError: 

973 try: 

974 self.kwonlyargs.remove(arg_name) 

975 except (AttributeError, ValueError): 

976 # missing from both 

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

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

979 exc.arg_name = arg_name 

980 raise exc 

981 else: 

982 self.kwonlydefaults.pop(arg_name, None) 

983 else: 

984 d_dict.pop(arg_name, None) 

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

986 return 

987 

988 def _compile(self, src, execdict): 

989 

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

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

992 try: 

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

994 exec(code, execdict) 

995 except Exception: 

996 raise 

997 return execdict 

998 

999 

1000class MissingArgument(ValueError): 

1001 pass 

1002 

1003 

1004class ExistingArgument(ValueError): 

1005 pass 

1006 

1007 

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

1009 "based on boltons.strutils.indent" 

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

1011 for line in text.splitlines()] 

1012 return newline.join(indented_lines) 

1013 

1014 

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

1016 """ 

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

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

1019 

1020 e.g. 

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

1022 if pre_func: 

1023 pre_func() 

1024 func() 

1025 if post_func: 

1026 post_func() 

1027 

1028 vs 

1029 

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

1031 pre_func() 

1032 func() 

1033 post_func() 

1034 """ 

1035 return None 

1036 

1037 

1038 

1039 

1040 

1041def once(func): 

1042 """Decorator that ensures a function is only executed once, caching 

1043 the result for all subsequent calls. Thread-safe: concurrent callers 

1044 block until the first execution completes, then all receive the 

1045 cached result. 

1046 

1047 This is especially useful in cases like logging, where multiple 

1048 initializations can cause problems. 

1049 

1050 The decorated function must take no arguments. 

1051 

1052 >>> call_count = 0 

1053 >>> @once 

1054 ... def expensive_setup(): 

1055 ... global call_count 

1056 ... call_count += 1 

1057 ... return 'initialized' 

1058 >>> expensive_setup() 

1059 'initialized' 

1060 >>> expensive_setup() 

1061 'initialized' 

1062 >>> call_count 

1063 1 

1064 """ 

1065 _UNSET = object() 

1066 lock = threading.Lock() 

1067 result = _UNSET 

1068 

1069 @functools.wraps(func) 

1070 def wrapper(): 

1071 nonlocal result 

1072 if result is not _UNSET: 

1073 return result 

1074 with lock: 

1075 if result is not _UNSET: 

1076 return result 

1077 result = func() 

1078 return result 

1079 

1080 return wrapper 

1081 

1082# end funcutils.py