Failed Conditions
Branch Makman2/conf-parser-auto-trim (6dfaa8)
by Mischa
01:32
created

coalib.misc.decorator()   C

Complexity

Conditions 7

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 7
dl 0
loc 21
rs 6.4706
1
from functools import total_ordering
2
import inspect
3
4
5
def yield_once(iterator):
6
    """
7
    Decorator to make an iterator yield each result only once.
8
9
    :param iterator: Any iterator
10
    :return:         An iterator that yields every result only once at most.
11
    """
12
    def yield_once_generator(*args, **kwargs):
13
        yielded = []
14
        for item in iterator(*args, **kwargs):
15
            if item in yielded:
16
                pass
17
            else:
18
                yielded.append(item)
19
                yield item
20
21
    return yield_once_generator
22
23
24
def _to_list(var):
25
    """
26
    Make variable to list.
27
28
    :param var: variable of any type
29
    :return:    list
30
    """
31
    if isinstance(var, list):
32
        return var
33
    elif var is None:
34
        return []
35
    elif isinstance(var, str) or isinstance(var, dict):
36
        # We dont want to make a list out of those via the default constructor
37
        return [var]
38
    else:
39
        try:
40
            return list(var)
41
        except TypeError:
42
            return [var]
43
44
45
def arguments_to_lists(function):
46
    """
47
    Decorator for a function that converts all arguments to lists.
48
49
    :param function: target function
50
    :return:         target function with only lists as parameters
51
    """
52
    def l_function(*args, **kwargs):
53
        l_args = [_to_list(arg) for arg in args]
54
        l_kwargs = {}
55
56
        for key, value in kwargs.items():
57
            l_kwargs[key] = _to_list(value)
58
        return function(*l_args, **l_kwargs)
59
60
    return l_function
61
62
63
def _get_member(obj, member):
64
    # If not found, pass AttributeError to invoking function.
65
    attribute = getattr(obj, member)
66
67
    if callable(attribute) and hasattr(attribute, "__self__"):
68
        # If the value is a bound method, invoke it like a getter and return
69
        # its value.
70
        try:
71
            return attribute()
72
        except TypeError:
73
            # Don't use repr() to display the member more accurately, because
74
            # invoking repr() on a bound method prints in this format:
75
            # <bound method CLASS.METHOD of **repr(instance)**>
76
            # This invokes repr() recursively.
77
            raise TypeError("Given bound method '" + member + "' must be "
78
                            "callable like a getter, taking no arguments.")
79
    else:
80
        # Otherwise it's a member variable or property (or any other attribute
81
        # that holds a value).
82
        return attribute
83
84
85
def _construct_repr_string(obj, members):
86
    # The passed entries have format (member-name, repr-function).
87
    values = ", ".join(member + "=" + func(_get_member(obj, member))
88
                       for member, func in members)
89
    return ("<" + type(obj).__name__ + " object(" + values + ") at "
90
            + hex(id(obj)) + ">")
91
92
93
def get_public_members(obj):
94
    """
95
    Retrieves a list of member-like objects (members or properties) that are
96
    publically exposed.
97
98
    :param obj: The object to probe.
99
    :return:    A list of strings.
100
    """
101
    # Get public members
102
    members = set(filter(lambda member: not member.startswith("_"),
103
                         obj.__dict__))
104
    # Also fetch properties
105
    type_dict = type(obj).__dict__
106
    members |= set(
107
        filter(lambda member: isinstance(type_dict[member], property)
108
                              and not member.startswith("_"), type_dict))
109
110
    return members
111
112
113
def generate_repr(*members):
114
    """
115
    Decorator that binds an auto-generated `__repr__()` function to a class.
116
117
    The generated `__repr__()` function prints in following format:
118
    <ClassName object(field1=1, field2='A string', field3=[1, 2, 3]) at 0xAAAA>
119
120
    Note that this decorator modifies the given class in place!
121
122
    :param members:         An iterable of member names to include into the
123
                            representation-string. Providing no members yields
124
                            to inclusion of all member variables and properties
125
                            in alphabetical order (except if they start with an
126
                            underscore).
127
128
                            To control the representation of each member, you
129
                            can also pass a tuple where the first element
130
                            contains the member to print and the second one the
131
                            representation function (which defaults to the
132
                            built-in `repr()`). Using None as representation
133
                            function is the same as using `repr()`.
134
135
                            Supported members are fields/variables, properties
136
                            and getter-like functions (functions that accept no
137
                            arguments).
138
    :raises ValueError:     Raised when the passed
139
                            (member, repr-function)-tuples have not a length of
140
                            2.
141
    :raises AttributeError: Raised when a given member/attribute was not found
142
                            in class.
143
    :raises TypeError:      Raised when a provided member is a bound method
144
                            that is not a getter-like function (means it must
145
                            accept no parameters).
146
    :return:                The class armed with an auto-generated __repr__
147
                            function.
148
    """
149
    def decorator(cls):
