Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

356 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 MethodType, FunctionType 

44 

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

46 

47 

48try: 

49 from .typeutils import make_sentinel 

50 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT') 

51except ImportError: 

52 NO_DEFAULT = object() 

53 

54 

55def inspect_formatargspec( 

56 args, varargs=None, varkw=None, defaults=None, 

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

58 formatarg=str, 

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

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

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

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

63 formatannotation=formatannotation): 

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

65 Python 3 has deprecated formatargspec and requested that Signature 

66 be used instead, however this requires a full reimplementation 

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

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

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

70 """ 

71 

72 def formatargandannotation(arg): 

73 result = formatarg(arg) 

74 if arg in annotations: 

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

76 return result 

77 specs = [] 

78 if defaults: 

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

80 for i, arg in enumerate(args): 

81 spec = formatargandannotation(arg) 

82 if defaults and i >= firstdefault: 

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

84 specs.append(spec) 

85 if varargs is not None: 

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

87 else: 

88 if kwonlyargs: 

89 specs.append('*') 

90 if kwonlyargs: 

91 for kwonlyarg in kwonlyargs: 

92 spec = formatargandannotation(kwonlyarg) 

93 if kwonlydefaults and kwonlyarg in kwonlydefaults: 

94 spec += formatvalue(kwonlydefaults[kwonlyarg]) 

95 specs.append(spec) 

96 if varkw is not None: 

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

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

99 if 'return' in annotations: 

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

101 return result 

102 

103 

104def get_module_callables(mod, ignore=None): 

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

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

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

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

109 """ 

110 if isinstance(mod, str): 

111 mod = sys.modules[mod] 

112 types, funcs = {}, {} 

113 for attr_name in dir(mod): 

114 if ignore and ignore(attr_name): 

115 continue 

116 try: 

117 attr = getattr(mod, attr_name) 

118 except Exception: 

119 continue 

120 try: 

121 attr_mod_name = attr.__module__ 

122 except AttributeError: 

123 continue 

124 if attr_mod_name != mod.__name__: 

125 continue 

126 if isinstance(attr, type): 

127 types[attr_name] = attr 

128 elif callable(attr): 

129 funcs[attr_name] = attr 

130 return types, funcs 

131 

132 

133def mro_items(type_obj): 

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

135 throughout the type hierarchy (respecting the MRO). 

136 

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

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

139 """ 

140 # TODO: handle slots? 

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

142 for ct in type_obj.__mro__) 

143 

144 

145def dir_dict(obj, raise_exc=False): 

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

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

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

149 """ 

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

151 ret = {} 

152 for k in dir(obj): 

153 try: 

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

155 except Exception: 

156 if raise_exc: 

157 raise 

158 return ret 

159 

160 

161def copy_function(orig, copy_dict=True): 

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

163 globals, closure, etc. 

164 

165 >>> func = lambda: func 

166 >>> func() is func 

167 True 

168 >>> func_copy = copy_function(func) 

169 >>> func_copy() is func 

170 True 

171 >>> func_copy is not func 

172 True 

173 

174 Args: 

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

176 function, not just any method or callable. 

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

178 instance. Defaults to ``True``. 

179 """ 

180 ret = FunctionType(orig.__code__, 

181 orig.__globals__, 

182 name=orig.__name__, 

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

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

185 if hasattr(orig, "__kwdefaults__"): 

186 ret.__kwdefaults__ = orig.__kwdefaults__ 

187 if copy_dict: 

188 ret.__dict__.update(orig.__dict__) 

189 return ret 

190 

191 

192def partial_ordering(cls): 

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

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

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

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

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

198 additional comparison methods. 

199 

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

201 

202 >>> @partial_ordering 

203 ... class MySet(set): 

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

205 ... return self.issubset(other) 

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

207 ... return self.issuperset(other) 

208 ... 

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

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

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

212 >>> b < a 

213 True 

214 >>> b > a 

215 False 

216 >>> b < c 

217 True 

218 >>> a < c 

219 False 

220 >>> c > a 

221 False 

222 """ 

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

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

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

226 

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

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

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

230 

231 return cls 

232 

233 

234class InstancePartial(functools.partial): 

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

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

237 developers to curry arguments and incrementally create simpler 

238 callables for a variety of use cases. 

239 

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

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

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

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

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

245 descriptor protocol. There are no other differences in 

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

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

248 

249 """ 

250 @property 

251 def _partialmethod(self): 

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

253 

254 def __get__(self, obj, obj_type): 

255 return make_method(self, obj, obj_type) 

256 

257 

258 

259class CachedInstancePartial(functools.partial): 

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

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

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

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

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

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

266 

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

268 """ 

269 @property 

270 def _partialmethod(self): 

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

272 

273 def __set_name__(self, obj_type, name): 

274 self.__name__ = name 

275 

