Passed
Push — main ( c9ac86...a4501a )
by Douglas
02:00
created

pocketutils.core.JsonEncoder.default()   A

Complexity

Conditions 3

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
from __future__ import annotations
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
3
import abc
4
import enum
5
import logging
6
from typing import Any, Callable, Generic, Iterable, Sequence, Type, TypeVar
7
8
# noinspection PyProtectedMember
9
from pocketutils.core._internal import PathLike, PathLikeUtils
10
from pocketutils.core._internal import look as _look
11
from pocketutils.core.exceptions import ImmutableError
12
13
T = TypeVar("T", covariant=True)
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
14
15
logger = logging.getLogger("pocketutils")
16
17
18
class Sentinel:
19
    """
20
    A sentinel value tied to nothing more than a memory address.
21
    """
22
23
    @classmethod
24
    def new(cls) -> Sentinel:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
25
        return Sentinel()
26
27
    def __init__(self):
28
        pass
29
30
31
V = TypeVar("V")
0 ignored issues
show
Coding Style Naming introduced by
Class name "V" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
32
33
34
class LazyWrapped(Generic[V], metaclass=abc.ABCMeta):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
35
    def __init__(self):
36
        self._v, self._exists = None, False
37
38
    def get(self) -> V:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
39
        if not self._exists:
40
            self._v = self._generate()
41
            self._exists = True
42
        return self._v
43
44
    @property
