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

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

362 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 re 

39import inspect 

40import functools 

41import itertools 

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 if fb.is_async: 

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

606 else: 

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

608 

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

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

611 

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

613 del fully_wrapped.__dict__['__wrapped__'] 

614 elif not hide_wrapped: 

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

616 

617 return fully_wrapped 

618 

619 

620def _parse_wraps_expected(expected): 

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

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

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

624 # you look 

625 if expected is None: 

626 expected = [] 

627 elif isinstance(expected, str): 

628 expected = [(expected, NO_DEFAULT)] 

629 

630 expected_items = [] 

631 try: 

632 expected_iter = iter(expected) 

633 except TypeError as e: 

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

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

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

637 for argname in expected_iter: 

638 if isinstance(argname, str): 

639 # dict keys and bare strings 

640 try: 

641 default = expected[argname] 

642 except TypeError: 

643 default = NO_DEFAULT 

644 else: 

645 # pairs 

646 try: 

647 argname, default = argname 

648 except (TypeError, ValueError): 

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

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

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

652 if not isinstance(argname, str): 

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

654 

655 expected_items.append((argname, default)) 

656 

657 return expected_items 

658 

659 

660class FunctionBuilder: 

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

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

663 scratch. 

664 

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

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

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

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

669 newly compiled function, based on the values configured. 

670 

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

672 ... body='return 5') 

673 >>> f = fb.get_func() 

674 >>> f() 

675 5 

676 >>> fb.varkw = 'kw' 

677 >>> f_kw = fb.get_func() 

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

679 5 

680 

681 Note that function signatures themselves changed quite a bit in 

682 Python 3, so several arguments are only applicable to 

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

684 the constructor are keyword arguments. 

685 

686 Args: 

687 name (str): Name of the function. 

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

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

690 imported. Defaults to None. 

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

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

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

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

695 denoting no arguments. 

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

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

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

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

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

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

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

703 those arguments that have defaults. 

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

705 keyword arguments. **Python 3 only.** 

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

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

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

709 forth. **Python 3 only.** 

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

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

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

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

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

715 functions compiled with this FunctionBuilder. 

716 

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

718 can be mutated as necessary. 

719 

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

721 

722 """ 

723 

724 _argspec_defaults = {'args': list, 

725 'varargs': lambda: None, 

726 'varkw': lambda: None, 

727 'defaults': lambda: None, 

728 'kwonlyargs': list, 

729 'kwonlydefaults': dict, 

730 'annotations': dict} 

731 

732 @classmethod 

733 def _argspec_to_dict(cls, f): 

734 argspec = inspect.getfullargspec(f) 

735 return {attr: getattr(argspec, attr) 

736 for attr in cls._argspec_defaults} 

737 

738 _defaults = {'doc': str, 

739 'dict': dict, 

740 'is_async': lambda: False, 

741 'module': lambda: None, 

742 'body': lambda: 'pass', 

743 'indent': lambda: 4, 

744 "annotations": dict, 

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

746 

747 _defaults.update(_argspec_defaults) 

748 

749 _compile_count = itertools.count() 

750 

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

752 self.name = name 

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

754 val = kw.pop(a, None) 

755 if val is None: 

756 val = default_factory() 

757 setattr(self, a, val) 

758 

759 if kw: 

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

761 return 

762 

763 # def get_argspec(self): # TODO 

764 

765 def get_sig_str(self, with_annotations=True): 

766 """Return function signature as a string. 

767 

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

769 will omit annotations if it is set to False. 

770 """ 

771 if with_annotations: 

772 annotations = self.annotations 

773 else: 

774 annotations = {} 

775 

776 return inspect_formatargspec(self.args, 

777 self.varargs, 

778 self.varkw, 

779 [], 

780 self.kwonlyargs, 

781 {}, 

782 annotations) 

783 

784 _KWONLY_MARKER = re.compile(r""" 

785 \* # a star 

786 \s* # followed by any amount of whitespace 

787 , # followed by a comma 

788 \s* # followed by any amount of whitespace 