276 def __get__(self, obj, obj_type): 

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

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

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

280 self.__doc__ = self.func.__doc__ 

281 self.__module__ = self.func.__module__ 

282 

283 name = self.__name__ 

284 

285 if obj is None: 

286 return make_method(self, obj, obj_type) 

287 try: 

288 # since this is a data descriptor, this block 

289 # is probably only hit once (per object) 

290 return obj.__dict__[name] 

291 except KeyError: 

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

293 return ret 

294 

295 

296partial = CachedInstancePartial 

297 

298 

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

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

301 a basic Python-style function call. 

302 

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

304 func(1, 2, c=3) 

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

306 a_func(1) 

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

308 kw_func(a=1, b=2) 

309 

310 """ 

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

312 if kw: 

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

314 kwargs = kwargs or {} 

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

316 if isinstance(kwargs, dict): 

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

318 else: 

319 kwarg_items = kwargs 

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

321 

322 all_args_text = a_text 

323 if all_args_text and kw_text: 

324 all_args_text += ', ' 

325 all_args_text += kw_text 

326 

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

328 

329 

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

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

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

333 

334 >>> class Flag(object): 

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

336 ... self.length = length 

337 ... self.width = width 

338 ... self.depth = depth 

339 ... 

340 

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

342 

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

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

345 Flag(5, 10) 

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

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

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

349 

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

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

352 

353 Args: 

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

355 attributes will be checked 

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

357 rendered as positional arguments in the output repr. 

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

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

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

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

362 the *opt_key* check. Defaults to None. 

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

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

365 ``None``-check. 

366 

367 """ 

368 cn = type(obj).__name__ 

369 req_names = req_names or [] 

370 opt_names = opt_names or [] 

371 uniq_names, all_names = set(), [] 

372 for name in req_names + opt_names: 

373 if name in uniq_names: 

374 continue 

375 uniq_names.add(name) 

376 all_names.append(name) 

377 

378 if opt_key is None: 

379 opt_key = lambda v: v is None 

380 assert callable(opt_key) 

381 

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

383 

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

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

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

387 

388 return format_invocation(cn, args, kw_items) 

389 

390 

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

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

393 

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

395 

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

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

398 roundtrip, like types and functions. 

399 

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

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

402 

403 >>> class Flag(object): 

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

405 ... self.length = length 

406 ... self.width = width 

407 ... self.depth = depth 

408 ... 

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

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

411 <Flag length=5 width=10> 

412 

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

414 built-in behavior. 

415 

416 >>> print(format_nonexp_repr(flag)) 

417 <Flag id=...> 

418 """ 

419 cn = obj.__class__.__name__ 

420 req_names = req_names or [] 

421 opt_names = opt_names or [] 

422 uniq_names, all_names = set(), [] 

423 for name in req_names + opt_names: 

424 if name in uniq_names: 

425 continue 

426 uniq_names.add(name) 

427 all_names.append(name) 

428 

429 if opt_key is None: 

430 opt_key = lambda v: v is None 

431 assert callable(opt_key) 

432 

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

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

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

436 if not labels: 

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

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

439 return ret 

440 

441 

442 

443# # # 

444# # # Function builder 

445# # # 

446 

447 

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

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

450 

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

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

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

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

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

456 

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

458 

459 >>> from boltons.funcutils import wraps 

460 >>> 

461 >>> def print_return(func): 

462 ... @wraps(func) 

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

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

465 ... print(ret) 

466 ... return ret 

467 ... return wrapper 

468 ... 

469 >>> @print_return 

470 ... def example(): 

471 ... '''docstring''' 

472 ... return 'example return value' 

473 >>> 

474 >>> val = example() 

475 example return value 

476 >>> example.__name__ 

477 'example' 

478 >>> example.__doc__ 

479 'docstring' 

480 """ 

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

482 injected=injected, expected=expected, **kw) 

483 

484 

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

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

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

488 wrapped function's: 

489 

490 * Name 

491 * Documentation 

492 * Module 

493 * Signature 

494 

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

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

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

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

499 built-in version:: 

500 

501 >>> from boltons.funcutils import update_wrapper 

502 >>> 

503 >>> def print_return(func): 

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

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

506 ... print(ret) 

507 ... return ret 

508 ... return update_wrapper(wrapper, func) 

509 ... 

510 >>> @print_return 

511 ... def example(): 

512 ... '''docstring''' 

513 ... return 'example return value' 

514 >>> 

515 >>> val = example() 

516 example return value 

517 >>> example.__name__ 

518 'example' 

519 >>> example.__doc__ 

520 'docstring' 

521 

522 In addition, the boltons version of update_wrapper supports 

523 modifying the outer signature. By passing a list of 

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

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

526 arguments that aren't passed in. 

527 

528 Args: 

529 

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

