| 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 |