Failed Conditions
Pull Request — master (#2076)
by Abdeali
02:11
created

coalib/settings/FunctionMetadata.py (49 issues)

1
from collections import OrderedDict
2
from copy import copy
3
from inspect import getfullargspec, ismethod
4
5
from coalib.settings.DocumentationComment import DocumentationComment
6
7
8
class FunctionMetadata:
9
    str_nodesc = "No description given."
10
    str_optional = "Optional, defaults to '{}'."
11
12
    def __init__(self,
13
                 name,
14
                 desc="",
15
                 retval_desc="",
16
                 non_optional_params=None,
17
                 optional_params=None,
18
                 omit=frozenset()):
19
        """
20
        Creates the FunctionMetadata object.
21
22
        :param name:                The name of the function.
23
        :param desc:                The description of the function.
24
        :param retval_desc:         The retval description of the function.
25
        :param non_optional_params: A dict containing the name of non optional
26
                                    parameters as the key and a tuple of a
27
                                    description and the python annotation. To
28
                                    preserve the order, use OrderedDict.
29
        :param optional_params:     A dict containing the name of optional
30
                                    parameters as the key and a tuple
31
                                    of a description, the python annotation and
32
                                    the default value. To preserve the order,
33
                                    use OrderedDict.
34
        :param omit:                A set of parameters to omit.
35
        """
36
        if non_optional_params is None:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable non_optional_params does not seem to be defined.
Loading history...
37
            non_optional_params = OrderedDict()
38
        if optional_params is None:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable optional_params does not seem to be defined.
Loading history...
39
            optional_params = OrderedDict()
40
41
        self.name = name
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable name does not seem to be defined.
Loading history...
42
        self.desc = desc
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable desc does not seem to be defined.
Loading history...
43
        self.retval_desc = retval_desc
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable retval_desc does not seem to be defined.
Loading history...
44
        self._non_optional_params = non_optional_params
45
        self._optional_params = optional_params
46
        self.omit = set(omit)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable omit does not seem to be defined.
Loading history...
47
48
    def _filter_out_omitted(self, params):
49
        """
50
        Filters out parameters that are to omit. This is a helper method for
51
        the param related properties.
52
53
        :param params: The parameter dictionary to filter.
54
        :return:       The filtered dictionary.
55
        """
56
        return OrderedDict(filter(lambda p: p[0] not in self.omit,
57
                                  tuple(params.items())))
58
59
    @property
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable property does not seem to be defined.
Loading history...
60
    def non_optional_params(self):
61
        """
62
        Retrieves a dict containing the name of non optional parameters as the
63
        key and a tuple of a description and the python annotation. Values that
64
        are present in self.omit will be omitted.
65
        """
66
        return self._filter_out_omitted(self._non_optional_params)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
67
68
    @property
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable property does not seem to be defined.
Loading history...
69
    def optional_params(self):
70
        """
71
        Retrieves a dict containing the name of optional parameters as the key
72
        and a tuple of a description, the python annotation and the default
73
        value. Values that are present in self.omit will be omitted.
74
        """
75
        return self._filter_out_omitted(self._optional_params)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
76
77
    def create_params_from_section(self, section):
78
        """
79
        Create a params dictionary for this function that holds all values the
80
        function needs plus optional ones that are available.
81
82
        :param section:    The section to retrieve the values from.
83
        :return:           The params dictionary.
84
        """
85
        params = {}
86
87
        for param in self.non_optional_params:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
88
            _, annotation = self.non_optional_params[param]
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable param does not seem to be defined.
Loading history...
89
            params[param] = self._get_param(param, section, annotation)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable annotation does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable section does not seem to be defined.
Loading history...
90
91
        for param in self.optional_params:
92
            if param in section:
93
                _, annotation, _ = self.optional_params[param]
94
                params[param] = self._get_param(param, section, annotation)
95
96
        return params
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable params does not seem to be defined.
Loading history...
97
98
    @staticmethod
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable staticmethod does not seem to be defined.
Loading history...
99
    def _get_param(param, section, annotation):
100
        if annotation is None:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable annotation does not seem to be defined.
Loading history...
101
            annotation = lambda x: x
102
103
        try:
104
            return annotation(section[param])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable section does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable param does not seem to be defined.
Loading history...
105
        except (TypeError, ValueError):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ValueError does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable TypeError does not seem to be defined.
Loading history...
106
            raise ValueError("Unable to convert parameter {} into type "
107
                             "{}.".format(repr(param), annotation))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable annotation does not seem to be defined.
Loading history...
108
109
    @classmethod
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable classmethod does not seem to be defined.
Loading history...
110
    def from_function(cls, func, omit=frozenset()):
111
        """
112
        Creates a FunctionMetadata object from a function. Please note that any
113
        variable argument lists are not supported. If you do not want the
114
        first (usual named 'self') argument to appear please pass the method of
115
        an actual INSTANCE of a class; passing the method of the class isn't
116
        enough. Alternatively you can add "self" to the omit set.
117
118
        :param func: The function. If __metadata__ of the unbound function is
119
                     present it will be copied and used, otherwise it will be
120
                     generated.
121
        :param omit: A set of parameter names that are to be ignored.
122
        :return:     The FunctionMetadata object corresponding to the given
123
                     function.
124
        """
125
        if hasattr(func, "__metadata__"):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable func does not seem to be defined.
Loading history...
126
            metadata = copy(func.__metadata__)
127
            metadata.omit = omit
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable omit does not seem to be defined.
Loading history...
128
            return metadata
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable metadata does not seem to be defined.
Loading history...
129
130
        doc = func.__doc__ or ""
131
        doc_comment = DocumentationComment.from_docstring(doc)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable doc does not seem to be defined.
Loading history...
132
133
        non_optional_params = OrderedDict()
134
        optional_params = OrderedDict()
135
136
        argspec = getfullargspec(func)
137
        args = argspec.args or ()
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable argspec does not seem to be defined.
Loading history...
138
        defaults = argspec.defaults or ()
139
        num_non_defaults = len(args) - len(defaults)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable args does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable defaults does not seem to be defined.
Loading history...
140
        for i, arg in enumerate(args):
141
            # Implicit self argument or omitted explicitly
142
            if i < 1 and ismethod(func):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable i does not seem to be defined.
Loading history...
143
                continue
144
145
            if i < num_non_defaults:
146
                non_optional_params[arg] = (
147
                    doc_comment.param_dict.get(arg, cls.str_nodesc),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable arg does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable cls does not seem to be defined.
Loading history...
148
                    argspec.annotations.get(arg, None))
149
            else:
150
                optional_params[arg] = (
151
                    doc_comment.param_dict.get(arg, cls.str_nodesc) + " (" +
152
                    cls.str_optional.format(
153
                        str(defaults[i-num_non_defaults])) + ")",
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable num_non_defaults does not seem to be defined.
Loading history...
154
                    argspec.annotations.get(arg, None),
155
                    defaults[i-num_non_defaults])
156
157
        return cls(name=func.__name__,
158
                   desc=doc_comment.desc,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable doc_comment does not seem to be defined.
Loading history...
159
                   retval_desc=doc_comment.retval_desc,
160
                   non_optional_params=non_optional_params,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable non_optional_params does not seem to be defined.
Loading history...
161
                   optional_params=optional_params,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable optional_params does not seem to be defined.
Loading history...
162
                   omit=omit)
163
164
    def filter_parameters(self, dct):
165
        """
166
        Filters the given dict for keys that are declared as parameters inside
167
        this metadata (either optional or non-optional).
168
169
        You can use this function to safely pass parameters from a given
170
        dictionary:
171
172
        >>> def multiply(a, b=2, c=0):
173
        ...     return a * b + c
174
        >>> metadata = FunctionMetadata.from_function(multiply)
175
        >>> args = metadata.filter_parameters({'a': 10, 'b': 20, 'd': 30})
176
177
        You can safely pass the arguments to the function now:
178
179
        >>> multiply(**args)  # 10 * 20
180
        200
181
182
        :param dct:
183
            The dict to filter.
184
        :return:
185
            A new dict containing the filtered items.
186
        """
187
        return {key: dct[key]
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable dct does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable key does not seem to be defined.
Loading history...
188
                for key in (self.non_optional_params.keys() |
189
                            self.optional_params.keys())
190
                if key in dct}
191
192
    @classmethod
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable classmethod does not seem to be defined.
Loading history...
193
    def merge(cls, *metadatas):
194
        """
195
        Merges signatures of ``FunctionMetadata`` objects.
196
197
        Parameter (either optional or non-optional) and non-parameter
198
        descriptions are merged from left to right, meaning the right hand
199
        metadata overrides the left hand one.
200
201
        >>> def a(x, y):
202
        ...     '''
203
        ...     desc of *a*
204
        ...     :param x: x of a
205
        ...     :param y: y of a
206
        ...     :return:  5*x*y
207
        ...     '''
208
        ...     return 5 * x * y
209
        >>> def b(x):
210
        ...     '''
211
        ...     desc of *b*
212
        ...     :param x: x of b
213
        ...     :return:  100*x
214
        ...     '''
215
        ...     return 100 * x
216
        >>> metadata1 = FunctionMetadata.from_function(a)
217
        >>> metadata2 = FunctionMetadata.from_function(b)
218
        >>> merged = FunctionMetadata.merge(metadata1, metadata2)
219
        >>> merged.name
220
        "<Merged signature of 'a', 'b'>"
221
        >>> merged.desc
222
        'desc of *b*'
223
        >>> merged.retval_desc
224
        '100*x'
225
        >>> merged.non_optional_params['x'][0]
226
        'x of b'
227
        >>> merged.non_optional_params['y'][0]
228
        'y of a'
229
230
        :param metadatas:
231
            The sequence of metadatas to merge.
232
        :return:
233
            A ``FunctionMetadata`` object containing the merged signature of
234
            all given metadatas.
235
        """
236
        # Collect the metadatas, as we operate on them more often and we want
237
        # to support arbitrary sequences.
238
        metadatas = tuple(metadatas)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable metadatas does not seem to be defined.
Loading history...
239
240
        merged_name = ("<Merged signature of " +
241
                       ", ".join(repr(metadata.name)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable metadata does not seem to be defined.
Loading history...
242
                                 for metadata in metadatas) +
243
                       ">")
244
245
        merged_desc = next((m.desc for m in reversed(metadatas) if m.desc), "")
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable m does not seem to be defined.
Loading history...
246
        merged_retval_desc = next(
247
            (m.retval_desc for m in reversed(metadatas) if m.retval_desc), "")
248
        merged_non_optional_params = {}
249
        merged_optional_params = {}
250
251
        for metadata in metadatas:
252
            # Use the fields and not the properties to get also omitted
253
            # parameters.
254
            merged_non_optional_params.update(metadata._non_optional_params)
255
            merged_optional_params.update(metadata._optional_params)
256
257
        merged_omit = set.union(*(metadata.omit for metadata in metadatas))
258
259
        return cls(merged_name,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable merged_name does not seem to be defined.
Loading history...
260
                   merged_desc,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable merged_desc does not seem to be defined.
Loading history...
261
                   merged_retval_desc,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable merged_retval_desc does not seem to be defined.
Loading history...
262
                   merged_non_optional_params,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable merged_non_optional_params does not seem to be defined.
Loading history...
263
                   merged_optional_params,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable merged_optional_params does not seem to be defined.
Loading history...
264
                   merged_omit)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable merged_omit does not seem to be defined.
Loading history...
265