531 *func* are to be copied. 

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

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

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

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

536 default) pairs) representing new arguments introduced by 

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

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

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

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

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

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

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

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

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

546 inject_to_varkw (bool): Ignore missing arguments when a 

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

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

549 in the updated function. 

550 

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

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

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

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

555 """ 

556 if injected is None: 

557 injected = [] 

558 elif isinstance(injected, str): 

559 injected = [injected] 

560 else: 

561 injected = list(injected) 

562 

563 expected_items = _parse_wraps_expected(expected) 

564 

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

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

567 ' staticmethods, change the order of wrapping to' 

568 ' wrap the underlying function: %r' 

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

570 

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

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

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

574 if kw: 

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

576 

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

578 build_from = build_from or wrapper 

579 

580 fb = FunctionBuilder.from_func(build_from or func) 

581 

582 for arg in injected: 

583 try: 

584 fb.remove_arg(arg) 

585 except MissingArgument: 

586 if inject_to_varkw and fb.varkw is not None: 

587 continue # keyword arg will be caught by the varkw 

588 raise 

589 

590 for arg, default in expected_items: 

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

592 

593 if fb.is_async: 

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

595 else: 

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

597 

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

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

600 

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

602 del fully_wrapped.__dict__['__wrapped__'] 

603 elif not hide_wrapped: 

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

605 

606 return fully_wrapped 

607 

608 

609def _parse_wraps_expected(expected): 

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

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

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

613 # you look 

614 if expected is None: 

615 expected = [] 

616 elif isinstance(expected, str): 

617 expected = [(expected, NO_DEFAULT)] 

618 

619 expected_items = [] 

620 try: 

621 expected_iter = iter(expected) 

622 except TypeError as e: 

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

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

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

626 for argname in expected_iter: 

627 if isinstance(argname, str): 

628 # dict keys and bare strings 

629 try: 

630 default = expected[argname] 

631 except TypeError: 

632 default = NO_DEFAULT 

633 else: 

634 # pairs 

635 try: 

636 argname, default = argname 

637 except (TypeError, ValueError): 

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

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

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

641 if not isinstance(argname, str): 

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

643 

644 expected_items.append((argname, default)) 

645 

646 return expected_items 

647 

648 

649class FunctionBuilder: 

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

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

652 scratch. 

653 

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

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

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

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

658 newly compiled function, based on the values configured. 

659 

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

661 ... body='return 5') 

662 >>> f = fb.get_func() 

663 >>> f() 

664 5 

665 >>> fb.varkw = 'kw' 

666 >>> f_kw = fb.get_func() 

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

668 5 

669 

670 Note that function signatures themselves changed quite a bit in 

671 Python 3, so several arguments are only applicable to 

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

673 the constructor are keyword arguments. 

674 

675 Args: 

676 name (str): Name of the function. 

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

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

679 imported. Defaults to None. 

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

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

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

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

684 denoting no arguments. 

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

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

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

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

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

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

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

692 those arguments that have defaults. 

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

694 keyword arguments. **Python 3 only.** 

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

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

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

698 forth. **Python 3 only.** 

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

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

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

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

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

704 functions compiled with this FunctionBuilder. 

705 

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

707 can be mutated as necessary. 

708 

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

710 

711 """ 

712 

713 _argspec_defaults = {'args': list, 

714 'varargs': lambda: None, 

715 'varkw': lambda: None, 

716 'defaults': lambda: None, 

717 'kwonlyargs': list, 

718 'kwonlydefaults': dict, 

719 'annotations': dict} 

720 

721 @classmethod 

722 def _argspec_to_dict(cls, f): 

723 argspec = inspect.getfullargspec(f) 

724 return {attr: getattr(argspec, attr) 

725 for attr in cls._argspec_defaults} 

726 

