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

typish.functions._subclass_of._subclass_of()   B

Complexity

Conditions 6

Size

Total Lines 22
Code Lines 18

Duplication

Lines 22
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nop 2
dl 22
loc 22
rs 8.5666
c 0
b 0
f 0
1
import typing
2
3
from typish._types import Unknown
4
5
6 View Code Duplication
def subclass_of(cls: type, *args: type) -> bool:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
7
    """
8
    Return whether ``cls`` is a subclass of all types in ``args`` while also
9
    considering generics.
10
    :param cls: the subject.
11
    :param args: the super types.
12
    :return: True if ``cls`` is a subclass of all types in ``args`` while also
13
    considering generics.
14
    """
15
    from typish.classes._literal import LiteralAlias
16
17
    if args and issubclass(args[0], LiteralAlias):
18
        return _check_literal(cls, subclass_of, *args)
19
20
    if len(args) > 1:
21
        result = subclass_of(cls, args[0]) and subclass_of(cls, *args[1:])
22
    else:
23
        if args[0] == cls:
24
            return True
25
        result = _subclass_of(cls, args[0])
26
    return result
27
28
29 View Code Duplication
def _subclass_of(cls: type, clsinfo: type) -> bool:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
30
    from typish.functions._get_origin import get_origin
31
    from typish.functions._get_args import get_args
32
33
    # Check whether cls is a subtype of clsinfo.
34
    clsinfo_origin = get_origin(clsinfo)
35
    clsinfo_args = get_args(clsinfo)
36
    cls_origin = get_origin(cls)
37
    if cls is Unknown or clsinfo in (typing.Any, object):
38
        result = True
39
    elif cls_origin is typing.Union:
40
        # cls is a Union; all options of that Union must subclass clsinfo.
41
        cls_args = get_args(cls)
42
        result = all([subclass_of(elem, clsinfo) for elem in cls_args])
43
    elif clsinfo_args:
44
        result = _subclass_of_generic(cls, clsinfo_origin, clsinfo_args)
45
    else:
46
        try:
47
            result = issubclass(cls_origin, clsinfo_origin)
48
        except TypeError:
49
            result = False
50
    return result
51
52
53 View Code Duplication
def _subclass_of_union(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
54
        cls: type,
55
        info_args: typing.Tuple[type, ...]) -> bool:
56
    # Handle subclass_of(*, union)
57
    result = True
58
    for cls_ in info_args:
59
        if subclass_of(cls, cls_):
60
            break
61
    else:
62
        result = False
63
    return result
64
65
66 View Code Duplication
def _subclass_of_generic(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
67
        cls: type,
68
        info_generic_type: type,
69
        info_args: typing.Tuple[type, ...]) -> bool:
70
    # Check if cls is a subtype of info_generic_type, knowing that the latter
71
    # is a generic type.
72
73
    from typish.functions._get_origin import get_origin
74
    from typish.functions._get_args import get_args
75
76
    result = False
77
78
    cls_origin = get_origin(cls)
79
    cls_args = get_args(cls)
80
    if info_generic_type is tuple:
81
        # Special case.
82
        result = (subclass_of(cls_origin, tuple)
83
                  and _subclass_of_tuple(cls_args, info_args))
84
    elif cls_origin is tuple and info_generic_type is typing.Iterable:
85
        # Another special case.
86
        args = cls_args
87
        if len(args) > 1 and args[1] is ...:
88
            args = [args[0]]
89
90
        # Match the number of arguments of info to that of cls.
91
        matched_info_args = info_args * len(args)
92
        result = _subclass_of_tuple(args, matched_info_args)
93
    elif info_generic_type is typing.Union:
94
        # Another special case.
95
        result = _subclass_of_union(cls, info_args)
96
    elif (subclass_of(cls_origin, info_generic_type) and cls_args
97
            and len(cls_args) == len(info_args)):
98
        for tup in zip(cls_args, info_args):
99
            if not subclass_of(*tup):
100
                result = False
101
                break
102
        else:
103
            result = True
104
    # Note that issubtype(list, List[...]) is always False.
105
    # Note that the number of arguments must be equal.
106
    return result
107
108
109 View Code Duplication
def _subclass_of_tuple(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
110
        cls_args: typing.Tuple[type, ...],
111
        info_args: typing.Tuple[type, ...]) -> bool:
112
    from typish.functions._get_origin import get_origin
113
    from typish.functions._common_ancestor import common_ancestor_of_types
114
115
    result = False
116
    if len(info_args) == 2 and info_args[1] is ...:
117
        type_ = get_origin(info_args[0])
118
        if type_ is typing.Union:
119
            # A heterogeneous tuple: check each element if it subclasses the
120
            # union.
121
            result = all([subclass_of(elem, info_args[0]) for elem in cls_args])
122
        else:
123
            result = subclass_of(common_ancestor_of_types(*cls_args), info_args[0])
124
    elif len(cls_args) == len(info_args):
125
        for c1, c2 in zip(cls_args, info_args):
126
            if not subclass_of(c1, c2):
127
                break
128
        else:
129
            result = True
130
    return result
131
132
133
def _check_literal(obj: object, func: typing.Callable, *args: type) -> bool:
134
    # Instance or subclass check for Literal.
135
    literal = args[0]
136
    leftovers = args[1:]
137
    literal_args = getattr(literal, '__args__', None)
138
    if literal_args:
139
        literal_arg = literal_args[0]
140
        return obj == literal_arg and (not leftovers or func(obj, *leftovers))
141
    return False
142