Passed
Push — master ( c1b59e...b3bb01 )
by Mitchell
03:13
created

injectify.inspect_mate.is_class_method()   B

Complexity

Conditions 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 19
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nop 3
dl 19
loc 19
rs 8.6666
c 0
b 0
f 0
1
"""
2
``inspect_mate`` provides more methods to get information about class attribute
3
than the standard library ``inspect``.
4
5
Includes tester function to check:
6
7
- is regular attribute
8
- is property style method
9
- is regular method, example: ``def method(self, *args, **kwargs)``
10
- is static method
11
- is class method
12
13
These are 5-kind class attributes.
14
15
and getter function to get each kind of class attributes of a class.
16
"""
17
18
import ast
19
import inspect
20
import functools
21
import linecache
22
from collections import deque
23
from types import FunctionType
24
25
from .exceptions import ClassFoundException
26
27
28
def is_attribute(klass, attr, value=None):
29
    """Test if a value of a class is attribute. (Not a @property style
30
    attribute)
31
32
    Args:
33
        klass: The class.
34
        attr: Attribute name.
35
        value: Attribute value.
36
    """
37
    if value is None:
38
        value = getattr(klass, attr)
39
    assert getattr(klass, attr) == value
40
41
    if not inspect.isroutine(value):
42
        if not isinstance(value, property):
43
            return True
44
    return False
45
46
47
def is_property_method(klass, attr, value=None):
48
    """Test if a value of a class is @property style attribute.
49
50
    Args:
51
        klass: The class.
52
        attr: Attribute name.
53
        value: Attribute value.
54
    """
55
    if value is None:
56
        value = getattr(klass, attr)
57
    assert getattr(klass, attr) == value
58
59
    if not inspect.isroutine(value):
60
        if isinstance(value, property):
61
            return True
62
    return False
63
64
65
def is_regular_method(klass, attr, value=None):
66
    """Test if a value of a class is regular method.
67
68
    Args:
69
        klass: The class.
70
        attr: Attribute name.
71
        value: Attribute value.
72
    """
73
    if value is None:
74
        value = getattr(klass, attr)
75
    assert getattr(klass, attr) == value
76
77
    if inspect.isroutine(value):
78
        if not is_static_method(klass, attr, value) \
79
                and not is_class_method(klass, attr, value):
80
            return True
81
82
    return False