45
    def raw_value(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
46
        return self._v
47
48
    @property
49
    def is_defined(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
50
        return self._exists
51
52
    @property
53
    def _name(self):
54
        raise NotImplementedError()
55
56
    def _generate(self):
57
        raise NotImplementedError()
58
59
    def __repr__(self):
60
        return self._name + "[" + (repr(self._v) if self.is_defined else "⌀") + "]"
61
62
    def __str__(self):
63
        return self._name + "[" + (str(self._v) if self.is_defined else "⌀") + "]"
64
65
    def __eq__(self, other):
66
        return (
67
            type(self) == type(other)
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
68
            and self.is_defined == other.is_defined
69
            and self.raw_value == other.raw_value
70
        )
71
72
73
class PlainLazyWrapped(LazyWrapped, metaclass=abc.ABCMeta):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
74
    pass
75
76
77
class ClearableLazyWrapped(LazyWrapped, metaclass=abc.ABCMeta):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
78
    def clear(self) -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
79
        self._exists = False
80
81
82
class LazyWrap:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
83
    @classmethod
84
    def new_type(cls, dtype: str, generator: Callable[[], V]) -> Type[PlainLazyWrapped]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
85
        # noinspection PyTypeChecker
86
        return cls._new_type(dtype, generator, PlainLazyWrapped)
87
88
    @classmethod
89
    def new_clearable_type(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
90
        cls, dtype: str, generator: Callable[[], V]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
91
    ) -> Type[ClearableLazyWrapped]:
92
        # noinspection PyTypeChecker
93
        return cls._new_type(dtype, generator, ClearableLazyWrapped)
94
95
    @classmethod
96
    def _new_type(
97
        cls, dtype: str, generator: Callable[[], V], superclass: Type[LazyWrapped]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
98
    ) -> Type[LazyWrapped]:
99
        """
100
        Creates a new mutable wrapped type.
101
102
        Example:
103
            >>> LazyRemoteTime = LazyWrap.new_type('RemoteTime', lambda: ...)
104
            >>> dt = LazyRemoteTime()  # nothing happens
105
            >>> dt.get()  # has a value
106
107
        Args:
108
            dtype: The name of the data type, such as 'datetime' if generator=datetime.now
109
            generator: This is called to (lazily) initialize an instance of the LazyWrapped
110
111
        Returns:
112
            A new class subclassing LazyWrapped
113
        """
114
115
        class X(superclass):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
Coding Style Naming introduced by
Class name "X" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
116
            @property
117
            def _name(self):
118
                return dtype
119
120
            def _generate(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
121
                return generator()
122
123
        X.__name__ = superclass.__name__ + dtype
124
        return X
125
126
127
class SmartEnum(enum.Enum):
128
    """
129
    An enum with a classmethod ``of`` that parses a string of the member's name.
130
    """
131
132
    @classmethod
133
    def of(cls, v):
0 ignored issues
show
Coding Style Naming introduced by
Argument name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style Naming introduced by
Method name "of" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
134
        """
135
        Returns the member of this enum class from a string with the member's name,
136
        case-insensitive and stripping whitespace.
137
        Will return ``v`` if ``v`` is already an instance of this class.
138
        """
139
        if isinstance(v, cls):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
140
            return v
141
        elif isinstance(v, str):
142
            if v in cls:
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
143
                return cls[v.upper().strip()]
144
            else:
145
                # in case the names are lowercase
146
                # noinspection PyTypeChecker
147
                for e in cls:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "e" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
148
                    if e.name.lower().strip() == v:
149
                        return e
150
                raise LookupError(f"{v} not found in {str(cls)}")
151
        else:
152
            raise TypeError(str(type(v)))
153
154
155
# noinspection PyPep8Naming
156
class frozenlist(Sequence):
0 ignored issues
show
Coding Style Naming introduced by
Class name "frozenlist" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Inheriting 'Sequence', which is not a class.
Loading history...
157
    """
158
    An immutable sequence backed by a list.
159
    The sole advantage over a tuple is the list-like __str__ with square brackets, which may be less confusing to a user.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (121/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
160
    """
161
162
    def __init__(self, items: Iterable[T]):
0 ignored issues
show
Bug introduced by
The __init__ method of the super-class _GenericAlias is not called.

It is generally advisable to initialize the super-class by calling its __init__ method:

class SomeParent:
    def __init__(self):
        self.x = 1

class SomeChild(SomeParent):
    def __init__(self):
        # Initialize the super class
        SomeParent.__init__(self)
Loading history...
163
        self.__items = list(items)
164
165
    def __getitem__(self, item) -> T:
166
        if isinstance(item, int):
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
167
            return self.__items[item]
168
        else:
169
            return frozenlist(self.__items[item])
170
171
    def __setitem__(self, key, value):
172
        raise ImmutableError()
173
174
    def __len__(self) -> int:
175
        return len(self.__items)
176
177
    def __repr__(self):
178
        return repr(self.__items)
179
180
    def __eq__(self, other):
181
        return type(self) == type(other) and self.__items == other.__items
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like __items was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
182
183
    def __str__(self):
184
        return repr(self.__items)
185
186
187
class OptRow:
188
    """
189
    Short for 'optional row'.
190
    A wrapper around a NamedTuple that returns None if the key doesn't exist.
191
    This is intended for Pandas itertuples().
192
    """
193
194
    def __init__(self, row):
195
        self._row = row
196
197
    def __getattr__(self, item: str) -> Any:
198
        try:
199
            return getattr(self._row, item)
200
        except AttributeError:
201
            return None
202
203
    def opt(self, item: str, look=None) -> Any:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
204
        x = getattr(self, item)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
205
        if x is None:
206
            return None
207
        return _look(x, look)
208
209
    def req(self, item: str, look=None) -> Any:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
210
        x = getattr(self._row, item)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
211
        if x is None:
212
            return None
213
        return _look(x, look)
214
215
    def __contains__(self, item):
216
        try:
217
            getattr(self._row, item)
218
            return True
219
        except AttributeError:
220
            return False
221
222
    def items(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
223
        # noinspection PyProtectedMember
224
        return self._row._asdict()
225
226
    def keys(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
227
        # noinspection PyProtectedMember
228
        return self._row._asdict().keys()
229
230
    def values(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
231
        # noinspection PyProtectedMember
232
        return self._row._asdict().values()
233
234
    def __repr__(self):
235
        return self.__class__.__name__ + "@" + hex(id(self))
236
237
    def __str__(self):
238
        return self.__class__.__name__
239
240
    def __eq__(self, other):
241
        # noinspection PyProtectedMember
242
        return self._row == other._row
243
244
245
__all__ = [
246
    "Sentinel",
247
    "SmartEnum",
248
    "frozenlist",
249
    "PathLike",
250
    "PathLikeUtils",
251
    "OptRow",
252
    "LazyWrap",
253
]
254