File: _something.py

package info (click to toggle)
python-typish 1.9.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 324 kB
  • sloc: python: 1,632; makefile: 2
file content (122 lines) | stat: -rw-r--r-- 4,767 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import types
from collections import OrderedDict
from typing import Any, Dict, Callable, Tuple

from typish.classes._subscriptable_type import SubscriptableType


class _SomethingMeta(SubscriptableType):
    """
    This metaclass is coupled to ``Something``.
    """
    def __instancecheck__(self, instance: object) -> bool:
        # Check if all attributes from self.signature are also present in
        # instance and also check that their types correspond.
        from typish.functions._instance_of import instance_of

        sig = self.signature()
        for key in sig:
            attr = getattr(instance, key, None)
            if not attr or not instance_of(attr, sig[key]):
                return False
        return True

    def __subclasscheck__(self, subclass: type) -> bool:
        # If an instance of type subclass is an instance of self, then subclass
        # is a sub class of self.
        from typish.functions._subclass_of import subclass_of
        from typish.functions._get_type_hints_of_callable import get_args_and_return_type

        self_sig = self.signature()
        other_sig = Something.like(subclass).signature()
        for attr in self_sig:
            if attr in other_sig:
                attr_sig = other_sig[attr]
                if (not isinstance(subclass.__dict__[attr], staticmethod)
                        and not isinstance(subclass.__dict__[attr], classmethod)
                        and subclass_of(attr_sig, Callable)):
                    # The attr must be a regular method or class method, so the
                    # first parameter should be ignored.
                    args, rt = get_args_and_return_type(attr_sig)
                    attr_sig = Callable[list(args[1:]), rt]
                if not subclass_of(attr_sig, self_sig[attr]):
                    return False
        return True

    def __eq__(self, other: 'Something') -> bool:
        return (isinstance(other, _SomethingMeta)
                and self.signature() == other.signature())

    def __repr__(self):
        sig = self.signature()
        sig_ = ', '.join(["'{}': {}".format(k, self._type_repr(sig[k]))
                          for k in sig])
        return 'typish.Something[{}]'.format(sig_)

    def __hash__(self):
        # This explicit super call is required for Python 3.5 and 3.6.
        return super.__hash__(self)

    def _type_repr(self, obj):
        """Return the repr() of an object, special-casing types (internal helper).

        If obj is a type, we return a shorter version than the default
        type.__repr__, based on the module and qualified name, which is
        typically enough to uniquely identify a type.  For everything
        else, we fall back on repr(obj).
        """
        if isinstance(obj, type) and not issubclass(obj, Callable):
            return obj.__qualname__
        if obj is ...:
            return '...'
        if isinstance(obj, types.FunctionType):
            return obj.__name__
        return repr(obj)


class Something(type, metaclass=_SomethingMeta):
    """
    This class allows one to define an interface for something that has some
    attributes, such as objects or classes or maybe even modules.
    """
    @classmethod
    def signature(mcs) -> Dict[str, type]:
        """
        Return the signature of this ``Something`` as a dict.
        :return: a dict with attribute names as keys and types as values.
        """
        result = OrderedDict()
        args = mcs.__args__
        if isinstance(mcs.__args__, slice):
            args = (mcs.__args__,)

        arg_keys = sorted(args)
        if isinstance(mcs.__args__, dict):
            for key in arg_keys:
                result[key] = mcs.__args__[key]
        else:
            for slice_ in arg_keys:
                result[slice_.start] = slice_.stop
        return result

    def __getattr__(cls, item):
        # This method exists solely to fool the IDE into believing that
        # Something can have any attribute.
        return type.__getattr__(cls, item)  # pragma: no cover

    @staticmethod
    def like(obj: Any, exclude_privates: bool = True) -> 'Something':
        """
        Return a ``Something`` for the given ``obj``.
        :param obj: the object of which a ``Something`` is to be made.
        :param exclude_privates: if ``True``, private variables are excluded.
        :return: a ``Something`` that corresponds to ``obj``.
        """
        from typish.functions._get_type import get_type

        signature = {attr: get_type(getattr(obj, attr)) for attr in dir(obj)
                     if not exclude_privates or not attr.startswith('_')}
        return Something[signature]


TypingType = Something['__origin__': type, '__args__': Tuple[type, ...]]