typeddfs.frozen_types   F
last analyzed

Complexity

Total Complexity 78

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 201
dl 0
loc 334
rs 2.16
c 0
b 0
f 0
wmc 78

51 Methods

Rating   Name   Duplication   Size   Complexity  
A FrozeList.__init__() 0 6 3
A FrozeList.__repr__() 0 2 1
A FrozeList.__iter__() 0 2 1
A FrozeList.__len__() 0 2 1
A FrozeList.__hash__() 0 2 1
A FrozeList.__getitem__() 0 2 1
A FrozeList.is_empty() 0 3 1
A FrozeList.get() 0 4 2
A FrozeList.length() 0 3 1
A FrozeList.__lt__() 0 2 1
A FrozeList.__str__() 0 2 1
A FrozeList.to_list() 0 2 1
A FrozeList.__eq__() 0 2 1
A FrozeSet.__lt__() 0 11 1
A FrozeSet.to_set() 0 2 1
A FrozeList.__make_other() 0 9 4
A FrozeDict.get() 0 2 1
A FrozeDict.__eq__() 0 9 4
A FrozeDict.__make_other() 0 12 4
A FrozeSet.__make_other() 0 9 4
A FrozeList.req() 0 14 3
A FrozeSet.__hash__() 0 2 1
A FrozeDict.is_empty() 0 3 1
A FrozeDict.__lt__() 0 22 3
A FrozeSet.__repr__() 0 2 1
A FrozeDict.__iter__() 0 2 1
A FrozeSet.get() 0 4 2
A FrozeSet.to_frozenset() 0 2 1
A FrozeDict.items() 0 2 1
A FrozeDict.__repr__() 0 2 1
A FrozeDict.__str__() 0 2 1
A FrozeSet.__eq__() 0 2 1
A FrozeSet.length() 0 3 1
A FrozeDict.length() 0 3 1
A FrozeDict.req() 0 12 2
A FrozeDict.__contains__() 0 2 1
A FrozeSet.__iter__() 0 2 1
A FrozeDict.__hash__() 0 2 1
A FrozeDict.__init__() 0 3 2
A FrozeDict.__len__() 0 2 1
A FrozeSet.__getitem__() 0 5 2
A FrozeDict.__getitem__() 0 2 1
A FrozeDict.keys() 0 2 1
A FrozeSet.__len__() 0 2 1
A FrozeSet.__str__() 0 2 1
A FrozeSet.req() 0 15 3
A FrozeSet.is_empty() 0 3 1
A FrozeSet.__init__() 0 8 3
A FrozeDict.to_dict() 0 2 1
A FrozeDict.values() 0 2 1
A FrozeSet.__contains__() 0 2 1

How to fix   Complexity   

Complexity

Complex classes like typeddfs.frozen_types often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# SPDX-License-Identifier Apache-2.0
2
# Source: https://github.com/dmyersturnbull/typed-dfs
3
#
4
"""
5
Hashable and ordered collections.
6
"""
7
from __future__ import annotations
8
9
import functools
10
from collections.abc import (
11
    Hashable,
12
    Iterator,
13
    Mapping,
14
    MutableMapping,
15
    Sequence,
16
    ValuesView,
17
)
18
from typing import TypeVar
19
20
T_co = TypeVar("T_co", covariant=True)
21
K_contra = TypeVar("K_contra", contravariant=True)
22
V_co = TypeVar("V_co", covariant=True)
23
24
25
@functools.total_ordering
26
class FrozeList(Sequence[T_co], Hashable):
27
    """
28
    An immutable list.
29
    Hashable and ordered.
30
    """
31
32
    EMPTY: FrozeList = NotImplemented  # delayed
33
34
    def __init__(self, lst: Sequence[T_co]) -> None:
35
        self.__lst = lst if isinstance(lst, list) else list(lst)
36
        try:
37
            self.__hash = hash(tuple(lst))
38
        except AttributeError:
39
            self.__hash = 0
40
41
    @property
42
    def is_empty(self) -> bool:  # pragma: no cover
43
        return len(self.__lst) == 0
44
45
    @property
46
    def length(self) -> int:  # pragma: no cover
47
        return len(self.__lst)
48
49
    def __iter__(self) -> Iterator[T_co]:  # pragma: no cover
50
        return iter(self.__lst)
51
52
    def __getitem__(self, item: int):  # pragma: no cover
53
        return self.__lst[item]
54
55
    def __hash__(self) -> int:
56
        return self.__hash
57
58
    def __eq__(self, other: FrozeList[T_co] | Sequence[T_co]) -> bool:
59
        return self.__lst == self.__make_other(other)
60
61
    def __lt__(self, other: FrozeList[T_co] | Sequence[T_co]):
62
        return self.__lst < self.__make_other(other)