789 """, re.VERBOSE) 

790 

791 def get_invocation_str(self): 

792 kwonly_pairs = None 

793 formatters = {} 

794 if self.kwonlyargs: 

795 kwonly_pairs = {arg: arg 

796 for arg in self.kwonlyargs} 

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

798 

799 sig = inspect_formatargspec(self.args, 

800 self.varargs, 

801 self.varkw, 

802 [], 

803 kwonly_pairs, 

804 kwonly_pairs, 

805 {}, 

806 **formatters) 

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

808 return sig[1:-1] 

809 

810 @classmethod 

811 def from_func(cls, func): 

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

813 function. The original function will not be stored or 

814 modified. 

815 """ 

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

817 # TODO: might worry about __closure__? 

818 if not callable(func): 

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

820 

821 if isinstance(func, functools.partial): 

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

823 'doc': func.func.__doc__, 

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

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

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

827 else: 

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

829 'doc': func.__doc__, 

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

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

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

833 

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

835 

836 if inspect.iscoroutinefunction(func): 

837 kwargs['is_async'] = True 

838 

839 return cls(**kwargs) 

840 

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

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

843 the FunctionBuilder. 

844 

845 Args: 

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

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

848 dict. 

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

850 special ``__source__`` attribute on the resulting 

851 function. Defaults to True. 

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

853 applicable. Defaults to True. 

854 

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

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

857 """ 

858 execdict = execdict or {} 

859 body = self.body or self._default_body 

860 

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

862 tmpl += '\n{body}' 

863 

864 if self.is_async: 

865 tmpl = 'async ' + tmpl 

866 

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

868 

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

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

871 doc=self.doc, body=body) 

872 self._compile(src, execdict) 

873 func = execdict[name] 

874 

875 func.__name__ = self.name 

876 func.__doc__ = self.doc 

877 func.__defaults__ = self.defaults 

878 func.__kwdefaults__ = self.kwonlydefaults 

879 func.__annotations__ = self.annotations 

880 

881 if with_dict: 

882 func.__dict__.update(self.dict) 

883 func.__module__ = self.module 

884 # TODO: caller module fallback? 

885 

886 if add_source: 

887 func.__source__ = src 

888 

889 return func 

890 

891 def get_defaults_dict(self): 

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

893 respective values. 

894 """ 

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

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

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

898 if kwonlydefaults: 

899 ret.update(kwonlydefaults) 

900 return ret 

901 

902 def get_arg_names(self, only_required=False): 

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

904 if only_required: 

905 defaults_dict = self.get_defaults_dict() 

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

907 return arg_names 

908 

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

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

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

912 keyword-only argument 

913 """ 

914 if arg_name in self.args: 

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

916 if arg_name in self.kwonlyargs: 

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

918 if not kwonly: 

919 self.args.append(arg_name) 

920 if default is not NO_DEFAULT: 

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

922 else: 

923 self.kwonlyargs.append(arg_name) 

924 if default is not NO_DEFAULT: 

925 self.kwonlydefaults[arg_name] = default 

926 

927 def remove_arg(self, arg_name): 

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

929 resulting function will have one less argument per call to 

930 this function. 

931 

932 Args: 

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

934 

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

936 

937 """ 

938 args = self.args 

939 d_dict = self.get_defaults_dict() 

940 try: 

941 args.remove(arg_name) 

942 except ValueError: 

943 try: 

944 self.kwonlyargs.remove(arg_name) 

945 except (AttributeError, ValueError): 

946 # missing from both 

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

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

949 exc.arg_name = arg_name 

950 raise exc 

951 else: 

952 self.kwonlydefaults.pop(arg_name, None) 

953 else: 

954 d_dict.pop(arg_name, None) 

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

956 return 

957 

958 def _compile(self, src, execdict): 

959 

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

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

962 try: 

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

964 exec(code, execdict) 

965 except Exception: 

966 raise 

967 return execdict 

968 

969 

970class MissingArgument(ValueError): 

971 pass 

972 

973 

974class ExistingArgument(ValueError): 

975 pass 

976 

977 

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

979 "based on boltons.strutils.indent" 

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

981 for line in text.splitlines()] 

982 return newline.join(indented_lines) 

983 

984 

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

986 """ 

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

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

989 

990 e.g. 

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

992 if pre_func: 

993 pre_func() 

994 func() 

995 if post_func: 

996 post_func() 

997 

998 vs 

999 

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

1001 pre_func() 

1002 func() 

1003 post_func() 

1004 """ 

1005 return None 

1006 

1007# end funcutils.py