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