Total Complexity | 60 |
Total Lines | 276 |
Duplicated Lines | 21.74 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like injectify.inspect_mate 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 | """ |
||
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): |
|
|
|||
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): |
|
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): |
|
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): |
|
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__) |
||
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 |