63
64
    def __len__(self) -> int:
65
        return len(self.__lst)
66
67
    def __str__(self) -> str:
68
        return str(self.__lst)
69
70
    def __repr__(self) -> str:
71
        return repr(self.__lst)
72
73
    def to_list(self) -> list[T_co]:
74
        return list(self.__lst)
75
76
    def get(self, item: T_co, default: T_co | None = None) -> T_co | None:
77
        if item in self.__lst:
78
            return item
79
        return default
80
81
    def req(self, item: T_co, default: T_co | None = None) -> T_co:
82
        """
83
        Returns the requested list item, falling back to a default.
84
        Short for "require".
85
86
        Raise:
87
            KeyError: If ``item`` is not in this list and ``default`` is ``None``
88
        """
89
        if item in self.__lst:
90
            return item
91
        if default is None:
92
            msg = f"Item {item} not found"
93
            raise KeyError(msg)
94
        return default
95
96
    def __make_other(self, other: FrozeList[T_co] | Sequence[T_co]) -> list[T_co]:
97
        if isinstance(other, FrozeList):
98
            other = other.__lst
99
        if isinstance(other, list):
100
            return other
101
        elif isinstance(other, Sequence):
102
            return list(other)
103
        msg = f"Cannot compare to {type(other)}"
104
        raise TypeError(msg)
105
106
107
class FrozeSet(frozenset[T_co], Hashable):
108
    """
109
    An immutable set.
110
    Hashable and ordered.
111
    This is almost identical to ``typing.FrozenSet``, but it's behavior was made
112
    equivalent to those of FrozeDict and FrozeList.
113
    """
114
115
    EMPTY: FrozeSet = NotImplemented  # delayed
116
117
    def __init__(self, lst: frozenset[T_co]) -> None:
118
        self.__lst = lst if isinstance(lst, set) else set(lst)
119
        try:
120
            self.__hash = hash(tuple(lst))
121
        except AttributeError:
122
            # the hashes will collide, making sets slow
123
            # but at least we'll have a hash and thereby not violate the constraint
124
            self.__hash = 0
125
126
    def get(self, item: T_co, default: T_co | None = None) -> T_co | None:
127
        if item in self.__lst:
128
            return item
129
        return default
130
131
    def req(self, item: T_co, default: T_co | None = None) -> T_co:
132
        """
133
        Returns ``item`` if it is in this set.
134
        Short for "require".
135
        Falls back to ``default`` if ``default`` is not ``None``.
136
137
        Raises:
138
            KeyError: If ``item`` is not in this set and ``default`` is ``None``
139
        """
140
        if item in self.__lst:
141
            return item
142
        if default is None:
143
            msg = f"Item {item} not found"
144
            raise KeyError(msg)
145
        return default
146
147
    def __getitem__(self, item: T_co) -> T_co:
148
        if item in self.__lst:
149
            return item
150
        msg = f"Item {item} not found"
151
        raise KeyError(msg)
152
153
    def __contains__(self, x: T_co) -> bool:  # pragma: no cover
154
        return x in self.__lst
155
156
    def __iter__(self) -> Iterator[T_co]:  # pragma: no cover
157
        return iter(self.__lst)
158
159
    def __hash__(self) -> int:
160
        return self.__hash
161
162
    def __eq__(self, other: FrozeSet[T_co]) -> bool:
163
        return self.__lst == self.__make_other(other)
164
165
    def __lt__(self, other: FrozeSet[T_co] | frozenset[T_co]):
166
        """
167
        Compares ``self`` and ``other`` for partial ordering.
168
        Sorts ``self`` and ``other``, then compares the two sorted sets.
169
170
        Approximately::
171
            return list(sorted(self)) < list(sorted(other))
172
        """
173
        other = sorted(self.__make_other(other))
174
        me = sorted(self.__lst)
175
        return me < other
176
177
    @property
178
    def is_empty(self) -> bool:  # pragma: no cover
179
        return len(self.__lst) == 0
180
181
    @property
182
    def length(self) -> int:  # pragma: no cover
183
        return len(self.__lst)
184
185
    def __len__(self) -> int:  # pragma: no cover
186
        return len(self.__lst)
187
188
    def __str__(self) -> str:
189
        return str(self.__lst)
190
191
    def __repr__(self) -> str:
192
        return repr(self.__lst)
193
194
    def to_set(self) -> frozenset[T_co]:
195
        return set(self.__lst)
196
197
    def to_frozenset(self) -> frozenset[T_co]:
198
        return frozenset(self.__lst)
199
200
    def __make_other(self, other: FrozeSet[T_co] | frozenset[T_co]) -> set[T_co]:
201
        if isinstance(other, FrozeSet):
202
            other = other.__lst
203
        if isinstance(other, set):
204
            return other