150
        cls.__repr__ = __repr__
151
        return cls
152
153
    if members:
154
        # Prepare members list.
155
        members_to_print = list(members)
156
        for i, member in enumerate(members_to_print):
157
            if isinstance(member, tuple):
158
                # Check tuple dimensions.
159
                length = len(member)
160
                if length == 2:
161
                    members_to_print[i] = (member[0],
162
                                           member[1] if member[1] else repr)
163
                else:
164
                    raise ValueError("Passed tuple " + repr(member) +
165
                                     " needs to be 2-dimensional, but has " +
166
                                     str(length) + " dimensions.")
167
            else:
168
                members_to_print[i] = (member, repr)
169
170
        def __repr__(self):
171
            return _construct_repr_string(self, members_to_print)
172
    else:
173
        def __repr__(self):
174
            # Need to fetch member variables every time since they are unknown
175
            # until class instantation.
176
            members_to_print = get_public_members(self)
177
178
            member_repr_list = ((member, repr) for member in
179
                sorted(members_to_print, key=str.lower))
180
181
            return _construct_repr_string(self, member_repr_list)
182
183
    return decorator
184
185
186
def generate_eq(*members):
187
    """
188
    Decorator that generates equality and inequality operators for the decorated
189
    class. The given members as well as the type of self and other will be taken
190
    into account.
191
192
    Note that this decorator modifies the given class in place!
193
194
    :param members: A list of members to compare for equality.
195
    """
196
    def decorator(cls):
197
        def eq(self, other):
198
            if type(other) is not type(self):
199
                return False
200
201
            return all(getattr(self, member) == getattr(other, member)
202
                       for member in members)
203
204
        def ne(self, other):
205
            return not eq(self, other)
206
207
        cls.__eq__ = eq
208
        cls.__ne__ = ne
209
        return cls
210
211
    return decorator
212
213
214
def generate_ordering(*members):
215
    """
216
    Decorator that generates ordering operators for the decorated class based on
217
    the given member names. All ordering except equality functions will raise a
218
    TypeError when a comparison with an unrelated class is attempted.
219
    (Comparisons with child classes will thus work fine with the capabilities
220
    of the base class as python will choose the base classes comparison operator
221
    in that case.)
222
223
    Note that this decorator modifies the given class in place!
224
225
    :param members: A list of members to compare, ordered from high priority to
226
                    low. I.e. if the first member is equal the second will be
227
                    taken for comparison and so on. If a member is None it is
228
                    considered smaller than any other value except None.
229
    """
230
    def decorator(cls):
231
        def lt(self, other):
232
            if not isinstance(other, cls):
233
                raise TypeError("Comparison with unrelated classes is "
234
                                "unsupported.")
235
236
            for member in members:
237
                if getattr(self, member) == getattr(other, member):
238
                    continue
239
240
                if (
241
                        getattr(self, member) is None or
242
                        getattr(other, member) is None):
243
                    return getattr(self, member) is None
244
245
                return getattr(self, member) < getattr(other, member)
246
247
            return False
248
249
        cls.__lt__ = lt
250
        return total_ordering(generate_eq(*members)(cls))
251
252
    return decorator
253
254
255
def _assert_right_type(value, types, argname):
256
    if isinstance(types, type) or types is None:
257
        types = (types,)
258
259
    for typ in types:
260
        if value == typ or (isinstance(typ, type) and isinstance(value, typ)):
261
            return
262
263
    raise TypeError("{} must be an instance of one of {} (provided value: "
264
                    "{})".format(argname, types, repr(value)))
265
266
267
def enforce_signature(function):
268
    """
269
    Enforces the signature of the function by throwing TypeError's if invalid
270
    arguments are provided. The return value is not checked.
271
272
    You can annotate any parameter of your function with the desired type or a
273
    tuple of allowed types. If you annotate the function with a value, this
274
    value only will be allowed (useful especially for None). Example:
275
276
    >>> @enforce_signature
277
    ... def test(arg: bool, another: (int, None)):
278
    ...     pass
279
    ...
280
    >>> test(True, 5)
281
    >>> test(True, None)
282
283
    Any string value for any parameter e.g. would then trigger a TypeError.
284
285
    :param function: The function to check.
286
    """
287
    argspec = inspect.getfullargspec(function)
288
    annotations = argspec.annotations
289
    argnames = argspec.args
290
291
    unnamed_annotations = {}
292
    for i, arg in enumerate(argnames):
293
        if arg in annotations:
294
            unnamed_annotations[i] = (annotations[arg], arg)
295
296
    def decorated(*args, **kwargs):
297
        for i, annotation in unnamed_annotations.items():
298
            if i < len(args):
299
                _assert_right_type(args[i], annotation[0], annotation[1])
300
301
        for argname, argval in kwargs.items():
302
            if argname in annotations:
303
                _assert_right_type(argval, annotations[argname], argname)
304
305
        return function(*args, **kwargs)
306
307
    return decorated
308