zstdlib 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zstdlib-0.1.0 → zstdlib-0.2.0}/PKG-INFO +1 -1
- zstdlib-0.2.0/tests/test_enum.py +133 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/__init__.py +2 -2
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/ansi.py +1 -1
- zstdlib-0.2.0/zstdlib/enum.py +154 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib.egg-info/PKG-INFO +1 -1
- zstdlib-0.1.0/tests/test_enum.py +0 -137
- zstdlib-0.1.0/zstdlib/enum.py +0 -53
- {zstdlib-0.1.0 → zstdlib-0.2.0}/LICENSE +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/README.md +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/pyproject.toml +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/setup.cfg +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/__init__.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/log/__init__.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/log/base.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/log/test_cute.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/log/test_trace.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/not_set.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/test_ansi.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/test_frozen.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/test_io.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/tests/test_singleton.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/frozen.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/io.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/log/__init__.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/log/cute.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/log/trace.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/not_set.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/py.typed +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib/singleton.py +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib.egg-info/SOURCES.txt +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib.egg-info/dependency_links.txt +0 -0
- {zstdlib-0.1.0 → zstdlib-0.2.0}/zstdlib.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring,unused-variable
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from zstdlib.enum import EnumType, Enum, entries, values, auto
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestEnumType(unittest.TestCase):
|
|
8
|
+
|
|
9
|
+
def test_valid(self) -> None:
|
|
10
|
+
class ET1(metaclass=EnumType):
|
|
11
|
+
arg1: int = 0
|
|
12
|
+
arg2: int = 1
|
|
13
|
+
|
|
14
|
+
def test_empty(self) -> None:
|
|
15
|
+
class ET1(metaclass=EnumType, empty_ok=True):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
with self.assertRaises(ValueError):
|
|
19
|
+
|
|
20
|
+
class ET2(metaclass=EnumType):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def test_dupes(self) -> None:
|
|
24
|
+
class ET1(metaclass=EnumType, dupes_ok=True):
|
|
25
|
+
a: int = 5
|
|
26
|
+
b: int = 5
|
|
27
|
+
c: dict = {}
|
|
28
|
+
|
|
29
|
+
with self.assertRaises(ValueError):
|
|
30
|
+
|
|
31
|
+
class ET2(metaclass=EnumType):
|
|
32
|
+
a: int = 5
|
|
33
|
+
b: int = 5
|
|
34
|
+
|
|
35
|
+
with self.assertRaises(TypeError):
|
|
36
|
+
|
|
37
|
+
class ET3(metaclass=EnumType):
|
|
38
|
+
a: dict = {}
|
|
39
|
+
|
|
40
|
+
def test_type_check(self) -> None:
|
|
41
|
+
class ET1(metaclass=EnumType, type_check=set()):
|
|
42
|
+
a: str = 5 # type: ignore[assignment]
|
|
43
|
+
|
|
44
|
+
with self.assertRaises(TypeError):
|
|
45
|
+
|
|
46
|
+
class ET2(metaclass=EnumType):
|
|
47
|
+
a: str = 5 # type: ignore[assignment]
|
|
48
|
+
|
|
49
|
+
def test_annotations(self) -> None:
|
|
50
|
+
with self.assertRaises(ValueError):
|
|
51
|
+
|
|
52
|
+
class ET1(metaclass=EnumType):
|
|
53
|
+
arg1 = 1
|
|
54
|
+
|
|
55
|
+
with self.assertRaises(ValueError):
|
|
56
|
+
|
|
57
|
+
class ET2(metaclass=EnumType):
|
|
58
|
+
arg1: int
|
|
59
|
+
|
|
60
|
+
def test_instantiation(self) -> None:
|
|
61
|
+
with self.assertRaises(AttributeError):
|
|
62
|
+
|
|
63
|
+
class ET1(metaclass=EnumType):
|
|
64
|
+
arg1: int = 1
|
|
65
|
+
|
|
66
|
+
def __init__(self):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
with self.assertRaises(AttributeError):
|
|
70
|
+
|
|
71
|
+
class ET2(metaclass=EnumType):
|
|
72
|
+
arg1: int = 1
|
|
73
|
+
|
|
74
|
+
def __new__(cls):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
class ET3(metaclass=EnumType):
|
|
78
|
+
arg1: int = 0
|
|
79
|
+
|
|
80
|
+
with self.assertRaises(TypeError):
|
|
81
|
+
ET3()
|
|
82
|
+
|
|
83
|
+
with self.assertRaises(TypeError):
|
|
84
|
+
ET3.__new__({}) # type: ignore[arg-type]
|
|
85
|
+
|
|
86
|
+
def test_iter(self) -> None:
|
|
87
|
+
|
|
88
|
+
class ET11(metaclass=EnumType):
|
|
89
|
+
arg1: int = 0
|
|
90
|
+
arg2: str = "1"
|
|
91
|
+
|
|
92
|
+
self.assertEqual(tuple(ET11), ("arg1", "arg2"))
|
|
93
|
+
|
|
94
|
+
def test_auto(self) -> None:
|
|
95
|
+
|
|
96
|
+
class ET1(metaclass=EnumType):
|
|
97
|
+
arg1: int = 0
|
|
98
|
+
arg2 = auto
|
|
99
|
+
arg3 = auto
|
|
100
|
+
|
|
101
|
+
self.assertEqual(3, len(set(values(ET1))))
|
|
102
|
+
|
|
103
|
+
with self.assertRaises(TypeError):
|
|
104
|
+
|
|
105
|
+
class ET2(metaclass=EnumType):
|
|
106
|
+
arg1: int = 0
|
|
107
|
+
arg2: str = auto # type: ignore[assignment]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TestEnum(unittest.TestCase):
|
|
111
|
+
|
|
112
|
+
def test_is(self) -> None:
|
|
113
|
+
self.assertIsInstance(Enum, EnumType)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TestFunctions(unittest.TestCase):
|
|
117
|
+
def test_entries(self) -> None:
|
|
118
|
+
class E1(Enum):
|
|
119
|
+
arg1: int = 0
|
|
120
|
+
arg2: str = "1"
|
|
121
|
+
|
|
122
|
+
self.assertEqual(entries(E1), {"arg1": (int, 0), "arg2": (str, "1")})
|
|
123
|
+
|
|
124
|
+
def test_values(self) -> None:
|
|
125
|
+
class E1(Enum):
|
|
126
|
+
arg1: int = 0
|
|
127
|
+
arg2: str = "1"
|
|
128
|
+
|
|
129
|
+
self.assertEqual(values(E1), (0, "1"))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
unittest.main()
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class _Auto:
|
|
6
|
+
"""A sentinel value for automatic enum values"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Pass this as a value to have it automatically assigned
|
|
10
|
+
auto = _Auto()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EnumType(type):
|
|
14
|
+
"""
|
|
15
|
+
Metaclass for uninstantiable Enum classes with required annotations and unique values
|
|
16
|
+
Enum entry names may not be prefixed with __ or be an EnumType._AutoValue
|
|
17
|
+
These 'Enum' classes may not be modified after creation
|
|
18
|
+
Items of annotation types in type_check will be type-checked
|
|
19
|
+
Duplicate values (as determined by set()) are not allowed unless dupes_ok is True
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
_AUTO_PREFIX = "Enum_Auto_"
|
|
23
|
+
_TC_DEFAULT: set[type | None] = {int, str, float, bool, complex, bytes, None, type(None)}
|
|
24
|
+
|
|
25
|
+
class _AutoValue:
|
|
26
|
+
"""A class representing an automatically assigned enum value"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, value: int) -> None:
|
|
29
|
+
self._value = value
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
return f"<Auto: {self._value}>"
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return str(self)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def _gen_entries_and_annotations(
|
|
39
|
+
mcs, attrs: dict, dupes_ok: bool
|
|
40
|
+
) -> tuple[dict[str, tuple[type | None, Any]], dict[str, type | None]]:
|
|
41
|
+
"""
|
|
42
|
+
Generate enum entries from attrs, checking for duplicates and missing annotations
|
|
43
|
+
Does not check for empty enum or type correctness
|
|
44
|
+
"""
|
|
45
|
+
seen = set()
|
|
46
|
+
auto_val = 0
|
|
47
|
+
_entries = {}
|
|
48
|
+
_annotations = dict(attrs.get("__annotations__", {}))
|
|
49
|
+
try:
|
|
50
|
+
for a_name, value in ((i, k) for i, k in attrs.items() if not i.startswith("__")):
|
|
51
|
+
if value is auto:
|
|
52
|
+
auto_val += 1
|
|
53
|
+
value = mcs._AutoValue(auto_val)
|
|
54
|
+
if a_name in _annotations:
|
|
55
|
+
raise TypeError("Auto enum entries may not be type annotated")
|
|
56
|
+
_annotations[a_name] = mcs._AutoValue
|
|
57
|
+
elif isinstance(value, mcs._AutoValue):
|
|
58
|
+
raise ValueError("Enum entries may not be of type EnumType._AutoValue")
|
|
59
|
+
if not dupes_ok:
|
|
60
|
+
if value in seen:
|
|
61
|
+
raise ValueError(f"Duplicate enum value: {value}")
|
|
62
|
+
seen.add(value)
|
|
63
|
+
_entries[a_name] = (_annotations[a_name], value)
|
|
64
|
+
except TypeError:
|
|
65
|
+
raise TypeError(f"Unhashable enum value: {value}") from None
|
|
66
|
+
except KeyError:
|
|
67
|
+
raise ValueError("Non-auto enum entries must be type annotated") from None
|
|
68
|
+
return _entries, _annotations
|
|
69
|
+
|
|
70
|
+
# pylint: disable=too-many-arguments, dangerous-default-value
|
|
71
|
+
def __new__(
|
|
72
|
+
mcs,
|
|
73
|
+
name,
|
|
74
|
+
bases,
|
|
75
|
+
attrs,
|
|
76
|
+
*,
|
|
77
|
+
empty_ok: bool = False,
|
|
78
|
+
dupes_ok: bool = False,
|
|
79
|
+
type_check: set[type | None] = _TC_DEFAULT,
|
|
80
|
+
**kwargs,
|
|
81
|
+
):
|
|
82
|
+
# Disallow undesired instance methods
|
|
83
|
+
if bad := attrs.keys() & {"__init__", "__new__", "__entries__"}:
|
|
84
|
+
raise AttributeError(f"Illegal methods in Enum class: {', '.join(bad)}")
|
|
85
|
+
# Generate entries
|
|
86
|
+
_entries, _annotations = mcs._gen_entries_and_annotations(attrs, dupes_ok)
|
|
87
|
+
# Validate entries
|
|
88
|
+
if no_value := (_annotations.keys() - _entries.keys()):
|
|
89
|
+
raise ValueError(f"Entries must have a value: {' '.join(no_value)}")
|
|
90
|
+
if (not empty_ok) and not _entries:
|
|
91
|
+
raise ValueError("Enum type may not be empty")
|
|
92
|
+
if type_check:
|
|
93
|
+
for a_name, (ann, val) in _entries.items():
|
|
94
|
+
if ann in type_check and (
|
|
95
|
+
(ann is None is not val) or (ann is not None and not isinstance(val, ann))
|
|
96
|
+
):
|
|
97
|
+
raise TypeError(f"Entry {a_name} is not of type {ann}")
|
|
98
|
+
# Construct class
|
|
99
|
+
attrs["__entries__"] = _entries
|
|
100
|
+
attrs["__annotations__"] = _annotations
|
|
101
|
+
attrs["__init__"] = attrs["__new__"] = NotImplemented
|
|
102
|
+
return type.__new__(mcs, name, bases, attrs, **kwargs)
|
|
103
|
+
|
|
104
|
+
# Disallow modification
|
|
105
|
+
|
|
106
|
+
def __delattr__(cls, *_):
|
|
107
|
+
raise AttributeError("This class cannot be modified")
|
|
108
|
+
|
|
109
|
+
def __setattr__(cls, *_):
|
|
110
|
+
raise AttributeError("This class cannot be modified")
|
|
111
|
+
|
|
112
|
+
# Class methods for the derived types
|
|
113
|
+
|
|
114
|
+
def __iter__(cls) -> Iterator[str]:
|
|
115
|
+
"""Iterator over enum keys"""
|
|
116
|
+
yield from cls.__entries__ # type: ignore
|
|
117
|
+
|
|
118
|
+
def _disallow(cls):
|
|
119
|
+
"""Disallow instantiation"""
|
|
120
|
+
raise AttributeError("This class may not be instantiated")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Enum(metaclass=EnumType, empty_ok=True):
|
|
124
|
+
"""
|
|
125
|
+
An uninstantiable Enum base type
|
|
126
|
+
Subclasses must provide type-annotated fields with unique values
|
|
127
|
+
Fields may not be prefixed with "__"
|
|
128
|
+
Derived classes will not be modifiable
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def entries(enum: Any) -> dict[str, tuple[type | None, Any]]:
|
|
133
|
+
"""
|
|
134
|
+
:param enum: A class whose metaclass is EnumType
|
|
135
|
+
:return: A dict mapping names to (type annotation, value) pairs
|
|
136
|
+
If enum's type is not an EnumType, raises TypeError
|
|
137
|
+
"""
|
|
138
|
+
if not isinstance(enum, EnumType):
|
|
139
|
+
raise TypeError("enum must have a metaclass of EnumType")
|
|
140
|
+
return dict(enum.__entries__) # type: ignore[attr-defined]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def values(enum: Any) -> tuple[Any, ...]:
|
|
144
|
+
"""
|
|
145
|
+
:param enum: A class whose metaclass is EnumType
|
|
146
|
+
:return: A tuple of all values of enum
|
|
147
|
+
If enum's type is not an EnumType, raises TypeError
|
|
148
|
+
"""
|
|
149
|
+
if not isinstance(enum, EnumType):
|
|
150
|
+
raise TypeError("enum must have a metaclass of EnumType")
|
|
151
|
+
return tuple(k[1] for k in enum.__entries__.values()) # type: ignore[attr-defined]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
__all__ = ("Enum", "EnumType", "entries", "values", "auto")
|
zstdlib-0.1.0/tests/test_enum.py
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring,unused-variable
|
|
2
|
-
import unittest
|
|
3
|
-
|
|
4
|
-
from zstdlib.enum import EnumType, Enum
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TestEnumType(unittest.TestCase):
|
|
8
|
-
|
|
9
|
-
def test_valid(self) -> None:
|
|
10
|
-
class ET1(metaclass=EnumType):
|
|
11
|
-
arg1: int = 0
|
|
12
|
-
arg2: int = 1
|
|
13
|
-
|
|
14
|
-
def test_empty(self) -> None:
|
|
15
|
-
class ET2(metaclass=EnumType, empty_ok=True):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
with self.assertRaises(ValueError):
|
|
19
|
-
|
|
20
|
-
class ET3(metaclass=EnumType):
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
def test_dupe(self) -> None:
|
|
24
|
-
with self.assertRaises(ValueError):
|
|
25
|
-
|
|
26
|
-
class ET4(metaclass=EnumType):
|
|
27
|
-
arg1: int = 0
|
|
28
|
-
arg2: int = 0
|
|
29
|
-
|
|
30
|
-
def test_annotations(self) -> None:
|
|
31
|
-
with self.assertRaises(ValueError):
|
|
32
|
-
|
|
33
|
-
class ET5(metaclass=EnumType):
|
|
34
|
-
arg1 = 1
|
|
35
|
-
|
|
36
|
-
with self.assertRaises(ValueError):
|
|
37
|
-
|
|
38
|
-
class ET6(metaclass=EnumType):
|
|
39
|
-
arg1: int
|
|
40
|
-
|
|
41
|
-
with self.assertRaises(TypeError):
|
|
42
|
-
|
|
43
|
-
class ET7(metaclass=EnumType):
|
|
44
|
-
arg1: str = 0 # type: ignore[assignment]
|
|
45
|
-
|
|
46
|
-
def test_instantiation(self) -> None:
|
|
47
|
-
with self.assertRaises(AttributeError):
|
|
48
|
-
|
|
49
|
-
class ET8(metaclass=EnumType):
|
|
50
|
-
arg1: int = 1
|
|
51
|
-
|
|
52
|
-
def __init__(self):
|
|
53
|
-
pass
|
|
54
|
-
|
|
55
|
-
with self.assertRaises(AttributeError):
|
|
56
|
-
|
|
57
|
-
class ET9(metaclass=EnumType):
|
|
58
|
-
arg1: int = 1
|
|
59
|
-
|
|
60
|
-
def __new__(cls):
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
class ET10(metaclass=EnumType):
|
|
64
|
-
arg1: int = 0
|
|
65
|
-
|
|
66
|
-
with self.assertRaises(TypeError):
|
|
67
|
-
ET10()
|
|
68
|
-
with self.assertRaises(TypeError):
|
|
69
|
-
ET10.__init__({})
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class TestEnum(unittest.TestCase):
|
|
73
|
-
def test_valid(self) -> None:
|
|
74
|
-
class E1(Enum):
|
|
75
|
-
arg1: int = 0
|
|
76
|
-
arg2: int = 1
|
|
77
|
-
|
|
78
|
-
def test_empty(self) -> None:
|
|
79
|
-
class E2(Enum, empty_ok=True):
|
|
80
|
-
pass
|
|
81
|
-
|
|
82
|
-
with self.assertRaises(ValueError):
|
|
83
|
-
|
|
84
|
-
class E3(Enum):
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
|
-
def test_dupe(self) -> None:
|
|
88
|
-
with self.assertRaises(ValueError):
|
|
89
|
-
|
|
90
|
-
class E4(Enum):
|
|
91
|
-
arg1: int = 0
|
|
92
|
-
arg2: int = 0
|
|
93
|
-
|
|
94
|
-
def test_annotations(self) -> None:
|
|
95
|
-
with self.assertRaises(ValueError):
|
|
96
|
-
|
|
97
|
-
class E5(Enum):
|
|
98
|
-
arg1 = 1
|
|
99
|
-
|
|
100
|
-
with self.assertRaises(ValueError):
|
|
101
|
-
|
|
102
|
-
class E6(Enum):
|
|
103
|
-
arg1: int
|
|
104
|
-
|
|
105
|
-
with self.assertRaises(TypeError):
|
|
106
|
-
|
|
107
|
-
class E7(Enum):
|
|
108
|
-
arg1: str = 0 # type: ignore[assignment]
|
|
109
|
-
|
|
110
|
-
def test_instantiation(self) -> None:
|
|
111
|
-
with self.assertRaises(AttributeError):
|
|
112
|
-
|
|
113
|
-
class E8(Enum):
|
|
114
|
-
arg1: int = 1
|
|
115
|
-
|
|
116
|
-
def __init__(self):
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
|
-
with self.assertRaises(AttributeError):
|
|
120
|
-
|
|
121
|
-
class E9(Enum):
|
|
122
|
-
arg1: int = 1
|
|
123
|
-
|
|
124
|
-
def __new__(cls):
|
|
125
|
-
pass
|
|
126
|
-
|
|
127
|
-
class E10(Enum):
|
|
128
|
-
arg1: int = 0
|
|
129
|
-
|
|
130
|
-
with self.assertRaises(TypeError):
|
|
131
|
-
E10()
|
|
132
|
-
with self.assertRaises(TypeError):
|
|
133
|
-
E10.__init__({})
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if __name__ == "__main__":
|
|
137
|
-
unittest.main()
|
zstdlib-0.1.0/zstdlib/enum.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import collections
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class EnumType(type):
|
|
5
|
-
"""
|
|
6
|
-
Metaclass for uninstantiable Enum classes with required annotations and unique values
|
|
7
|
-
Enum values may not be prefixed with _
|
|
8
|
-
These 'Enum' classes may not be modified after creation
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
def __new__(mcs, name, bases, attrs, **kwargs):
|
|
12
|
-
# Disallow instantiation
|
|
13
|
-
for bad in ("__init__", "__new__"):
|
|
14
|
-
if bad in attrs:
|
|
15
|
-
raise AttributeError("Cannot define __init__ or __new__")
|
|
16
|
-
attrs[bad] = None
|
|
17
|
-
# Check annotations
|
|
18
|
-
public = {i: k for i, k in attrs.items() if not i.startswith("__")}
|
|
19
|
-
annotations = attrs.get("__annotations__", {})
|
|
20
|
-
eok = kwargs.pop("empty_ok", False)
|
|
21
|
-
if not public and not annotations:
|
|
22
|
-
if not eok:
|
|
23
|
-
raise ValueError("Enum type is empty")
|
|
24
|
-
if bad := (pub_set := set(public)) - (an_set := set(annotations)):
|
|
25
|
-
raise ValueError(f"All enum entries must be type annotated: {bad}")
|
|
26
|
-
if bad := an_set - pub_set:
|
|
27
|
-
raise ValueError(f"All type annotated entries must have a value: {bad}")
|
|
28
|
-
for i, typ in annotations.items():
|
|
29
|
-
if not isinstance(attrs[i], typ):
|
|
30
|
-
raise TypeError(f"{i} is not of type {typ}")
|
|
31
|
-
# Disallow duplicate values
|
|
32
|
-
counts = collections.Counter(public.values())
|
|
33
|
-
if dups := {i: k for i, k in public.items() if counts[k] > 1}:
|
|
34
|
-
raise ValueError(f"Duplicate values: {dups}")
|
|
35
|
-
# Construct class
|
|
36
|
-
return type.__new__(mcs, name, bases, attrs, **kwargs)
|
|
37
|
-
|
|
38
|
-
# Disallow modification
|
|
39
|
-
|
|
40
|
-
def __delattr__(cls, *_):
|
|
41
|
-
raise AttributeError("This class cannot be modified")
|
|
42
|
-
|
|
43
|
-
def __setattr__(cls, *_):
|
|
44
|
-
raise AttributeError("This class cannot be modified")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class Enum(metaclass=EnumType, empty_ok=True):
|
|
48
|
-
"""
|
|
49
|
-
A uninstantiable Enum base type
|
|
50
|
-
Subclasses must provide type-annotated fields with unique values
|
|
51
|
-
Fields may not be prefixed with "_"
|
|
52
|
-
Derived classes will not be modifiable
|
|
53
|
-
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|