get_argspec()   F
last analyzed

Complexity

Conditions 14

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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