Passed
Push — main ( ed7d21...87238c )
by Douglas
01:43
created

pocketutils.core   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 36
eloc 116
dl 0
loc 203
rs 9.52
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A LazyWrapped.__str__() 0 2 2
A LazyWrap.new_clearable_type() 0 8 1
A LazyWrapped.__init__() 0 2 1
A LazyWrapped.__repr__() 0 2 2
A OptRow.keys() 0 3 1
A LazyWrapped.__eq__() 0 2 1
A OptRow.values() 0 3 1
A LazyWrapped.is_defined() 0 3 1
A LazyWrapped.get() 0 5 2
A ClearableLazyWrapped.clear() 0 2 1
A OptRow.__str__() 0 2 1
A OptRow.__getattr__() 0 5 2
A LazyWrap.new_type() 0 4 1
A DictNamespace.__init__() 0 3 1
A OptRow.__repr__() 0 2 1
A OptRow.items() 0 3 1
A OptRow.__contains__() 0 6 2
A OptRow.__init__() 0 2 1
A OptRow.opt() 0 2 1
A Sentinel.__init__() 0 2 1
A OptRow.__eq__() 0 3 1
A LazyWrapped._name() 0 3 1
A LazyWrap._new_type() 0 33 1
A Sentinel.new() 0 3 1
A OptRow.req() 0 2 1
A LazyWrapped.raw_value() 0 3 1
A DictNamespace.__eq__() 0 4 3
A LazyWrapped._generate() 0 2 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A null_context() 0 2 1
1
from __future__ import annotations
2
3
import abc
4
import logging
5
from collections import UserDict
6
from typing import TYPE_CHECKING, Any, Generic, Self, TypeVar, Unpack
7
8
if TYPE_CHECKING:
9
    from collections.abc import Callable, Iterable, Mapping
10
11
T_co = TypeVar("T_co", covariant=True)
12
13
logger = logging.getLogger("pocketutils")
14
15
16
def null_context():
17
    yield
18
19
20
class Sentinel:
21
    """
22
    A sentinel value tied to nothing more than a memory address.
23
    """
24
25
    @classmethod
26
    def new(cls: type[Self]) -> Sentinel:
27
        return Sentinel()
28
29
    def __init__(self: Self) -> None:
30
        pass
31
32
33
V = TypeVar("V")
34
35
36
class LazyWrapped(Generic[V], metaclass=abc.ABCMeta):
37
    def __init__(self: Self) -> None:
38
        self._v, self._exists = None, False
39
40
    def get(self: Self) -> V:
41
        if not self._exists:
42
            self._v = self._generate()
43
            self._exists = True
44
        return self._v
45
46
    @property
47
    def raw_value(self: Self) -> V:
48
        return self._v
49
50
    @property
51
    def is_defined(self: Self) -> bool:
52
        return self._exists
53
54
    @property
55
    def _name(self: Self) -> str:
56
        raise NotImplementedError()
57
58
    def _generate(self: Self) -> V:
59
        raise NotImplementedError()
60
61
    def __repr__(self: Self) -> str:
62
        return self._name + "[" + (repr(self._v) if self.is_defined else "⌀") + "]"
63
64
    def __str__(self: Self) -> str:
65
        return self._name + "[" + (str(self._v) if self.is_defined else "⌀") + "]"
66
67
    def __eq__(self: Self, other: Self) -> bool:
68
        return type(self) == type(other) and self.is_defined == other.is_defined and self.raw_value == other.raw_value
69
70
71
class PlainLazyWrapped(LazyWrapped, metaclass=abc.ABCMeta):
72
    pass
73
74
75
class ClearableLazyWrapped(LazyWrapped, metaclass=abc.ABCMeta):
76
    def clear(self: Self) -> None:
77
        self._exists = False
78
79
80
class LazyWrap:
81
    @classmethod
82
    def new_type(cls: type[Self], dtype: str, generator: Callable[[], V]) -> type[PlainLazyWrapped]:
83
        # noinspection PyTypeChecker
84
        return cls._new_type(dtype, generator, PlainLazyWrapped)
85
86
    @classmethod
87
    def new_clearable_type(
88
        cls: type[Self],
89
        dtype: str,
90
        generator: Callable[[], V],
91
    ) -> type[ClearableLazyWrapped]:
92
        # noinspection PyTypeChecker
93
        return cls._new_type(dtype, generator, ClearableLazyWrapped)
94
95
    @classmethod
96
    def _new_type(
97
        cls: type[Self],
98
        dtype: str,
99
        generator: Callable[[], V],
100
        superclass: type[LazyWrapped],
101
    ) -> type[LazyWrapped]:
102
        """
103
        Creates a new mutable wrapped type.
104
105
        Example:
106
            >>> LazyRemoteTime = LazyWrap.new_type('RemoteTime', lambda: ...)
107
            >>> dt = LazyRemoteTime()  # nothing happens
108
            >>> dt.get()  # has a value
109
110
        Args:
111
            dtype: The name of the data type, such as 'datetime' if generator=datetime.now
112
            generator: This is called to (lazily) initialize an instance of the LazyWrapped
113
114
        Returns:
115
            A new class subclassing LazyWrapped
116
        """
117
118
        class X(superclass):
119
            @property
120
            def _name(self: Self):
121
                return dtype
122
123
            def _generate(self: Self):
124
                return generator()
125
126
        X.__name__ = superclass.__name__ + dtype
127
        return X
128
129
130
class DictNamespace(UserDict):
131
    """
132
    Behaves like a dict and a `SimpleNamespace`.
133
    This means it has a length, can be iterated over, etc., and can be accessed via `.`.
134
    """
135
136
    def __init__(self: Self, **kwargs: Unpack[Mapping[str, Any]]) -> None:
137
        super().__init__(**kwargs)
138
        self.__dict__.update(kwargs)
139
140
    def __eq__(self: Self, other: Self | DictNamespace) -> bool:
141
        if isinstance(self, DictNamespace) and isinstance(other, DictNamespace):
142
            return self.__dict__ == other.__dict__
143
        return NotImplemented
144
145
146
class OptRow:
147
    """
148
    Short for 'optional row'.
149
    A wrapper around a NamedTuple that returns None if the key doesn't exist.
150
    This is intended for Pandas itertuples().
151
    """
152
153
    def __init__(self: Self, row) -> None:
154
        self._row = row
155
156
    def __getattr__(self: Self, item: str) -> Any:
157
        try:
158
            return getattr(self._row, item)
159
        except AttributeError:
160
            return None
161
162
    def opt(self: Self, item: str) -> Any:
163
        return getattr(self, item)
164
165
    def req(self: Self, item: str) -> Any:
166
        return getattr(self._row, item)
167
168
    def __contains__(self: Self, item) -> bool:
169
        try:
170
            getattr(self._row, item)
171
            return True
172
        except AttributeError:
173
            return False
174
175
    def items(self: Self) -> Iterable[Any]:
176
        # noinspection PyProtectedMember
177
        return self._row._asdict()
178
179
    def keys(self: Self) -> Iterable[Any]:
180
        # noinspection PyProtectedMember
181
        return self._row._asdict().keys()
182
183
    def values(self: Self) -> Iterable[Any]:
184
        # noinspection PyProtectedMember
185
        return self._row._asdict().values()
186
187
    def __repr__(self: Self) -> str:
188
        return self.__class__.__name__ + "@" + hex(id(self))
189
190
    def __str__(self: Self) -> str:
191
        return self.__class__.__name__
192
193
    def __eq__(self: Self, other: Self) -> bool:
194
        # noinspection PyProtectedMember
195
        return self._row == other._row
196
197
198
__all__ = [
199
    "Sentinel",
200
    "OptRow",
201
    "LazyWrap",
202
    "DictNamespace",
203
]
204