typish.functions._subclass_of   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 159
Duplicated Lines 64.78 %

Importance

Changes 0
Metric Value
eloc 95
dl 103
loc 159
rs 10
c 0
b 0
f 0
wmc 29

9 Functions

Rating   Name   Duplication   Size   Complexity  
A _check_literal() 11 11 2
A subclass_of() 0 13 1
A _is_true_case() 0 4 1
B _subclass_of_generic() 33 33 8
A _forward_subclass_check() 23 23 4
A _subclass_of() 17 17 4
A _tuple_args() 0 7 3
A is_issubclass_case() 0 9 1
A _subclass_of_tuple() 19 19 5

How to fix   Duplicated Code   

Duplicated Code

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:

1
import typing
2
3
from typish._types import Unknown
4
from typish.functions._get_alias import get_alias
5
6
7
def subclass_of(cls: object, *args: object) -> bool:
8
    """
9
    Return whether ``cls`` is a subclass of all types in ``args`` while also
10
    considering generics.
11
12
    If you want the subclass check to be customized for your type, then make
13
    sure it has a __subclasscheck__ defined (not in a base class).
14
    :param cls: the subject.
15
    :param args: the super types.
16
    :return: True if ``cls`` is a subclass of all types in ``args`` while also
17
    considering generics.
18
    """
19
    return all(_subclass_of(cls, clsinfo) for clsinfo in args)
20
21
22 View Code Duplication
def _subclass_of(cls: type, clsinfo: object) -> bool:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
23
    # Check whether cls is a subtype of clsinfo.
24
    from typish.classes._literal import LiteralAlias
25
26
    # Translate to typing type if possible.
27
    clsinfo = get_alias(clsinfo) or clsinfo
28
29
    if _is_true_case(cls, clsinfo):
30
        result = True
31
    elif issubclass(clsinfo, LiteralAlias):
32
        return _check_literal(cls, subclass_of, clsinfo)
33
    elif is_issubclass_case(cls, clsinfo):
34
        result = issubclass(cls, clsinfo)
35
    else:
36
        result = _forward_subclass_check(cls, clsinfo)
37
38
    return result
39
40
41 View Code Duplication
def _forward_subclass_check(cls: type, clsinfo: type) -> bool:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
42
    # Forward the subclass check for cls and clsinfo to delegates that know how
43
    # to check that particular cls/clsinfo type.
44
45
    from typish.functions._get_origin import get_origin
46
    from typish.functions._get_args import get_args
47
48
    clsinfo_origin = get_origin(clsinfo)
49
    clsinfo_args = get_args(clsinfo)
50
    cls_origin = get_origin(cls)
51
52
    if cls_origin is typing.Union:
53
        # cls is a Union; all options of that Union must subclass clsinfo.
54
        cls_args = get_args(cls)
55
        result = all([subclass_of(elem, clsinfo) for elem in cls_args])
56
    elif clsinfo_args:
57
        result = _subclass_of_generic(cls, clsinfo_origin, clsinfo_args)
58
    else:
59
        try:
60
            result = issubclass(cls_origin, clsinfo_origin)
61
        except TypeError:
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
    cls_origin = get_origin(cls)
78
    cls_args = get_args(cls)
79
    if info_generic_type is tuple:
80
        # Special case.
81
        result = (subclass_of(cls_origin, tuple)
82
                  and _subclass_of_tuple(cls_args, info_args))
83
    elif info_generic_type is typing.Union:
84
        # Another special case.
85
        result = any(subclass_of(cls, cls_) for cls_ in info_args)
86
    elif cls_origin is tuple and info_generic_type is typing.Iterable:
87
        # Another special case.
88
        args = _tuple_args(cls_args)
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 (subclass_of(cls_origin, info_generic_type) and cls_args
94
          and len(cls_args) == len(info_args)):
95
        result = all(subclass_of(*tup) for tup in zip(cls_args, info_args))
96
    # Note that issubtype(list, List[...]) is always False.
97
    # Note that the number of arguments must be equal.
98
    return result
99
100
101 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...
102
        cls_args: typing.Tuple[type, ...],
103
        info_args: typing.Tuple[type, ...]) -> bool:
104
    from typish.functions._get_origin import get_origin
105
    from typish.functions._common_ancestor import common_ancestor_of_types
106
107
    result = False
108
    if len(info_args) == 2 and info_args[1] is ...:
109
        type_ = get_origin(info_args[0])
110
        if type_ is typing.Union:
111
            # A heterogeneous tuple: check each element if it subclasses the
112
            # union.
113
            result = all([subclass_of(elem, info_args[0]) for elem in cls_args])
114
        else:
115
            result = subclass_of(common_ancestor_of_types(*cls_args), info_args[0])
116
    elif len(cls_args) == len(info_args):
117
        result = all(subclass_of(c1, c2)
118
                     for c1, c2 in zip(cls_args, info_args))
119
    return result
120
121
122 View Code Duplication
def _check_literal(obj: object, func: typing.Callable, *args: type) -> bool:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
123
    # Instance or subclass check for Literal.
124
    literal = args[0]
125
    leftovers = args[1:]
126
    literal_args = getattr(literal, '__args__', None)
127
    result = False
128
    if literal_args:
129
        literal_arg = literal_args[0]
130
        result = (obj == literal_arg
131
                  and (not leftovers or func(obj, *leftovers)))
132
    return result
133
134
135
def _is_true_case(cls: type, clsinfo: type) -> bool:
136
    # Return whether subclass_of(cls, clsinfo) holds a case that must always be
137
    # True, without the need of further checking.
138
    return cls == clsinfo or cls is Unknown or clsinfo in (typing.Any, object)
139
140
141
def is_issubclass_case(cls: type, clsinfo: type) -> bool:
142
    # Return whether subclass_of(cls, clsinfo) holds a case that can be handled
143
    # by the builtin issubclass.
144
    from typish.functions._is_from_typing import is_from_typing
145
146
    return (not is_from_typing(clsinfo)
147
            and isinstance(cls, type)
148
            and clsinfo is not type
149
            and '__subclasscheck__' in dir(clsinfo))
150
151
152
def _tuple_args(
153
        cls_args: typing.Iterable[typing.Any]) -> typing.Iterable[type]:
154
    # Get the argument types from a tuple, even if the form is Tuple[int, ...].
155
    result = cls_args
156
    if len(cls_args) > 1 and cls_args[1] is ...:
157
        result = [cls_args[0]]
158
    return result
159