83
84
85 View Code Duplication
def is_static_method(klass, attr, value=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
86
    """Test if a value of a class is static method.
87
88
    Args:
89
        klass: The class.
90
        attr: Attribute name.
91
        value: Attribute value.
92
    """
93
    if value is None:
94
        value = getattr(klass, attr)
95
    assert getattr(klass, attr) == value
96
97
    for cls in inspect.getmro(klass):
98
        if inspect.isroutine(value):
99
            if attr in cls.__dict__:
100
                binded_value = cls.__dict__[attr]
101
                if isinstance(binded_value, staticmethod):
102
                    return True
103
    return False
104
105
106 View Code Duplication
def is_class_method(klass, attr, value=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
107
    """Test if a value of a class is class method.
108
109
    Args:
110
        klass: The class.
111
        attr: Attribute name.
112
        value: Attribute value.
113
    """
114
    if value is None:
115
        value = getattr(klass, attr)
116
    assert getattr(klass, attr) == value
117
118
    for cls in inspect.getmro(klass):
119
        if inspect.isroutine(value):
120
            if attr in cls.__dict__:
121
                binded_value = cls.__dict__[attr]
122
                if isinstance(binded_value, classmethod):
123
                    return True
124
    return False
125
126
127
def _get_members(klass, tester_func, return_builtin):
128
    """
129
130
    Args:
131
        klass: The class.
132
        tester_func: Function to test.
133
        allow_builtin: If ``False``, built-in variable or method such as
134
            ``__name__``, ``__init__`` will not be returned.
135
    """
136
    if not inspect.isclass(klass):
137
        raise ValueError
138
139
    pairs = list()
140
    for attr, value in inspect.getmembers(klass):
141
        if tester_func(klass, attr, value):
142
            if return_builtin:
143
                pairs.append((attr, value))
144
            else:
145
                if not (attr.startswith('__') or attr.endswith('__')):
146
                    pairs.append((attr, value))
147
148
    return pairs
149
150
151
get_attributes = functools.partial(
152
    _get_members, tester_func=is_attribute, return_builtin=False)
153
get_attributes.__doc__ = 'Get all class attributes members.'
154
155
get_property_methods = functools.partial(
156
    _get_members, tester_func=is_property_method, return_builtin=False)
157
get_property_methods.__doc__ = 'Get all property style attributes members.'
158
159
get_regular_methods = functools.partial(
160
    _get_members, tester_func=is_regular_method, return_builtin=False)
161
get_regular_methods.__doc__ = 'Get all non static and class method members'
162
163
get_static_methods = functools.partial(
164
    _get_members, tester_func=is_static_method, return_builtin=False)
165
get_static_methods.__doc__ = 'Get all static method attributes members.'
166
167
get_class_methods = functools.partial(
168
    _get_members, tester_func=is_class_method, return_builtin=False)
169
get_class_methods.__doc__ = 'Get all class method attributes members.'
170
171
172 View Code Duplication
def get_all_attributes(klass):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
173
    """Get all attribute members (attribute, property style method)."""
174
    if not inspect.isclass(klass):
175
        raise ValueError
176
177
    pairs = list()
178
    for attr, value in inspect.getmembers(
179
            klass, lambda x: not inspect.isroutine(x)):
180
        if not (attr.startswith('__') or attr.endswith('__')):
181
            pairs.append((attr, value))
182
    return pairs
183
184
185 View Code Duplication
def get_all_methods(klass):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
186
    """Get all method members (regular, static, class method)."""
187
    if not inspect.isclass(klass):
188
        raise ValueError
189
190
    pairs = list()
191
    for attr, value in inspect.getmembers(
192
            klass, lambda x: inspect.isroutine(x)):
193
        if not (attr.startswith('__') or attr.endswith('__')):
194
            pairs.append((attr, value))
195
    return pairs
196
197
198
def extract_wrapped(decorated):
199
    """Extract a wrapped method."""
200
    try:
201
        closure = (c.cell_contents for c in decorated.__closure__)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable c does not seem to be defined.
Loading history...
202
        return next((c for c in closure if isinstance(c, FunctionType)), None)
203
    except (TypeError, AttributeError):
204
        return
205
206
207
class _ClassFinder(ast.NodeVisitor):
208
209
    def __init__(self, qualname):
210
        self.stack = deque()
211
        self.qualname = qualname
212
213
    def visit_FunctionDef(self, node):
214
        self.stack.append(node.name)
215
        self.stack.append('<locals>')
216
        self.generic_visit(node)
217
        self.stack.pop()
218
        self.stack.pop()
219
220
    visit_AsyncFunctionDef = visit_FunctionDef
221
222
    def visit_ClassDef(self, node):
223
        self.stack.append(node.name)
224
        if self.qualname == '.'.join(self.stack):
225
            # Return the decorator for the class if present
226
            if node.decorator_list:
227
                line_number = node.decorator_list[0].lineno
228
            else:
229
                line_number = node.lineno
230
231
            # decrement by one since lines starts with indexing by zero
232
            line_number -= 1
233
            raise ClassFoundException(line_number)
234
        self.generic_visit(node)
235
        self.stack.pop()
236
237
238
def getsource(obj):
239
    """Get the source code of an object."""
240
    if inspect.isclass(obj):
241
        # From Python 3.9 inspect library
242
        obj = inspect.unwrap(obj)
243
        file = inspect.getsourcefile(obj)
244
        if file:
245
            # Invalidate cache if needed.
246
            linecache.checkcache(file)
247
        else:
248
            file = inspect.getfile(obj)
249
            # Allow filenames in form of "<something>" to pass through.
250
            # `doctest` monkeypatches `linecache` module to enable
251
            # inspection, so let `linecache.getlines` to be called.
252
            if not (file.startswith('<') and file.endswith('>')):
253
                raise OSError('source code not available')
254
255
        module = inspect.getmodule(obj, file)
256
        if module:
257
            lines = linecache.getlines(file, module.__dict__)
258
        else:
259
            lines = linecache.getlines(file)
260
        if not lines:
261
            raise OSError('could not get source code')
262
263
        qualname = obj.__qualname__
264
        source = ''.join(lines)
265
        tree = ast.parse(source)
266
        class_finder = _ClassFinder(qualname)
267
        try:
268
            class_finder.visit(tree)
269
        except ClassFoundException as e:
270
            line_number = e.args[0]
271
            return ''.join(inspect.getblock(lines[line_number:]))
272
        else:
273
            raise OSError('could not find class definition')
274
    else:
275
        return getattr(obj, '__inject_code__', inspect.getsource(obj))
276