Passed
Push — master ( c3c399...6aa0ae )
by Ramon
55s queued 10s
created

_SomethingMeta.__subclasscheck__()   B

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 ``Interface``.
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
            if obj.__module__ == 'builtins':
70
                return obj.__qualname__
71
            return '{}.{}'.format(obj.__module__, obj.__qualname__)
72
        if obj is ...:
73
            return '...'
74
        if isinstance(obj, types.FunctionType):
75
            return obj.__name__
76
        return repr(obj)
77
78
79 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...
80
    """
81
    This class allows one to define an interface for something that has some
82
    attributes, such as objects or classes or maybe even modules.
83
    """
84
    @classmethod
85
    def signature(mcs) -> Dict[str, type]:
86
        """
87
        Return the signature of this ``Something`` as a dict.
88
        :return: a dict with attribute names as keys and types as values.
89
        """
90
        result = OrderedDict()
91
        args = mcs.__args__
92
        if isinstance(mcs.__args__, slice):
93
            args = (mcs.__args__,)
94
95
        arg_keys = sorted(args)
96
        if isinstance(mcs.__args__, dict):
97
            for key in arg_keys:
98
                result[key] = mcs.__args__[key]
99
        else:
100
            for slice_ in arg_keys:
101
                result[slice_.start] = slice_.stop
102
        return result
103
104
    def __getattr__(cls, item):
105
        # This method exists solely to fool the IDE into believing that
106
        # Something can have any attribute.
107
        return type.__getattr__(cls, item)
108
109
    @staticmethod
110
    def like(obj: Any, exclude_privates: bool = True) -> 'Something':
111
        """
112
        Return a ``Something`` for the given ``obj``.
113
        :param obj: the object of which a ``Something`` is to be made.
114
        :param exclude_privates: if ``True``, private variables are excluded.
115
        :return: a ``Something`` that corresponds to ``obj``.
116
        """
117
        from typish.functions._get_type import get_type
118
119
        signature = {attr: get_type(getattr(obj, attr)) for attr in dir(obj)
120
                     if not exclude_privates or not attr.startswith('_')}
121
        return Something[signature]
122
123
124
TypingType = Something['__origin__': type, '__args__': Tuple[type, ...]]
125