##// END OF EJS Templates
Use inspect instead of type checks
Jeroen Demeyer -
Show More
@@ -1,817 +1,818 b''
1 1 """Function signature objects for callables.
2 2
3 3 Back port of Python 3.3's function signature tools from the inspect module,
4 4 modified to be compatible with Python 2.7 and 3.2+.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Python 3.3 stdlib inspect.py is public domain
9 9 #
10 10 # Backports Copyright (C) 2013 Aaron Iles
11 11 # Used under Apache License Version 2.0
12 12 #
13 13 # Further Changes are Copyright (C) 2013 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from __future__ import absolute_import, division, print_function
20 20 import itertools
21 21 import functools
22 22 import re
23 23 import types
24 import inspect
24 25
25 26
26 27 # patch for single-file
27 28 # we don't support 2.6, so we can just import OrderedDict
28 29 from collections import OrderedDict
29 30
30 31 __version__ = '0.3'
31 32 # end patch
32 33
33 34 __all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
34 35
35 36
36 37 _WrapperDescriptor = type(type.__call__)
37 38 _MethodWrapper = type(all.__call__)
38 39
39 40 _NonUserDefinedCallables = (_WrapperDescriptor,
40 41 _MethodWrapper,
41 42 types.BuiltinFunctionType)
42 43
43 44
44 45 def formatannotation(annotation, base_module=None):
45 46 if isinstance(annotation, type):
46 47 if annotation.__module__ in ('builtins', '__builtin__', base_module):
47 48 return annotation.__name__
48 49 return annotation.__module__+'.'+annotation.__name__
49 50 return repr(annotation)
50 51
51 52
52 53 def _get_user_defined_method(cls, method_name, *nested):
53 54 try:
54 55 if cls is type:
55 56 return
56 57 meth = getattr(cls, method_name)
57 58 for name in nested:
58 59 meth = getattr(meth, name, meth)
59 60 except AttributeError:
60 61 return
61 62 else:
62 63 if not isinstance(meth, _NonUserDefinedCallables):
63 64 # Once '__signature__' will be added to 'C'-level
64 65 # callables, this check won't be necessary
65 66 return meth
66 67
67 68
68 69 def signature(obj):
69 70 '''Get a signature object for the passed callable.'''
70 71
71 72 if not callable(obj):
72 73 raise TypeError('{0!r} is not a callable object'.format(obj))
73 74
74 if isinstance(obj, types.MethodType):
75 if inspect.ismethod(obj):
75 76 if obj.__self__ is None:
76 77 # Unbound method - treat it as a function (no distinction in Py 3)
77 78 obj = obj.__func__
78 79 else:
79 80 # Bound method: trim off the first parameter (typically self or cls)
80 81 sig = signature(obj.__func__)
81 82 return sig.replace(parameters=tuple(sig.parameters.values())[1:])
82 83
83 84 try:
84 85 sig = obj.__signature__
85 86 except AttributeError:
86 87 pass
87 88 else:
88 89 if sig is not None:
89 90 return sig
90 91
91 92 try:
92 93 # Was this function wrapped by a decorator?
93 94 wrapped = obj.__wrapped__
94 95 except AttributeError:
95 96 pass
96 97 else:
97 98 return signature(wrapped)
98 99
99 if isinstance(obj, types.FunctionType):
100 if inspect.isfunction(obj):
100 101 return Signature.from_function(obj)
101 102
102 103 if isinstance(obj, functools.partial):
103 104 sig = signature(obj.func)
104 105
105 106 new_params = OrderedDict(sig.parameters.items())
106 107
107 108 partial_args = obj.args or ()
108 109 partial_keywords = obj.keywords or {}
109 110 try:
110 111 ba = sig.bind_partial(*partial_args, **partial_keywords)
111 112 except TypeError as ex:
112 113 msg = 'partial object {0!r} has incorrect arguments'.format(obj)
113 114 raise ValueError(msg)
114 115
115 116 for arg_name, arg_value in ba.arguments.items():
116 117 param = new_params[arg_name]
117 118 if arg_name in partial_keywords:
118 119 # We set a new default value, because the following code
119 120 # is correct:
120 121 #
121 122 # >>> def foo(a): print(a)
122 123 # >>> print(partial(partial(foo, a=10), a=20)())
123 124 # 20
124 125 # >>> print(partial(partial(foo, a=10), a=20)(a=30))
125 126 # 30
126 127 #
127 128 # So, with 'partial' objects, passing a keyword argument is
128 129 # like setting a new default value for the corresponding
129 130 # parameter
130 131 #
131 132 # We also mark this parameter with '_partial_kwarg'
132 133 # flag. Later, in '_bind', the 'default' value of this
133 134 # parameter will be added to 'kwargs', to simulate
134 135 # the 'functools.partial' real call.
135 136 new_params[arg_name] = param.replace(default=arg_value,
136 137 _partial_kwarg=True)
137 138
138 139 elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
139 140 not param._partial_kwarg):
140 141 new_params.pop(arg_name)
141 142
142 143 return sig.replace(parameters=new_params.values())
143 144
144 145 sig = None
145 146 if isinstance(obj, type):
146 147 # obj is a class or a metaclass
147 148
148 149 # First, let's see if it has an overloaded __call__ defined
149 150 # in its metaclass
150 151 call = _get_user_defined_method(type(obj), '__call__')
151 152 if call is not None:
152 153 sig = signature(call)
153 154 else:
154 155 # Now we check if the 'obj' class has a '__new__' method
155 156 new = _get_user_defined_method(obj, '__new__')
156 157 if new is not None:
157 158 sig = signature(new)
158 159 else:
159 160 # Finally, we should have at least __init__ implemented
160 161 init = _get_user_defined_method(obj, '__init__')
161 162 if init is not None:
162 163 sig = signature(init)
163 164 elif not isinstance(obj, _NonUserDefinedCallables):
164 165 # An object with __call__
165 166 # We also check that the 'obj' is not an instance of
166 167 # _WrapperDescriptor or _MethodWrapper to avoid
167 168 # infinite recursion (and even potential segfault)
168 169 call = _get_user_defined_method(type(obj), '__call__', 'im_func')
169 170 if call is not None:
170 171 sig = signature(call)
171 172
172 173 if sig is not None:
173 174 return sig
174 175
175 176 if isinstance(obj, types.BuiltinFunctionType):
176 177 # Raise a nicer error message for builtins
177 178 msg = 'no signature found for builtin function {0!r}'.format(obj)
178 179 raise ValueError(msg)
179 180
180 181 raise ValueError('callable {0!r} is not supported by signature'.format(obj))
181 182
182 183
183 184 class _void(object):
184 185 '''A private marker - used in Parameter & Signature'''
185 186
186 187
187 188 class _empty(object):
188 189 pass
189 190
190 191
191 192 class _ParameterKind(int):
192 193 def __new__(self, *args, **kwargs):
193 194 obj = int.__new__(self, *args)
194 195 obj._name = kwargs['name']
195 196 return obj
196 197
197 198 def __str__(self):
198 199 return self._name
199 200
200 201 def __repr__(self):
201 202 return '<_ParameterKind: {0!r}>'.format(self._name)
202 203
203 204
204 205 _POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
205 206 _POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
206 207 _VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
207 208 _KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
208 209 _VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
209 210
210 211
211 212 class Parameter(object):
212 213 '''Represents a parameter in a function signature.
213 214
214 215 Has the following public attributes:
215 216
216 217 * name : str
217 218 The name of the parameter as a string.
218 219 * default : object
219 220 The default value for the parameter if specified. If the
220 221 parameter has no default value, this attribute is not set.
221 222 * annotation
222 223 The annotation for the parameter if specified. If the
223 224 parameter has no annotation, this attribute is not set.
224 225 * kind : str
225 226 Describes how argument values are bound to the parameter.
226 227 Possible values: `Parameter.POSITIONAL_ONLY`,
227 228 `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
228 229 `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
229 230 '''
230 231
231 232 __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
232 233
233 234 POSITIONAL_ONLY = _POSITIONAL_ONLY
234 235 POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
235 236 VAR_POSITIONAL = _VAR_POSITIONAL
236 237 KEYWORD_ONLY = _KEYWORD_ONLY
237 238 VAR_KEYWORD = _VAR_KEYWORD
238 239
239 240 empty = _empty
240 241
241 242 def __init__(self, name, kind, default=_empty, annotation=_empty,
242 243 _partial_kwarg=False):
243 244
244 245 if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
245 246 _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
246 247 raise ValueError("invalid value for 'Parameter.kind' attribute")
247 248 self._kind = kind
248 249
249 250 if default is not _empty:
250 251 if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
251 252 msg = '{0} parameters cannot have default values'.format(kind)
252 253 raise ValueError(msg)
253 254 self._default = default
254 255 self._annotation = annotation
255 256
256 257 if name is None:
257 258 if kind != _POSITIONAL_ONLY:
258 259 raise ValueError("None is not a valid name for a "
259 260 "non-positional-only parameter")
260 261 self._name = name
261 262 else:
262 263 name = str(name)
263 264 if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
264 265 msg = '{0!r} is not a valid parameter name'.format(name)
265 266 raise ValueError(msg)
266 267 self._name = name
267 268
268 269 self._partial_kwarg = _partial_kwarg
269 270
270 271 @property
271 272 def name(self):
272 273 return self._name
273 274
274 275 @property
275 276 def default(self):
276 277 return self._default
277 278
278 279 @property
279 280 def annotation(self):
280 281 return self._annotation
281 282
282 283 @property
283 284 def kind(self):
284 285 return self._kind
285 286
286 287 def replace(self, name=_void, kind=_void, annotation=_void,
287 288 default=_void, _partial_kwarg=_void):
288 289 '''Creates a customized copy of the Parameter.'''
289 290
290 291 if name is _void:
291 292 name = self._name
292 293
293 294 if kind is _void:
294 295 kind = self._kind
295 296
296 297 if annotation is _void:
297 298 annotation = self._annotation
298 299
299 300 if default is _void:
300 301 default = self._default
301 302
302 303 if _partial_kwarg is _void:
303 304 _partial_kwarg = self._partial_kwarg
304 305
305 306 return type(self)(name, kind, default=default, annotation=annotation,
306 307 _partial_kwarg=_partial_kwarg)
307 308
308 309 def __str__(self):
309 310 kind = self.kind
310 311
311 312 formatted = self._name
312 313 if kind == _POSITIONAL_ONLY:
313 314 if formatted is None:
314 315 formatted = ''
315 316 formatted = '<{0}>'.format(formatted)
316 317
317 318 # Add annotation and default value
318 319 if self._annotation is not _empty:
319 320 formatted = '{0}:{1}'.format(formatted,
320 321 formatannotation(self._annotation))
321 322
322 323 if self._default is not _empty:
323 324 formatted = '{0}={1}'.format(formatted, repr(self._default))
324 325
325 326 if kind == _VAR_POSITIONAL:
326 327 formatted = '*' + formatted
327 328 elif kind == _VAR_KEYWORD:
328 329 formatted = '**' + formatted
329 330
330 331 return formatted
331 332
332 333 def __repr__(self):
333 334 return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
334 335 id(self), self.name)
335 336
336 337 def __hash__(self):
337 338 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
338 339 raise TypeError(msg)
339 340
340 341 def __eq__(self, other):
341 342 return (issubclass(other.__class__, Parameter) and
342 343 self._name == other._name and
343 344 self._kind == other._kind and
344 345 self._default == other._default and
345 346 self._annotation == other._annotation)
346 347
347 348 def __ne__(self, other):
348 349 return not self.__eq__(other)
349 350
350 351
351 352 class BoundArguments(object):
352 353 '''Result of :meth:`Signature.bind` call. Holds the mapping of arguments
353 354 to the function's parameters.
354 355
355 356 Has the following public attributes:
356 357
357 358 arguments : :class:`collections.OrderedDict`
358 359 An ordered mutable mapping of parameters' names to arguments' values.
359 360 Does not contain arguments' default values.
360 361 signature : :class:`Signature`
361 362 The Signature object that created this instance.
362 363 args : tuple
363 364 Tuple of positional arguments values.
364 365 kwargs : dict
365 366 Dict of keyword arguments values.
366 367 '''
367 368
368 369 def __init__(self, signature, arguments):
369 370 self.arguments = arguments
370 371 self._signature = signature
371 372
372 373 @property
373 374 def signature(self):
374 375 return self._signature
375 376
376 377 @property
377 378 def args(self):
378 379 args = []
379 380 for param_name, param in self._signature.parameters.items():
380 381 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
381 382 param._partial_kwarg):
382 383 # Keyword arguments mapped by 'functools.partial'
383 384 # (Parameter._partial_kwarg is True) are mapped
384 385 # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
385 386 # KEYWORD_ONLY
386 387 break
387 388
388 389 try:
389 390 arg = self.arguments[param_name]
390 391 except KeyError:
391 392 # We're done here. Other arguments
392 393 # will be mapped in 'BoundArguments.kwargs'
393 394 break
394 395 else:
395 396 if param.kind == _VAR_POSITIONAL:
396 397 # *args
397 398 args.extend(arg)
398 399 else:
399 400 # plain argument
400 401 args.append(arg)
401 402
402 403 return tuple(args)
403 404
404 405 @property
405 406 def kwargs(self):
406 407 kwargs = {}
407 408 kwargs_started = False
408 409 for param_name, param in self._signature.parameters.items():
409 410 if not kwargs_started:
410 411 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
411 412 param._partial_kwarg):
412 413 kwargs_started = True
413 414 else:
414 415 if param_name not in self.arguments:
415 416 kwargs_started = True
416 417 continue
417 418
418 419 if not kwargs_started:
419 420 continue
420 421
421 422 try:
422 423 arg = self.arguments[param_name]
423 424 except KeyError:
424 425 pass
425 426 else:
426 427 if param.kind == _VAR_KEYWORD:
427 428 # **kwargs
428 429 kwargs.update(arg)
429 430 else:
430 431 # plain keyword argument
431 432 kwargs[param_name] = arg
432 433
433 434 return kwargs
434 435
435 436 def __hash__(self):
436 437 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
437 438 raise TypeError(msg)
438 439
439 440 def __eq__(self, other):
440 441 return (issubclass(other.__class__, BoundArguments) and
441 442 self.signature == other.signature and
442 443 self.arguments == other.arguments)
443 444
444 445 def __ne__(self, other):
445 446 return not self.__eq__(other)
446 447
447 448
448 449 class Signature(object):
449 450 '''A Signature object represents the overall signature of a function.
450 451 It stores a Parameter object for each parameter accepted by the
451 452 function, as well as information specific to the function itself.
452 453
453 454 A Signature object has the following public attributes:
454 455
455 456 parameters : :class:`collections.OrderedDict`
456 457 An ordered mapping of parameters' names to the corresponding
457 458 Parameter objects (keyword-only arguments are in the same order
458 459 as listed in `code.co_varnames`).
459 460 return_annotation
460 461 The annotation for the return type of the function if specified.
461 462 If the function has no annotation for its return type, this
462 463 attribute is not set.
463 464 '''
464 465
465 466 __slots__ = ('_return_annotation', '_parameters')
466 467
467 468 _parameter_cls = Parameter
468 469 _bound_arguments_cls = BoundArguments
469 470
470 471 empty = _empty
471 472
472 473 def __init__(self, parameters=None, return_annotation=_empty,
473 474 __validate_parameters__=True):
474 475 '''Constructs Signature from the given list of Parameter
475 476 objects and 'return_annotation'. All arguments are optional.
476 477 '''
477 478
478 479 if parameters is None:
479 480 params = OrderedDict()
480 481 else:
481 482 if __validate_parameters__:
482 483 params = OrderedDict()
483 484 top_kind = _POSITIONAL_ONLY
484 485
485 486 for idx, param in enumerate(parameters):
486 487 kind = param.kind
487 488 if kind < top_kind:
488 489 msg = 'wrong parameter order: {0} before {1}'
489 490 msg = msg.format(top_kind, param.kind)
490 491 raise ValueError(msg)
491 492 else:
492 493 top_kind = kind
493 494
494 495 name = param.name
495 496 if name is None:
496 497 name = str(idx)
497 498 param = param.replace(name=name)
498 499
499 500 if name in params:
500 501 msg = 'duplicate parameter name: {0!r}'.format(name)
501 502 raise ValueError(msg)
502 503 params[name] = param
503 504 else:
504 505 params = OrderedDict(((param.name, param)
505 506 for param in parameters))
506 507
507 508 self._parameters = params
508 509 self._return_annotation = return_annotation
509 510
510 511 @classmethod
511 512 def from_function(cls, func):
512 513 '''Constructs Signature for the given python function'''
513 514
514 if not isinstance(func, types.FunctionType):
515 if not inspect.isfunction(func):
515 516 raise TypeError('{0!r} is not a Python function'.format(func))
516 517
517 518 Parameter = cls._parameter_cls
518 519
519 520 # Parameter information.
520 521 func_code = func.__code__
521 522 pos_count = func_code.co_argcount
522 523 arg_names = func_code.co_varnames
523 524 positional = tuple(arg_names[:pos_count])
524 525 keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
525 526 keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
526 527 annotations = getattr(func, '__annotations__', {})
527 528 defaults = func.__defaults__
528 529 kwdefaults = getattr(func, '__kwdefaults__', None)
529 530
530 531 if defaults:
531 532 pos_default_count = len(defaults)
532 533 else:
533 534 pos_default_count = 0
534 535
535 536 parameters = []
536 537
537 538 # Non-keyword-only parameters w/o defaults.
538 539 non_default_count = pos_count - pos_default_count
539 540 for name in positional[:non_default_count]:
540 541 annotation = annotations.get(name, _empty)
541 542 parameters.append(Parameter(name, annotation=annotation,
542 543 kind=_POSITIONAL_OR_KEYWORD))
543 544
544 545 # ... w/ defaults.
545 546 for offset, name in enumerate(positional[non_default_count:]):
546 547 annotation = annotations.get(name, _empty)
547 548 parameters.append(Parameter(name, annotation=annotation,
548 549 kind=_POSITIONAL_OR_KEYWORD,
549 550 default=defaults[offset]))
550 551
551 552 # *args
552 553 if func_code.co_flags & 0x04:
553 554 name = arg_names[pos_count + keyword_only_count]
554 555 annotation = annotations.get(name, _empty)
555 556 parameters.append(Parameter(name, annotation=annotation,
556 557 kind=_VAR_POSITIONAL))
557 558
558 559 # Keyword-only parameters.
559 560 for name in keyword_only:
560 561 default = _empty
561 562 if kwdefaults is not None:
562 563 default = kwdefaults.get(name, _empty)
563 564
564 565 annotation = annotations.get(name, _empty)
565 566 parameters.append(Parameter(name, annotation=annotation,
566 567 kind=_KEYWORD_ONLY,
567 568 default=default))
568 569 # **kwargs
569 570 if func_code.co_flags & 0x08:
570 571 index = pos_count + keyword_only_count
571 572 if func_code.co_flags & 0x04:
572 573 index += 1
573 574
574 575 name = arg_names[index]
575 576 annotation = annotations.get(name, _empty)
576 577 parameters.append(Parameter(name, annotation=annotation,
577 578 kind=_VAR_KEYWORD))
578 579
579 580 return cls(parameters,
580 581 return_annotation=annotations.get('return', _empty),
581 582 __validate_parameters__=False)
582 583
583 584 @property
584 585 def parameters(self):
585 586 try:
586 587 return types.MappingProxyType(self._parameters)
587 588 except AttributeError:
588 589 return OrderedDict(self._parameters.items())
589 590
590 591 @property
591 592 def return_annotation(self):
592 593 return self._return_annotation
593 594
594 595 def replace(self, parameters=_void, return_annotation=_void):
595 596 '''Creates a customized copy of the Signature.
596 597 Pass 'parameters' and/or 'return_annotation' arguments
597 598 to override them in the new copy.
598 599 '''
599 600
600 601 if parameters is _void:
601 602 parameters = self.parameters.values()
602 603
603 604 if return_annotation is _void:
604 605 return_annotation = self._return_annotation
605 606
606 607 return type(self)(parameters,
607 608 return_annotation=return_annotation)
608 609
609 610 def __hash__(self):
610 611 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
611 612 raise TypeError(msg)
612 613
613 614 def __eq__(self, other):
614 615 if (not issubclass(type(other), Signature) or
615 616 self.return_annotation != other.return_annotation or
616 617 len(self.parameters) != len(other.parameters)):
617 618 return False
618 619
619 620 other_positions = dict((param, idx)
620 621 for idx, param in enumerate(other.parameters.keys()))
621 622
622 623 for idx, (param_name, param) in enumerate(self.parameters.items()):
623 624 if param.kind == _KEYWORD_ONLY:
624 625 try:
625 626 other_param = other.parameters[param_name]
626 627 except KeyError:
627 628 return False
628 629 else:
629 630 if param != other_param:
630 631 return False
631 632 else:
632 633 try:
633 634 other_idx = other_positions[param_name]
634 635 except KeyError:
635 636 return False
636 637 else:
637 638 if (idx != other_idx or
638 639 param != other.parameters[param_name]):
639 640 return False
640 641
641 642 return True
642 643
643 644 def __ne__(self, other):
644 645 return not self.__eq__(other)
645 646
646 647 def _bind(self, args, kwargs, partial=False):
647 648 '''Private method. Don't use directly.'''
648 649
649 650 arguments = OrderedDict()
650 651
651 652 parameters = iter(self.parameters.values())
652 653 parameters_ex = ()
653 654 arg_vals = iter(args)
654 655
655 656 if partial:
656 657 # Support for binding arguments to 'functools.partial' objects.
657 658 # See 'functools.partial' case in 'signature()' implementation
658 659 # for details.
659 660 for param_name, param in self.parameters.items():
660 661 if (param._partial_kwarg and param_name not in kwargs):
661 662 # Simulating 'functools.partial' behavior
662 663 kwargs[param_name] = param.default
663 664
664 665 while True:
665 666 # Let's iterate through the positional arguments and corresponding
666 667 # parameters
667 668 try:
668 669 arg_val = next(arg_vals)
669 670 except StopIteration:
670 671 # No more positional arguments
671 672 try:
672 673 param = next(parameters)
673 674 except StopIteration:
674 675 # No more parameters. That's it. Just need to check that
675 676 # we have no `kwargs` after this while loop
676 677 break
677 678 else:
678 679 if param.kind == _VAR_POSITIONAL:
679 680 # That's OK, just empty *args. Let's start parsing
680 681 # kwargs
681 682 break
682 683 elif param.name in kwargs:
683 684 if param.kind == _POSITIONAL_ONLY:
684 685 msg = '{arg!r} parameter is positional only, ' \
685 686 'but was passed as a keyword'
686 687 msg = msg.format(arg=param.name)
687 688 raise TypeError(msg)
688 689 parameters_ex = (param,)
689 690 break
690 691 elif (param.kind == _VAR_KEYWORD or
691 692 param.default is not _empty):
692 693 # That's fine too - we have a default value for this
693 694 # parameter. So, lets start parsing `kwargs`, starting
694 695 # with the current parameter
695 696 parameters_ex = (param,)
696 697 break
697 698 else:
698 699 if partial:
699 700 parameters_ex = (param,)
700 701 break
701 702 else:
702 703 msg = '{arg!r} parameter lacking default value'
703 704 msg = msg.format(arg=param.name)
704 705 raise TypeError(msg)
705 706 else:
706 707 # We have a positional argument to process
707 708 try:
708 709 param = next(parameters)
709 710 except StopIteration:
710 711 raise TypeError('too many positional arguments')
711 712 else:
712 713 if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
713 714 # Looks like we have no parameter for this positional
714 715 # argument
715 716 raise TypeError('too many positional arguments')
716 717
717 718 if param.kind == _VAR_POSITIONAL:
718 719 # We have an '*args'-like argument, let's fill it with
719 720 # all positional arguments we have left and move on to
720 721 # the next phase
721 722 values = [arg_val]
722 723 values.extend(arg_vals)
723 724 arguments[param.name] = tuple(values)
724 725 break
725 726
726 727 if param.name in kwargs:
727 728 raise TypeError('multiple values for argument '
728 729 '{arg!r}'.format(arg=param.name))
729 730
730 731 arguments[param.name] = arg_val
731 732
732 733 # Now, we iterate through the remaining parameters to process
733 734 # keyword arguments
734 735 kwargs_param = None
735 736 for param in itertools.chain(parameters_ex, parameters):
736 737 if param.kind == _POSITIONAL_ONLY:
737 738 # This should never happen in case of a properly built
738 739 # Signature object (but let's have this check here
739 740 # to ensure correct behaviour just in case)
740 741 raise TypeError('{arg!r} parameter is positional only, '
741 742 'but was passed as a keyword'. \
742 743 format(arg=param.name))
743 744
744 745 if param.kind == _VAR_KEYWORD:
745 746 # Memorize that we have a '**kwargs'-like parameter
746 747 kwargs_param = param
747 748 continue
748 749
749 750 param_name = param.name
750 751 try:
751 752 arg_val = kwargs.pop(param_name)
752 753 except KeyError:
753 754 # We have no value for this parameter. It's fine though,
754 755 # if it has a default value, or it is an '*args'-like
755 756 # parameter, left alone by the processing of positional
756 757 # arguments.
757 758 if (not partial and param.kind != _VAR_POSITIONAL and
758 759 param.default is _empty):
759 760 raise TypeError('{arg!r} parameter lacking default value'. \
760 761 format(arg=param_name))
761 762
762 763 else:
763 764 arguments[param_name] = arg_val
764 765
765 766 if kwargs:
766 767 if kwargs_param is not None:
767 768 # Process our '**kwargs'-like parameter
768 769 arguments[kwargs_param.name] = kwargs
769 770 else:
770 771 raise TypeError('too many keyword arguments')
771 772
772 773 return self._bound_arguments_cls(self, arguments)
773 774
774 775 def bind(self, *args, **kwargs):
775 776 '''Get a :class:`BoundArguments` object, that maps the passed `args`
776 777 and `kwargs` to the function's signature. Raises :exc:`TypeError`
777 778 if the passed arguments can not be bound.
778 779 '''
779 780 return self._bind(args, kwargs)
780 781
781 782 def bind_partial(self, *args, **kwargs):
782 783 '''Get a :class:`BoundArguments` object, that partially maps the
783 784 passed `args` and `kwargs` to the function's signature.
784 785 Raises :exc:`TypeError` if the passed arguments can not be bound.
785 786 '''
786 787 return self._bind(args, kwargs, partial=True)
787 788
788 789 def __str__(self):
789 790 result = []
790 791 render_kw_only_separator = True
791 792 for idx, param in enumerate(self.parameters.values()):
792 793 formatted = str(param)
793 794
794 795 kind = param.kind
795 796 if kind == _VAR_POSITIONAL:
796 797 # OK, we have an '*args'-like parameter, so we won't need
797 798 # a '*' to separate keyword-only arguments
798 799 render_kw_only_separator = False
799 800 elif kind == _KEYWORD_ONLY and render_kw_only_separator:
800 801 # We have a keyword-only parameter to render and we haven't
801 802 # rendered an '*args'-like parameter before, so add a '*'
802 803 # separator to the parameters list ("foo(arg1, *, arg2)" case)
803 804 result.append('*')
804 805 # This condition should be only triggered once, so
805 806 # reset the flag
806 807 render_kw_only_separator = False
807 808
808 809 result.append(formatted)
809 810
810 811 rendered = '({0})'.format(', '.join(result))
811 812
812 813 if self.return_annotation is not _empty:
813 814 anno = formatannotation(self.return_annotation)
814 815 rendered += ' -> {0}'.format(anno)
815 816
816 817 return rendered
817 818
General Comments 0
You need to be logged in to leave comments. Login now