| Total Complexity | 61 | 
| Total Lines | 317 | 
| Duplicated Lines | 84.23 % | 
| 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 typish._classes 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 | PRIVATE MODULE: do not import (from) it directly. | ||
| 3 | |||
| 4 | This module contains class implementations. | ||
| 5 | """ | ||
| 6 | import inspect | ||
| 7 | import types | ||
| 8 | from collections import OrderedDict | ||
| 9 | from typing import Any, Callable, Dict, Tuple, Optional, Union, List, Iterable | ||
| 10 | |||
| 11 | from typish._types import Empty | ||
| 12 | from typish._functions import ( | ||
| 13 | get_type, | ||
| 14 | subclass_of, | ||
| 15 | instance_of, | ||
| 16 | get_args_and_return_type, | ||
| 17 | is_type_annotation, | ||
| 18 | ) | ||
| 19 | |||
| 20 | |||
| 21 | class _SubscribedType(type): | ||
| 22 | """ | ||
| 23 | This class is a placeholder to let the IDE know the attributes of the | ||
| 24 | returned type after a __getitem__. | ||
| 25 | """ | ||
| 26 | __origin__ = None | ||
| 27 | __args__ = None | ||
| 28 | |||
| 29 | |||
| 30 | View Code Duplication | class SubscriptableType(type): | |
|  | |||
| 31 | """ | ||
| 32 | This metaclass will allow a type to become subscriptable. | ||
| 33 | |||
| 34 | >>> class SomeType(metaclass=SubscriptableType): | ||
| 35 | ... pass | ||
| 36 | >>> SomeTypeSub = SomeType['some args'] | ||
| 37 | >>> SomeTypeSub.__args__ | ||
| 38 | 'some args' | ||
| 39 | >>> SomeTypeSub.__origin__.__name__ | ||
| 40 | 'SomeType' | ||
| 41 | """ | ||
| 42 | def __init_subclass__(mcs, **kwargs): | ||
| 43 | mcs._hash = None | ||
| 44 | mcs.__args__ = None | ||
| 45 | mcs.__origin__ = None | ||
| 46 | |||
| 47 | def __getitem__(self, item) -> _SubscribedType: | ||
| 48 |         body = { | ||
| 49 | **self.__dict__, | ||
| 50 | '__args__': item, | ||
| 51 | '__origin__': self, | ||
| 52 | } | ||
| 53 | bases = self, *self.__bases__ | ||
| 54 | result = type(self.__name__, bases, body) | ||
| 55 | if hasattr(result, '_after_subscription'): | ||
| 56 | # TODO check if _after_subscription is static | ||
| 57 | result._after_subscription(item) | ||
| 58 | return result | ||
| 59 | |||
| 60 | def __eq__(self, other): | ||
| 61 | self_args = getattr(self, '__args__', None) | ||
| 62 | self_origin = getattr(self, '__origin__', None) | ||
| 63 | other_args = getattr(other, '__args__', None) | ||
| 64 | other_origin = getattr(other, '__origin__', None) | ||
| 65 | return self_args == other_args and self_origin == other_origin | ||
| 66 | |||
| 67 | def __hash__(self): | ||
| 68 | if not getattr(self, '_hash', None): | ||
| 69 |             self._hash = hash('{}{}'.format(self.__origin__, self.__args__)) | ||
| 70 | return self._hash | ||
| 71 | |||
| 72 | |||
| 73 | View Code Duplication | class _SomethingMeta(SubscriptableType): | |
| 74 | """ | ||
| 75 | This metaclass is coupled to ``Interface``. | ||
| 76 | """ | ||
| 77 | def __instancecheck__(self, instance: object) -> bool: | ||
| 78 | # Check if all attributes from self.signature are also present in | ||
| 79 | # instance and also check that their types correspond. | ||
| 80 | sig = self.signature() | ||
| 81 | for key in sig: | ||
| 82 | attr = getattr(instance, key, None) | ||
| 83 | if not attr or not instance_of(attr, sig[key]): | ||
| 84 | return False | ||
| 85 | return True | ||
| 86 | |||
| 87 | def __subclasscheck__(self, subclass: type) -> bool: | ||
| 88 | # If an instance of type subclass is an instance of self, then subclass | ||
| 89 | # is a sub class of self. | ||
| 90 | self_sig = self.signature() | ||
| 91 | other_sig = Something.like(subclass).signature() | ||
| 92 | for attr in self_sig: | ||
| 93 | if attr in other_sig: | ||
| 94 | attr_sig = other_sig[attr] | ||
| 95 | if (not isinstance(subclass.__dict__[attr], staticmethod) | ||
| 96 | and not isinstance(subclass.__dict__[attr], classmethod) | ||
| 97 | and subclass_of(attr_sig, Callable)): | ||
| 98 | # The attr must be a regular method or class method, so the | ||
| 99 | # first parameter should be ignored. | ||
| 100 | args, rt = get_args_and_return_type(attr_sig) | ||
| 101 | attr_sig = Callable[list(args[1:]), rt] | ||
| 102 | if not subclass_of(attr_sig, self_sig[attr]): | ||
| 103 | return False | ||
| 104 | return True | ||
| 105 | |||
| 106 | def __eq__(self, other: 'Something') -> bool: | ||
| 107 | return (isinstance(other, _SomethingMeta) | ||
| 108 | and self.signature() == other.signature()) | ||
| 109 | |||
| 110 | def __repr__(self): | ||
| 111 | sig = self.signature() | ||
| 112 |         sig_ = ', '.join(["'{}': {}".format(k, self._type_repr(sig[k])) | ||
| 113 | for k in sig]) | ||
| 114 |         return 'typish.Something[{}]'.format(sig_) | ||
| 115 | |||
| 116 | def __hash__(self): | ||
| 117 | # This explicit super call is required for Python 3.5 and 3.6. | ||
| 118 | return super.__hash__(self) | ||
| 119 | |||
| 120 | def _type_repr(self, obj): | ||
| 121 | """Return the repr() of an object, special-casing types (internal helper). | ||
| 122 | |||
| 123 | If obj is a type, we return a shorter version than the default | ||
| 124 | type.__repr__, based on the module and qualified name, which is | ||
| 125 | typically enough to uniquely identify a type. For everything | ||
| 126 | else, we fall back on repr(obj). | ||
| 127 | """ | ||
| 128 | if isinstance(obj, type) and not issubclass(obj, Callable): | ||
| 129 | if obj.__module__ == 'builtins': | ||
| 130 | return obj.__qualname__ | ||
| 131 |             return '{}.{}'.format(obj.__module__, obj.__qualname__) | ||
| 132 | if obj is ...: | ||
| 133 | return '...' | ||
| 134 | if isinstance(obj, types.FunctionType): | ||
| 135 | return obj.__name__ | ||
| 136 | return repr(obj) | ||
| 137 | |||
| 138 | |||
| 139 | View Code Duplication | class Something(type, metaclass=_SomethingMeta): | |
| 140 | """ | ||
| 141 | This class allows one to define an interface for something that has some | ||
| 142 | attributes, such as objects or classes or maybe even modules. | ||
| 143 | """ | ||
| 144 | @classmethod | ||
| 145 | def signature(mcs) -> Dict[str, type]: | ||
| 146 | """ | ||
| 147 | Return the signature of this ``Something`` as a dict. | ||
| 148 | :return: a dict with attribute names as keys and types as values. | ||
| 149 | """ | ||
| 150 | result = OrderedDict() | ||
| 151 | args = mcs.__args__ | ||
| 152 | if isinstance(mcs.__args__, slice): | ||
| 153 | args = (mcs.__args__,) | ||
| 154 | |||
| 155 | arg_keys = sorted(args) | ||
| 156 | if isinstance(mcs.__args__, dict): | ||
| 157 | for key in arg_keys: | ||
| 158 | result[key] = mcs.__args__[key] | ||
| 159 | else: | ||
| 160 | for slice_ in arg_keys: | ||
| 161 | result[slice_.start] = slice_.stop | ||
| 162 | return result | ||
| 163 | |||
| 164 | def __getattr__(cls, item): | ||
| 165 | # This method exists solely to fool the IDE into believing that | ||
| 166 | # Something can have any attribute. | ||
| 167 | return type.__getattr__(cls, item) | ||
| 168 | |||
| 169 | @staticmethod | ||
| 170 | def like(obj: Any, exclude_privates: bool = True) -> 'Something': | ||
| 171 | """ | ||
| 172 | Return a ``Something`` for the given ``obj``. | ||
| 173 | :param obj: the object of which a ``Something`` is to be made. | ||
| 174 | :param exclude_privates: if ``True``, private variables are excluded. | ||
| 175 | :return: a ``Something`` that corresponds to ``obj``. | ||
| 176 | """ | ||
| 177 |         signature = {attr: get_type(getattr(obj, attr)) for attr in dir(obj) | ||
| 178 |                      if not exclude_privates or not attr.startswith('_')} | ||
| 179 | return Something[signature] | ||
| 180 | |||
| 181 | |||
| 182 | View Code Duplication | class ClsDict(OrderedDict): | |
| 183 | """ | ||
| 184 | ClsDict is a dict that accepts (only) types as keys and will return its | ||
| 185 | values depending on instance checks rather than equality checks. | ||
| 186 | """ | ||
| 187 | def __new__(cls, *args, **kwargs): | ||
| 188 | """ | ||
| 189 | Construct a new instance of ``ClsDict``. | ||
| 190 | :param args: a dict. | ||
| 191 | :param kwargs: any kwargs that ``dict`` accepts. | ||
| 192 | :return: a ``ClsDict``. | ||
| 193 | """ | ||
| 194 | if len(args) > 1: | ||
| 195 |             raise TypeError('TypeDict accepts only one positional argument, ' | ||
| 196 | 'which must be a dict.') | ||
| 197 | if args and not isinstance(args[0], dict): | ||
| 198 |             raise TypeError('TypeDict accepts only a dict as positional ' | ||
| 199 | 'argument.') | ||
| 200 | if not all([is_type_annotation(key) for key in args[0]]): | ||
| 201 |             raise TypeError('The given dict must only hold types as keys.') | ||
| 202 | return super().__new__(cls, args[0], **kwargs) | ||
| 203 | |||
| 204 | def __getitem__(self, item: Any) -> Any: | ||
| 205 | """ | ||
| 206 | Return the value of the first encounter of a key for which | ||
| 207 | ``is_instance(item, key)`` holds ``True``. | ||
| 208 | :param item: any item. | ||
| 209 | :return: the value of which the type corresponds with item. | ||
| 210 | """ | ||
| 211 | item_type = get_type(item, use_union=True) | ||
| 212 | for key, value in self.items(): | ||
| 213 | if subclass_of(item_type, key): | ||
| 214 | return value | ||
| 215 |         raise KeyError('No match for {}'.format(item)) | ||
| 216 | |||
| 217 | def get(self, item: Any, default: Any = None) -> Optional[Any]: | ||
| 218 | try: | ||
| 219 | return self.__getitem__(item) | ||
| 220 | except KeyError: | ||
| 221 | return default | ||
| 222 | |||
| 223 | |||
| 224 | View Code Duplication | class ClsFunction: | |
| 225 | """ | ||
| 226 | ClsDict is a callable that takes a ClsDict or a dict. When called, it uses | ||
| 227 | the first argument to check for the right function in its body, executes it | ||
| 228 | and returns the result. | ||
| 229 | """ | ||
| 230 | def __init__(self, body: Union[ClsDict, dict, Iterable[Callable]]): | ||
| 231 | if isinstance(body, ClsDict): | ||
| 232 | self.body = body | ||
| 233 | elif isinstance(body, dict): | ||
| 234 | self.body = ClsDict(body) | ||
| 235 | elif instance_of(body, Iterable[Callable]): | ||
| 236 | list_of_tuples = [] | ||
| 237 | for func in body: | ||
| 238 | signature = inspect.signature(func) | ||
| 239 | params = list(signature.parameters.keys()) | ||
| 240 | if not params: | ||
| 241 |                     raise TypeError('ClsFunction expects callables that take ' | ||
| 242 |                                     'at least one parameter, {} does not.' | ||
| 243 | .format(func.__name__)) | ||
| 244 | first_param = signature.parameters[params[0]] | ||
| 245 | hint = first_param.annotation | ||
| 246 | key = Any if hint == Empty else hint | ||
| 247 | list_of_tuples.append((key, func)) | ||
| 248 | self.body = ClsDict(OrderedDict(list_of_tuples)) | ||
| 249 | else: | ||
| 250 |             raise TypeError('ClsFunction expects a ClsDict or a dict that can ' | ||
| 251 | 'be turned to a ClsDict or an iterable of ' | ||
| 252 | 'callables.') | ||
| 253 | |||
| 254 | if not all(isinstance(value, Callable) for value in self.body.values()): | ||
| 255 |             raise TypeError('ClsFunction expects a dict or ClsDict with only ' | ||
| 256 | 'callables as values.') | ||
| 257 | |||
| 258 | def understands(self, item: Any) -> bool: | ||
| 259 | """ | ||
| 260 | Check to see if this ClsFunction can take item. | ||
| 261 | :param item: the item that is checked. | ||
| 262 | :return: True if this ClsFunction can take item. | ||
| 263 | """ | ||
| 264 | try: | ||
| 265 | self.body[item] | ||
| 266 | return True | ||
| 267 | except KeyError: | ||
| 268 | return False | ||
| 269 | |||
| 270 | def __call__(self, *args, **kwargs): | ||
| 271 | if not args: | ||
| 272 |             raise TypeError('ClsFunction must be called with at least 1 ' | ||
| 273 | 'positional argument.') | ||
| 274 | callable_ = self.body[args[0]] | ||
| 275 | try: | ||
| 276 | return callable_(*args, **kwargs) | ||
| 277 | except TypeError as err: | ||
| 278 |             raise TypeError('Unable to call function for \'{}\': {}' | ||
| 279 | .format(args[0], err.args[0])) | ||
| 280 | |||
| 281 | |||
| 282 | View Code Duplication | class _LiteralMeta(SubscriptableType): | |
| 283 | """ | ||
| 284 | A Metaclass that exists to serve Literal and alter the __args__ attribute. | ||
| 285 | """ | ||
| 286 | def __getattribute__(cls, item): | ||
| 287 | """ | ||
| 288 | This method makes sure that __args__ is a tuple, like with | ||
| 289 | typing.Literal. | ||
| 290 | :param item: the name of the attribute that is obtained. | ||
| 291 | :return: the attribute. | ||
| 292 | """ | ||
| 293 | if item == '__args__': | ||
| 294 | try: | ||
| 295 | result = SubscriptableType.__getattribute__(cls, item), | ||
| 296 | except AttributeError: | ||
| 297 | # In case of Python 3.5 | ||
| 298 | result = tuple() | ||
| 299 | elif item == '__origin__': | ||
| 300 | result = 'Literal' | ||
| 301 | else: | ||
| 302 | result = SubscriptableType.__getattribute__(cls, item) | ||
| 303 | return result | ||
| 304 | |||
| 305 | def __instancecheck__(self, instance): | ||
| 306 | return self.__args__ and self.__args__[0] == instance | ||
| 307 | |||
| 308 | |||
| 309 | class Literal(metaclass=_LiteralMeta): | ||
| 310 | """ | ||
| 311 | This is a backwards compatible variant of typing.Literal (Python 3.8+). | ||
| 312 | """ | ||
| 313 | _name = 'Literal' | ||
| 314 | |||
| 315 | |||
| 316 | TypingType = Something['__origin__': type, '__args__': Tuple[type, ...]] | ||
| 317 |