Completed
Push — master ( dbc38f...56accc )
by Klaus
01:34
created

get_argspec()   C

Complexity

Conditions 14

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
dl 0
loc 6
rs 5.2747
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like get_argspec() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# coding=utf-8
3
from __future__ import division, print_function, unicode_literals
4
5
import inspect
6
from collections import OrderedDict
7
import sys
8
9
__sacred__ = True  # marks files that should be filtered from stack traces
10
11
12
if sys.version_info[0] < 3:  # python2
13
    def get_argspec(f):
14
        args, vararg_name, kw_wildcard_name, defaults = inspect.getargspec(f)
15
        defaults = defaults or []
16
        positional_args = args[:len(args) - len(defaults)]
17
        kwargs = OrderedDict(zip(args[-len(defaults):], defaults))
18
        return args, vararg_name, kw_wildcard_name, positional_args, kwargs
19
20
elif sys.version_info[1] < 3:  # python 3.2
21
    def get_argspec(f):
22
        (args, vararg_name, kw_wildcard_name, defaults, kwonlyargs, kwdefaults,
23
         annotations) = inspect.getfullargspec(f)
24
        defaults = defaults or []
25
        positional_args = args[:len(args) - len(defaults)]
26
        kwargs = OrderedDict(zip(args[-len(defaults):], defaults))
27
        return args, vararg_name, kw_wildcard_name, positional_args, kwargs
28
29
else:  # python >= 3.3
30
    from inspect import Parameter
31
    ARG_TYPES = [Parameter.POSITIONAL_ONLY,
32
                 Parameter.POSITIONAL_OR_KEYWORD,
33
                 Parameter.KEYWORD_ONLY]
34
    POSARG_TYPES = [Parameter.POSITIONAL_ONLY,
35
                    Parameter.POSITIONAL_OR_KEYWORD]
36
37
    def get_argspec(f):
38
        sig = inspect.signature(f)
39
        args = [n for n, p in sig.parameters.items()
40
                if p.kind in ARG_TYPES]
41
        pos_args = [n for n, p in sig.parameters.items()
42
                    if p.kind in POSARG_TYPES and p.default == inspect._empty]
43
        varargs = [n for n, p in sig.parameters.items()
44
                   if p.kind == Parameter.VAR_POSITIONAL]
45
        # only use first vararg  (how on earth would you have more anyways?)
46
        vararg_name = varargs[0] if varargs else None
47
48
        varkws = [n for n, p in sig.parameters.items()
49
                  if p.kind == Parameter.VAR_KEYWORD]
50
        # only use first varkw  (how on earth would you have more anyways?)
51
        kw_wildcard_name = varkws[0] if varkws else None
52
        kwargs = OrderedDict([(n, p.default)
53
                              for n, p in sig.parameters.items()
54
                              if p.default != inspect._empty])
55
56
        return args, vararg_name, kw_wildcard_name, pos_args, kwargs
57
58
59
class Signature(object):
60
    """
61
    Extracts and stores information about the signature of a function.
62
63
    name : the functions name
64
    arguments : list of all arguments
65
    vararg_name : name of the *args variable
66
    kw_wildcard_name : name of the **kwargs variable
67
    positional_args : list of all positional-only arguments
68
    kwargs : dict of all keyword arguments mapped to their default
69
    """
70
71
    def __init__(self, f):
72
        self.name = f.__name__
73
        args, vararg_name, kw_wildcard_name, pos_args, kwargs = get_argspec(f)
74
        self.arguments = args
75
        self.vararg_name = vararg_name
76
        self.kw_wildcard_name = kw_wildcard_name
77
        self.positional_args = pos_args
78
        self.kwargs = kwargs
79
80
    def get_free_parameters(self, args, kwargs, bound=False):
81
        expected_args = self._get_expected_args(bound)
82
        return [a for a in expected_args[len(args):] if a not in kwargs]
83
84
    def construct_arguments(self, args, kwargs, options, bound=False):
85
        """
86
        Construct args list and kwargs dictionary for this signature.
87
88
        They are created such that:
89
          - the original explicit call arguments (args, kwargs) are preserved
90
          - missing arguments are filled in by name using options (if possible)
91
          - default arguments are overridden by options
92
          - TypeError is thrown if:
93
            * kwargs contains one or more unexpected keyword arguments
94
            * conflicting values for a parameter in both args and kwargs
95
            * there is an unfilled parameter at the end of this process
96
        """
97
        expected_args = self._get_expected_args(bound)
98
        self._assert_no_unexpected_args(expected_args, args)
99
        self._assert_no_unexpected_kwargs(expected_args, kwargs)
100
        self._assert_no_duplicate_args(expected_args, args, kwargs)
101
102
        args, kwargs = self._fill_in_options(args, kwargs, options, bound)
103
104
        self._assert_no_missing_args(args, kwargs, bound)
105
        return args, kwargs
106
107
    def __unicode__(self):
108
        pos_args = self.positional_args
109
        varg = ["*" + self.vararg_name] if self.vararg_name else []
110
        kwargs = ["{}={}".format(n, v.__repr__())
111
                  for n, v in self.kwargs.items()]
112
        kw_wc = ["**" + self.kw_wildcard_name] if self.kw_wildcard_name else []
113
        arglist = pos_args + varg + kwargs + kw_wc
114
        return "{}({})".format(self.name, ", ".join(arglist))
115
116
    def __repr__(self):
117
        return "<Signature at 0x{1:x} for '{0}'>".format(self.name, id(self))
118
119
    def _get_expected_args(self, bound):
120
        if bound:
121
            # When called as instance method, the instance ('self') will be
122
            # passed as first argument automatically, so the first argument
123
            # should be excluded from the signature during this invocation.
124
            return self.arguments[1:]
125
        else:
126
            return self.arguments
127
128
    def _assert_no_unexpected_args(self, expected_args, args):
129
        if not self.vararg_name and len(args) > len(expected_args):
130
            unexpected_args = args[len(expected_args):]
131
            raise TypeError("{} got unexpected argument(s): {}".format(
132
                self.name, unexpected_args))
133
134
    def _assert_no_unexpected_kwargs(self, expected_args, kwargs):
135
        if self.kw_wildcard_name:
136
            return
137
        unexpected_kwargs = set(kwargs) - set(expected_args)
138
        if unexpected_kwargs:
139
            raise TypeError("{} got unexpected kwarg(s): {}".format(
140
                self.name, sorted(unexpected_kwargs)))
141
142
    def _assert_no_duplicate_args(self, expected_args, args, kwargs):
143
        positional_arguments = expected_args[:len(args)]
144
        duplicate_arguments = [v for v in positional_arguments if v in kwargs]
145
        if duplicate_arguments:
146
            raise TypeError("{} got multiple values for argument(s) {}".format(
147
                self.name, duplicate_arguments))
148
149
    def _fill_in_options(self, args, kwargs, options, bound):
150
        free_params = self.get_free_parameters(args, kwargs, bound)
151
        for param in free_params:
152
            if param in options:
153
                kwargs[param] = options[param]
154
        return args, kwargs
155
156
    def _assert_no_missing_args(self, args, kwargs, bound):
157
        free_params = self.get_free_parameters(args, kwargs, bound)
158
        missing_args = [m for m in free_params if m not in self.kwargs]
159
        if missing_args:
160
            raise TypeError("{} is missing value(s) for {}".format(
161
                self.name, missing_args))
162