727 _defaults = {'doc': str, 

728 'dict': dict, 

729 'is_async': lambda: False, 

730 'module': lambda: None, 

731 'body': lambda: 'pass', 

732 'indent': lambda: 4, 

733 "annotations": dict, 

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

735 

736 _defaults.update(_argspec_defaults) 

737 

738 _compile_count = itertools.count() 

739 

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

741 self.name = name 

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

743 val = kw.pop(a, None) 

744 if val is None: 

745 val = default_factory() 

746 setattr(self, a, val) 

747 

748 if kw: 

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

750 return 

751 

752 # def get_argspec(self): # TODO 

753 

754 def get_sig_str(self, with_annotations=True): 

755 """Return function signature as a string. 

756 

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

758 will omit annotations if it is set to False. 

759 """ 

760 if with_annotations: 

761 annotations = self.annotations 

762 else: 

763 annotations = {} 

764 

765 return inspect_formatargspec(self.args, 

766 self.varargs, 

767 self.varkw, 

768 [], 

769 self.kwonlyargs, 

770 {}, 

771 annotations) 

772 

773 _KWONLY_MARKER = re.compile(r""" 

774 \* # a star 

775 \s* # followed by any amount of whitespace 

776 , # followed by a comma 

777 \s* # followed by any amount of whitespace 

778 """, re.VERBOSE) 

779 

780 def get_invocation_str(self): 

781 kwonly_pairs = None 

782 formatters = {} 

783 if self.kwonlyargs: 

784 kwonly_pairs = {arg: arg 

785 for arg in self.kwonlyargs} 

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

787 

788 sig = inspect_formatargspec(self.args, 

789 self.varargs, 

790 self.varkw, 

791 [], 

792 kwonly_pairs, 

793 kwonly_pairs, 

794 {}, 

795 **formatters) 

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

797 return sig[1:-1] 

798 

799 @classmethod 

800 def from_func(cls, func): 

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

802 function. The original function will not be stored or 

803 modified. 

804 """ 

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

806 # TODO: might worry about __closure__? 

807 if not callable(func): 

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

809 

810 if isinstance(func, functools.partial): 

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

812 'doc': func.func.__doc__, 

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

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

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

816 else: 

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

818 'doc': func.__doc__, 

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

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

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

822 

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

824 

825 if inspect.iscoroutinefunction(func): 

826 kwargs['is_async'] = True 

827 

828 return cls(**kwargs) 

829 

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

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

832 the FunctionBuilder. 

833 

834 Args: 

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

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

837 dict. 

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

839 special ``__source__`` attribute on the resulting 

840 function. Defaults to True. 

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

842 applicable. Defaults to True. 

843 

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

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

846 """ 

847 execdict = execdict or {} 

848 body = self.body or self._default_body 

849 

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

851 tmpl += '\n{body}' 

852 

853 if self.is_async: 

854 tmpl = 'async ' + tmpl 

855 

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

857 

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

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

860 doc=self.doc, body=body) 

861 self._compile(src, execdict) 

862 func = execdict[name] 

863 

864 func.__name__ = self.name 

865 func.__doc__ = self.doc 

866 func.__defaults__ = self.defaults 

867 func.__kwdefaults__ = self.kwonlydefaults 

868 func.__annotations__ = self.annotations 

869 

870 if with_dict: 

871 func.__dict__.update(self.dict) 

872 func.__module__ = self.module 

873 # TODO: caller module fallback? 

874 

875 if add_source: 

876 func.__source__ = src 

877 

878 return func 

879 

880 def get_defaults_dict(self): 

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

882 respective values. 

883 """ 

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

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

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

887 if kwonlydefaults: 

888 ret.update(kwonlydefaults) 

889 return ret 

890 

891 def get_arg_names(self, only_required=False): 

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

893 if only_required: 

894 defaults_dict = self.get_defaults_dict() 

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

896 return arg_names 

897 

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

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

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

901 keyword-only argument 

902 """ 

903 if arg_name in self.args: 

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

905 if arg_name in self.kwonlyargs: 

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

907 if not kwonly: 

908 self.args.append(arg_name) 

909 if default is not NO_DEFAULT: 

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

911 else: 

912 self.kwonlyargs.append(arg_name) 

913 if default is not NO_DEFAULT: 

914 self.kwonlydefaults[arg_name] = default 

915 

916 def remove_arg(self, arg_name): 

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

918 resulting function will have one less argument per call to 

919 this function. 

920 

921 Args: 

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

923 

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

925 

926 """ 

927 args = self.args 

928 d_dict = self.get_defaults_dict() 

929 try: 

930 args.remove(arg_name) 

931 except ValueError: 

932 try: 

933 self.kwonlyargs.remove(arg_name) 

934 except (AttributeError, ValueError): 

935 # missing from both 

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

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

938 exc.arg_name = arg_name 

939 raise exc 

940 else: 

941 self.kwonlydefaults.pop(arg_name, None) 

942 else: 

943 d_dict.pop(arg_name, None) 

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

945 return 

946 

947 def _compile(self, src, execdict): 

948 

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

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

951 try: 

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

953 exec(code, execdict) 

954 except Exception: 

955 raise 

956 return execdict 

957 

958 

959class MissingArgument(ValueError): 

960 pass 

961 

962 

963class ExistingArgument(ValueError): 

964 pass 

965 

966 

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

968 "based on boltons.strutils.indent" 

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

970 for line in text.splitlines()] 

971 return newline.join(indented_lines) 

972 

973 

974from functools import total_ordering 

975 

976 

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

978 """ 

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

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

981 

982 e.g. 

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

984 if pre_func: 

985 pre_func() 

986 func() 

987 if post_func: 

988 post_func() 

989 

990 vs 

991 

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

993 pre_func() 

994 func() 

995 post_func() 

996 """ 

997 return None 

998 

999# end funcutils.py