_SomethingMeta.__subclasscheck__()   B
last analyzed

Complexity

Conditions 7

Size

Total Lines 21
Code Lines 16

Duplication

Lines 21
Ratio 100 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nop 2
dl 21
loc 21
rs 8
c 0
b 0
f 0
1
import types
2
from collections import OrderedDict
3
from typing import Any, Dict, Callable, Tuple
4
5
from typish.classes._subscriptable_type import SubscriptableType
6
7
8 View Code Duplication
class _SomethingMeta(SubscriptableType):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
9
    """
10
    This metaclass is coupled to ``Something``.
11
    """
12
    def __instancecheck__(self, instance: object) -> bool:
13
        # Check if all attributes from self.signature are also present in
14
        # instance and also check that their types correspond.
15
        from typish.functions._instance_of import instance_of
16
17
        sig = self.signature()
18
        for key in sig:
19
            attr = getattr(instance, key, None)
20
            if not attr or not instance_of(attr, sig[key]):
21
                return False
22
        return True
23
24
    def __subclasscheck__(self, subclass: type) -> bool:
25
        # If an instance of type subclass is an instance of self, then subclass
26
        # is a sub class of self.
27
        from typish.functions._subclass_of import subclass_of
28
        from typish.functions._get_type_hints_of_callable import get_args_and_return_type
29
30
        self_sig = self.signature()
31
        other_sig = Something.like(subclass).signature()
32
        for attr in self_sig:
33
            if attr in other_sig:
34
                attr_sig = other_sig[attr]
35
                if (not isinstance(subclass.__dict__[attr], staticmethod)
36
                        and not isinstance(subclass.__dict__[attr], classmethod)
37
                        and subclass_of(attr_sig, Callable)):
38
                    # The attr must be a regular method or class method, so the
39
                    # first parameter should be ignored.
40
                    args, rt = get_args_and_return_type(attr_sig)
41
                    attr_sig = Callable[list(args[1:]), rt]
42
                if not subclass_of(attr_sig, self_sig[attr]):
43
                    return False
44
        return True
45
46
    def __eq__(self, other: 'Something') -> bool:
47
        return (isinstance(other, _SomethingMeta)
48
                and self.signature() == other.signature())
49
50
    def __repr__(self):
51
        sig = self.signature()
52
        sig_ = ', '.join(["'{}': {}".format(k, self._type_repr(sig[k]))
53
                          for k in sig])
54
        return 'typish.Something[{}]'.format(sig_)
55
56
    def __hash__(self):
57
        # This explicit super call is required for Python 3.5 and 3.6.
58
        return super.__hash__(self)
59
60
    def _type_repr(self, obj):
61
        """Return the repr() of an object, special-casing types (internal helper).
62
63
        If obj is a type, we return a shorter version than the default
64
        type.__repr__, based on the module and qualified name, which is
65
        typically enough to uniquely identify a type.  For everything
66
        else, we fall back on repr(obj).
67
        """
68
        if isinstance(obj, type) and not issubclass(obj, Callable):
69
            return obj.__qualname__
70
        if obj is ...:
71
            return '...'
72
        if isinstance(obj, types.FunctionType):
73
            return obj.__name__
74
        return repr(obj)
75
76
77 View Code Duplication
class Something(type, metaclass=_SomethingMeta):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
78
    """
79
    This class allows one to define an interface for something that has some
80
    attributes, such as objects or classes or maybe even modules.
81
    """
82
    @classmethod
83
    def signature(mcs) -> Dict[str, type]:
84
        """
85
        Return the signature of this ``Something`` as a dict.
86
        :return: a dict with attribute names as keys and types as values.
87
        """
88
        result = OrderedDict()
89
        args = mcs.__args__
90
        if isinstance(mcs.__args__, slice):
91
            args = (mcs.__args__,)
92
93
        arg_keys = sorted(args)
94
        if isinstance(mcs.__args__, dict):
95
            for key in arg_keys:
96
                result[key] = mcs.__args__[key]
97
        else:
98
            for slice_ in arg_keys:
99
                result[slice_.start] = slice_.stop
100
        return result
101
102
    def __getattr__(cls, item):
103
        # This method exists solely to fool the IDE into believing that
104
        # Something can have any attribute.
105
        return type.__getattr__(cls, item)  # pragma: no cover
106
107
    @staticmethod
108
    def like(obj: Any, exclude_privates: bool = True) -> 'Something':
109
        """
110
        Return a ``Something`` for the given ``obj``.
111
        :param obj: the object of which a ``Something`` is to be made.
112
        :param exclude_privates: if ``True``, private variables are excluded.
113
        :return: a ``Something`` that corresponds to ``obj``.
114
        """
115
        from typish.functions._get_type import get_type
116
117
        signature = {attr: get_type(getattr(obj, attr)) for attr in dir(obj)
118
                     if not exclude_privates or not attr.startswith('_')}
119
        return Something[signature]
120
121
122
TypingType = Something['__origin__': type, '__args__': Tuple[type, ...]]
123