Completed
Pull Request — master (#1127)
by Mischa
01:42
created

non_optional_params()   A

Complexity

Conditions 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 8
rs 9.4286
1
from inspect import ismethod, getfullargspec
2
from collections import OrderedDict
3
from copy import copy
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:
37
            non_optional_params = OrderedDict()
38
        if optional_params is None:
39
            optional_params = OrderedDict()
40
41
        self.name = name
42
        self.desc = desc
43
        self.retval_desc = retval_desc
44
        self._non_optional_params = non_optional_params
45
        self._optional_params = optional_params
46
        self.omit = set(omit)
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
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)
67
68
    @property
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)
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:
88
            dummy, annotation = self.non_optional_params[param]
89
            params[param] = self._get_param(param, section, annotation)
90
91
        for param in self.optional_params:
92
            if param in section:
93
                dummy, annotation, dummy = self.optional_params[param]
94
                params[param] = self._get_param(param, section, annotation)
95
96
        return params
97
98
    @staticmethod
99
    def _get_param(param, section, annotation):
100
        if annotation is None:
101
            annotation = lambda x: x
102
103
        try:
104
            return annotation(section[param])
105
        except:
106
            raise ValueError("Unable to convert parameter {} into type "
107
                             "{}.".format(repr(param), annotation))
108
109
    @classmethod
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__"):
126
            metadata = copy(func.__metadata__)
127
            metadata.omit = omit
128
            return metadata
129
130
        doc = func.__doc__
131
        if doc is None:
132
            doc = ""
133
        doc_comment = DocumentationComment.from_docstring(doc)
134
135
        non_optional_params = OrderedDict()
136
        optional_params = OrderedDict()
137
138
        argspec = getfullargspec(func)
139
        args = argspec.args if argspec.args is not None else ()
140
        defaults = argspec.defaults if argspec.defaults is not None else ()
141
        num_non_defaults = len(args) - len(defaults)
142
        for i, arg in enumerate(args):
143
            # Implicit self argument or omitted explicitly
144
            if i < 1 and ismethod(func):
145
                continue
146
147
            if i < num_non_defaults:
148
                non_optional_params[arg] = (
149
                    doc_comment.param_dict.get(arg, cls.str_nodesc),
150
                    argspec.annotations.get(arg, None))
151
            else:
152
                optional_params[arg] = (
153
                    doc_comment.param_dict.get(arg, cls.str_nodesc) + " (" +
154
                    cls.str_optional.format(str(defaults[i-num_non_defaults]))
155
                    + ")",
156
                    argspec.annotations.get(arg, None),
157
                    defaults[i-num_non_defaults])
158
159
        return cls(name=func.__name__,
160
                   desc=doc_comment.desc,
161
                   retval_desc=doc_comment.retval_desc,
162
                   non_optional_params=non_optional_params,
163
                   optional_params=optional_params,
164
                   omit=omit)
165