205
        elif isinstance(other, frozenset):
206
            return set(other)
207
        msg = f"Cannot compare to {type(other)}"
208
        raise TypeError(msg)
209
210
211
class FrozeDict(Mapping[K_contra, V_co], Hashable):
212
    """
213
    An immutable dictionary/mapping.
214
    Hashable and ordered.
215
    """
216
217
    EMPTY: FrozeDict = NotImplemented  # delayed
218
219
    def __init__(self, dct: Mapping[K_contra, V_co]) -> None:
220
        self.__dct = dct if isinstance(dct, dict) else dict(dct)
221
        self.__hash = hash(tuple(dct.items()))
222
223
    def get(self, key: K_contra, default: V_co | None = None) -> V_co | None:  # pragma: no cover
224
        return self.__dct.get(key, default)
225
226
    def req(self, key: K_contra, default: V_co | None = None) -> V_co:
227
        """
228
        Returns the value corresponding to ``key``.
229
        Short for "require".
230
        Falls back to ``default`` if ``default`` is not None and ``key`` is not in this dict.
231
232
        Raise:
233
        KeyError: If ``key`` is not in this dict and ``default`` is ``None``
234
        """
235
        if default is None:
236
            return self.__dct[key]
237
        return self.__dct.get(key, default)
238
239
    def items(self) -> frozenset[tuple[K_contra, V_co]]:  # pragma: no cover
240
        return self.__dct.items()
241
242
    def keys(self) -> frozenset[K_contra]:  # pragma: no cover
243
        return self.__dct.keys()
244
245
    def values(self) -> ValuesView[V_co]:  # pragma: no cover
246
        return self.__dct.values()
247
248
    def __iter__(self):  # pragma: no cover
249
        return iter(self.__dct)
250
251
    def __contains__(self, item: K_contra) -> bool:  # pragma: no cover
252
        return item in self.__dct
253
254
    def __getitem__(self, item: K_contra) -> T_co:  # pragma: no cover
255
        return self.__dct[item]
256
257
    def __hash__(self) -> int:
258
        return self.__hash
259
260
    def __eq__(self, other: FrozeDict[K_contra, V_co]) -> bool:
261
        if isinstance(self, FrozeDict):
262
            return self.__dct == other.__dct
263
        elif isinstance(self, dict):
264
            return self == other.__dct
265
        elif isinstance(self, Mapping):
266
            return self == dict(other.__dct)
267
        msg = f"Cannot compare to {type(other)}"
268
        raise TypeError(msg)
269
270
    def __lt__(self, other: Mapping[K_contra, V_co]):
271
        """
272
        Compares this dict to another, with partial ordering.
273
274
        The algorithm is:
275
            1. Sort ``self`` and ``other`` by keys
276
            2. If ``sorted_self < sorted_other``, return ``False``
277
            3. If the reverse is true (``sorted_other < sorted_self``), return ``True``
278
            4. (The keys are now known to be the same.)
279
               For each key, in order: If ``self[key] < other[key]``, return ``True``
280
            5. Return ``False``
281
        """
282
        other = self.__make_other(other)
283
        me = self.__dct
284
        o_keys = sorted(other.keys())
285
        s_keys = sorted(me.keys())
286
        if o_keys < s_keys:
287
            return False
288
        if o_keys > s_keys:
289
            return True
290
        # keys are equal
291
        return any(other[k] > me[k] for k in o_keys)
292
293
    @property
294
    def is_empty(self) -> bool:  # pragma: no cover
295
        return len(self.__dct) == 0
296
297
    @property
298
    def length(self) -> int:  # pragma: no cover
299
        return len(self.__dct)
300
301
    def __len__(self) -> int:
302
        return len(self.__dct)
303
304
    def __str__(self) -> str:
305
        return str(self.__dct)
306
307
    def __repr__(self) -> str:
308
        return repr(self.__dct)
309
310
    def to_dict(self) -> MutableMapping[K_contra, V_co]:  # pragma: no cover
311
        return dict(self.__dct)
312
313
    def __make_other(
314
        self,
315
        other: FrozeDict[K_contra, V_co] | Mapping[K_contra, V_co],
316
    ) -> dict[K_contra, V_co]:
317
        if isinstance(other, FrozeDict):
318
            other = other.__dct
319
        if isinstance(other, dict):
320
            return other
321
        elif isinstance(other, Mapping):
322
            return dict(other)
323
        msg = f"Cannot compare to {type(other)}"
324
        raise TypeError(msg)
325
326
327
# for performance, only make these once:
328
FrozeList.EMPTY = FrozeList([])
329
FrozeSet.EMPTY = FrozeSet(set())
330
FrozeDict.EMPTY = FrozeDict({})
331
332
333
__all__ = ["FrozeList", "FrozeSet", "FrozeDict"]
334