PythonIota 1.0.0__tar.gz → 1.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.
- {pythoniota-1.0.0 → pythoniota-1.2.0}/PKG-INFO +1 -1
- {pythoniota-1.0.0 → pythoniota-1.2.0}/pyproject.toml +1 -1
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/PKG-INFO +1 -1
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/SOURCES.txt +7 -1
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/__init__.py +10 -2
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/_bitflag.py +28 -0
- pythoniota-1.2.0/src/pythoniota/enum.py +540 -0
- pythoniota-1.2.0/src/pythoniota/recipes.py +236 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/sequence.py +138 -0
- pythoniota-1.2.0/tests/test_advanced.py +377 -0
- pythoniota-1.2.0/tests/test_enum_enhanced.py +89 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_integration.py +1 -1
- pythoniota-1.2.0/tests/test_recipes.py +130 -0
- pythoniota-1.2.0/tests/test_serialization.py +86 -0
- pythoniota-1.2.0/tests/test_string_enum.py +114 -0
- pythoniota-1.0.0/src/pythoniota/enum.py +0 -239
- {pythoniota-1.0.0 → pythoniota-1.2.0}/setup.cfg +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/dependency_links.txt +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/top_level.txt +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/_compat.py +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/_safe_eval.py +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_bitflags.py +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_compat.py +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_enum.py +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_safe_eval.py +0 -0
- {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_sequence.py +0 -0
|
@@ -8,10 +8,16 @@ src/pythoniota/_bitflag.py
|
|
|
8
8
|
src/pythoniota/_compat.py
|
|
9
9
|
src/pythoniota/_safe_eval.py
|
|
10
10
|
src/pythoniota/enum.py
|
|
11
|
+
src/pythoniota/recipes.py
|
|
11
12
|
src/pythoniota/sequence.py
|
|
13
|
+
tests/test_advanced.py
|
|
12
14
|
tests/test_bitflags.py
|
|
13
15
|
tests/test_compat.py
|
|
14
16
|
tests/test_enum.py
|
|
17
|
+
tests/test_enum_enhanced.py
|
|
15
18
|
tests/test_integration.py
|
|
19
|
+
tests/test_recipes.py
|
|
16
20
|
tests/test_safe_eval.py
|
|
17
|
-
tests/test_sequence.py
|
|
21
|
+
tests/test_sequence.py
|
|
22
|
+
tests/test_serialization.py
|
|
23
|
+
tests/test_string_enum.py
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
from pythoniota._compat import Iota
|
|
2
2
|
from pythoniota._bitflag import BitFlag
|
|
3
3
|
from pythoniota._safe_eval import safe_eval
|
|
4
|
-
from pythoniota.enum import
|
|
4
|
+
from pythoniota.enum import (
|
|
5
|
+
IotaEnum, IotaBitFlags, IotaStringEnum,
|
|
6
|
+
FlagScope, iota_enum, _EnumMemberProxy,
|
|
7
|
+
)
|
|
5
8
|
from pythoniota.sequence import IotaSequence as iota
|
|
9
|
+
from pythoniota import recipes
|
|
6
10
|
|
|
7
|
-
__version__ = "1.
|
|
11
|
+
__version__ = "1.2.0"
|
|
8
12
|
__all__ = [
|
|
9
13
|
"Iota",
|
|
10
14
|
"IotaEnum",
|
|
11
15
|
"IotaBitFlags",
|
|
16
|
+
"IotaStringEnum",
|
|
12
17
|
"iota",
|
|
13
18
|
"BitFlag",
|
|
19
|
+
"FlagScope",
|
|
20
|
+
"iota_enum",
|
|
14
21
|
"safe_eval",
|
|
22
|
+
"recipes",
|
|
15
23
|
]
|
|
@@ -69,6 +69,20 @@ class BitFlag:
|
|
|
69
69
|
def has(self, flag: BitFlag | int) -> bool:
|
|
70
70
|
return flag in self
|
|
71
71
|
|
|
72
|
+
def has_any(self, *flags: BitFlag | int) -> bool:
|
|
73
|
+
for f in flags:
|
|
74
|
+
fv = f._value if isinstance(f, BitFlag) else int(f)
|
|
75
|
+
if self._value & fv:
|
|
76
|
+
return True
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def has_all(self, *flags: BitFlag | int) -> bool:
|
|
80
|
+
for f in flags:
|
|
81
|
+
fv = f._value if isinstance(f, BitFlag) else int(f)
|
|
82
|
+
if (self._value & fv) != fv:
|
|
83
|
+
return False
|
|
84
|
+
return True
|
|
85
|
+
|
|
72
86
|
def decompose(self) -> list[BitFlag]:
|
|
73
87
|
result = []
|
|
74
88
|
v = self._value
|
|
@@ -87,5 +101,19 @@ class BitFlag:
|
|
|
87
101
|
def __str__(self) -> str:
|
|
88
102
|
return self._name or str(self._value)
|
|
89
103
|
|
|
104
|
+
def __format__(self, format_spec: str) -> str:
|
|
105
|
+
if not format_spec:
|
|
106
|
+
return self._name or str(self._value)
|
|
107
|
+
if format_spec == "v":
|
|
108
|
+
return str(self._value)
|
|
109
|
+
if format_spec == "n":
|
|
110
|
+
return self._name
|
|
111
|
+
if format_spec == "b":
|
|
112
|
+
return bin(self._value)
|
|
113
|
+
return format(self._value, format_spec)
|
|
114
|
+
|
|
115
|
+
def __reduce__(self) -> tuple:
|
|
116
|
+
return (BitFlag, (self._value, self._name))
|
|
117
|
+
|
|
90
118
|
def __iter__(self) -> Iterator[BitFlag]:
|
|
91
119
|
return iter(self.decompose())
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pickle
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from typing import Any, Callable, Iterator
|
|
8
|
+
|
|
9
|
+
from pythoniota._bitflag import BitFlag
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _IotaCounter:
|
|
13
|
+
"""Sentinel returned by IotaNamespace when 'iota' is read. Holds the current counter value."""
|
|
14
|
+
|
|
15
|
+
__slots__ = ("value",)
|
|
16
|
+
|
|
17
|
+
def __init__(self, value: int) -> None:
|
|
18
|
+
self.value = value
|
|
19
|
+
|
|
20
|
+
def __int__(self) -> int:
|
|
21
|
+
return self.value
|
|
22
|
+
|
|
23
|
+
def __index__(self) -> int:
|
|
24
|
+
return self.value
|
|
25
|
+
|
|
26
|
+
def __lshift__(self, other: Any) -> int:
|
|
27
|
+
return self.value << int(other)
|
|
28
|
+
|
|
29
|
+
def __rlshift__(self, other: Any) -> int:
|
|
30
|
+
return int(other) << self.value
|
|
31
|
+
|
|
32
|
+
def __rshift__(self, other: Any) -> int:
|
|
33
|
+
return self.value >> int(other)
|
|
34
|
+
|
|
35
|
+
def __rrshift__(self, other: Any) -> int:
|
|
36
|
+
return int(other) >> self.value
|
|
37
|
+
|
|
38
|
+
def __add__(self, other: Any) -> int:
|
|
39
|
+
return self.value + int(other)
|
|
40
|
+
|
|
41
|
+
def __radd__(self, other: Any) -> int:
|
|
42
|
+
return int(other) + self.value
|
|
43
|
+
|
|
44
|
+
def __sub__(self, other: Any) -> int:
|
|
45
|
+
return self.value - int(other)
|
|
46
|
+
|
|
47
|
+
def __rsub__(self, other: Any) -> int:
|
|
48
|
+
return int(other) - self.value
|
|
49
|
+
|
|
50
|
+
def __mul__(self, other: Any) -> int:
|
|
51
|
+
return self.value * int(other)
|
|
52
|
+
|
|
53
|
+
def __rmul__(self, other: Any) -> int:
|
|
54
|
+
return int(other) * self.value
|
|
55
|
+
|
|
56
|
+
def __floordiv__(self, other: Any) -> int:
|
|
57
|
+
return self.value // int(other)
|
|
58
|
+
|
|
59
|
+
def __rfloordiv__(self, other: Any) -> int:
|
|
60
|
+
return int(other) // self.value
|
|
61
|
+
|
|
62
|
+
def __mod__(self, other: Any) -> int:
|
|
63
|
+
return self.value % int(other)
|
|
64
|
+
|
|
65
|
+
def __rmod__(self, other: Any) -> int:
|
|
66
|
+
return int(other) % self.value
|
|
67
|
+
|
|
68
|
+
def __pow__(self, other: Any) -> int:
|
|
69
|
+
return self.value ** int(other)
|
|
70
|
+
|
|
71
|
+
def __rpow__(self, other: Any) -> int:
|
|
72
|
+
return int(other) ** self.value
|
|
73
|
+
|
|
74
|
+
def __or__(self, other: Any) -> int:
|
|
75
|
+
return self.value | int(other)
|
|
76
|
+
|
|
77
|
+
def __ror__(self, other: Any) -> int:
|
|
78
|
+
return int(other) | self.value
|
|
79
|
+
|
|
80
|
+
def __and__(self, other: Any) -> int:
|
|
81
|
+
return self.value & int(other)
|
|
82
|
+
|
|
83
|
+
def __rand__(self, other: Any) -> int:
|
|
84
|
+
return int(other) & self.value
|
|
85
|
+
|
|
86
|
+
def __xor__(self, other: Any) -> int:
|
|
87
|
+
return self.value ^ int(other)
|
|
88
|
+
|
|
89
|
+
def __rxor__(self, other: Any) -> int:
|
|
90
|
+
return int(other) ^ self.value
|
|
91
|
+
|
|
92
|
+
def __neg__(self) -> int:
|
|
93
|
+
return -self.value
|
|
94
|
+
|
|
95
|
+
def __pos__(self) -> int:
|
|
96
|
+
return +self.value
|
|
97
|
+
|
|
98
|
+
def __invert__(self) -> int:
|
|
99
|
+
return ~self.value
|
|
100
|
+
|
|
101
|
+
def __eq__(self, other: Any) -> bool:
|
|
102
|
+
return self.value == int(other) if isinstance(other, (int, _IotaCounter)) else NotImplemented
|
|
103
|
+
|
|
104
|
+
def __hash__(self) -> int:
|
|
105
|
+
return hash(self.value)
|
|
106
|
+
|
|
107
|
+
def __repr__(self) -> str:
|
|
108
|
+
return f"iota({self.value})"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class IotaNamespace(dict): # type: ignore[type-arg]
|
|
112
|
+
|
|
113
|
+
def __init__(self) -> None:
|
|
114
|
+
super().__init__()
|
|
115
|
+
self._counter = 0
|
|
116
|
+
self._member_names: list[str] = []
|
|
117
|
+
|
|
118
|
+
def __getitem__(self, key: str) -> Any:
|
|
119
|
+
if key == "iota":
|
|
120
|
+
val = _IotaCounter(self._counter)
|
|
121
|
+
self._counter += 1
|
|
122
|
+
return val
|
|
123
|
+
return super().__getitem__(key)
|
|
124
|
+
|
|
125
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
126
|
+
super().__setitem__(key, value)
|
|
127
|
+
if (
|
|
128
|
+
not key.startswith("_")
|
|
129
|
+
and key != "iota"
|
|
130
|
+
and isinstance(value, (int, float, str, _IotaCounter))
|
|
131
|
+
):
|
|
132
|
+
if key not in self._member_names:
|
|
133
|
+
self._member_names.append(key)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class IotaEnumMeta(type):
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def __prepare__(mcs, name: str, bases: tuple[type, ...], **kwargs: Any) -> IotaNamespace: # type: ignore[override]
|
|
140
|
+
return IotaNamespace()
|
|
141
|
+
|
|
142
|
+
def __new__(
|
|
143
|
+
mcs,
|
|
144
|
+
name: str,
|
|
145
|
+
bases: tuple[type, ...],
|
|
146
|
+
namespace: IotaNamespace, # type: ignore[override]
|
|
147
|
+
**kwargs: Any,
|
|
148
|
+
) -> IotaEnumMeta:
|
|
149
|
+
member_names = namespace._member_names if isinstance(namespace, IotaNamespace) else []
|
|
150
|
+
members: OrderedDict[str, Any] = OrderedDict()
|
|
151
|
+
for mname in member_names:
|
|
152
|
+
val = namespace[mname] if mname in namespace else 0
|
|
153
|
+
members[mname] = int(val) if isinstance(val, _IotaCounter) else val
|
|
154
|
+
|
|
155
|
+
cls = super().__new__(mcs, name, bases, dict(namespace))
|
|
156
|
+
cls._members_ = members # type: ignore[attr-defined]
|
|
157
|
+
cls._aliases_: dict[str, str] = {} # type: ignore[attr-defined]
|
|
158
|
+
|
|
159
|
+
is_bitflag = any(
|
|
160
|
+
hasattr(b, "_is_bitflag_base") for b in bases
|
|
161
|
+
)
|
|
162
|
+
is_string_enum = any(
|
|
163
|
+
hasattr(b, "_is_string_enum_base") for b in bases
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
is_ordered = namespace.get("_ordered_", False) or any(
|
|
167
|
+
getattr(b, "_ordered_", False) for b in bases
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if is_string_enum:
|
|
171
|
+
fmt = namespace.get("_format_", None)
|
|
172
|
+
for mname, mval in list(members.items()):
|
|
173
|
+
if isinstance(mval, _IotaCounter) or isinstance(mval, int):
|
|
174
|
+
if fmt:
|
|
175
|
+
members[mname] = fmt.format(name=mname, index=mval if isinstance(mval, int) else int(mval))
|
|
176
|
+
else:
|
|
177
|
+
members[mname] = mname
|
|
178
|
+
type.__setattr__(cls, mname, members[mname])
|
|
179
|
+
elif is_bitflag:
|
|
180
|
+
for mname, mval in members.items():
|
|
181
|
+
flag = BitFlag(int(mval), mname)
|
|
182
|
+
members[mname] = flag
|
|
183
|
+
type.__setattr__(cls, mname, flag)
|
|
184
|
+
elif is_ordered:
|
|
185
|
+
for order, (mname, mval) in builtins_enumerate(members.items()):
|
|
186
|
+
proxy = _EnumMemberProxy(mname, mval, cls, order)
|
|
187
|
+
members[mname] = proxy
|
|
188
|
+
type.__setattr__(cls, mname, proxy)
|
|
189
|
+
else:
|
|
190
|
+
for mname, mval in members.items():
|
|
191
|
+
type.__setattr__(cls, mname, mval)
|
|
192
|
+
|
|
193
|
+
doc_lines = [f"{name} enum members:"]
|
|
194
|
+
for mname, mval in members.items():
|
|
195
|
+
doc_lines.append(f" {mname} = {mval}")
|
|
196
|
+
cls.__doc__ = "\n".join(doc_lines)
|
|
197
|
+
|
|
198
|
+
return cls
|
|
199
|
+
|
|
200
|
+
def __iter__(cls) -> Iterator[tuple[str, int | float]]:
|
|
201
|
+
return iter(cls._members_.items()) # type: ignore[attr-defined]
|
|
202
|
+
|
|
203
|
+
def __contains__(cls, item: Any) -> bool:
|
|
204
|
+
if isinstance(item, str):
|
|
205
|
+
return item in cls._members_ # type: ignore[attr-defined]
|
|
206
|
+
return item in cls._members_.values() # type: ignore[attr-defined]
|
|
207
|
+
|
|
208
|
+
def __len__(cls) -> int:
|
|
209
|
+
return len(cls._members_) # type: ignore[attr-defined]
|
|
210
|
+
|
|
211
|
+
def __getitem__(cls, key: str) -> int | float:
|
|
212
|
+
return cls._members_[key] # type: ignore[attr-defined]
|
|
213
|
+
|
|
214
|
+
def __setattr__(cls, name: str, value: Any) -> None:
|
|
215
|
+
if hasattr(cls, "_members_") and name in cls._members_: # type: ignore[attr-defined]
|
|
216
|
+
raise AttributeError(f"Cannot modify enum member '{name}'")
|
|
217
|
+
super().__setattr__(name, value)
|
|
218
|
+
|
|
219
|
+
def __delattr__(cls, name: str) -> None:
|
|
220
|
+
if hasattr(cls, "_members_") and name in cls._members_: # type: ignore[attr-defined]
|
|
221
|
+
raise AttributeError(f"Cannot delete enum member '{name}'")
|
|
222
|
+
super().__delattr__(name)
|
|
223
|
+
|
|
224
|
+
def __call__(cls, value: Any) -> Any:
|
|
225
|
+
for name, val in cls._members_.items(): # type: ignore[attr-defined]
|
|
226
|
+
cmp_val = int(val) if isinstance(val, BitFlag) else val
|
|
227
|
+
if cmp_val == value:
|
|
228
|
+
return val
|
|
229
|
+
raise ValueError(f"{value!r} is not a valid value in {cls.__name__}")
|
|
230
|
+
|
|
231
|
+
def __repr__(cls) -> str:
|
|
232
|
+
items = ", ".join(f"{k}={v}" for k, v in cls._members_.items()) # type: ignore[attr-defined]
|
|
233
|
+
return f"<{cls.__name__}: {items}>"
|
|
234
|
+
|
|
235
|
+
def __reduce__(cls) -> tuple[Any, ...]:
|
|
236
|
+
return (cls.__class__, (cls.__name__, cls.__bases__, dict(cls._members_))) # type: ignore[attr-defined]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class _EnumMemberProxy:
|
|
240
|
+
"""Wraps an enum member value to support ordered comparison within the same enum."""
|
|
241
|
+
|
|
242
|
+
__slots__ = ("_name", "_value", "_enum_cls", "_order")
|
|
243
|
+
|
|
244
|
+
def __init__(self, name: str, value: Any, enum_cls: type, order: int) -> None:
|
|
245
|
+
object.__setattr__(self, "_name", name)
|
|
246
|
+
object.__setattr__(self, "_value", value)
|
|
247
|
+
object.__setattr__(self, "_enum_cls", enum_cls)
|
|
248
|
+
object.__setattr__(self, "_order", order)
|
|
249
|
+
|
|
250
|
+
def __int__(self) -> int:
|
|
251
|
+
return int(self._value)
|
|
252
|
+
|
|
253
|
+
def __index__(self) -> int:
|
|
254
|
+
return int(self._value)
|
|
255
|
+
|
|
256
|
+
def __eq__(self, other: Any) -> bool:
|
|
257
|
+
if isinstance(other, _EnumMemberProxy):
|
|
258
|
+
return self._value == other._value
|
|
259
|
+
return self._value == other
|
|
260
|
+
|
|
261
|
+
def __ne__(self, other: Any) -> bool:
|
|
262
|
+
return not self.__eq__(other)
|
|
263
|
+
|
|
264
|
+
def __lt__(self, other: Any) -> bool:
|
|
265
|
+
if isinstance(other, _EnumMemberProxy):
|
|
266
|
+
if self._enum_cls is not other._enum_cls:
|
|
267
|
+
raise TypeError(f"'<' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
268
|
+
return self._order < other._order
|
|
269
|
+
return NotImplemented
|
|
270
|
+
|
|
271
|
+
def __le__(self, other: Any) -> bool:
|
|
272
|
+
if isinstance(other, _EnumMemberProxy):
|
|
273
|
+
if self._enum_cls is not other._enum_cls:
|
|
274
|
+
raise TypeError(f"'<=' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
275
|
+
return self._order <= other._order
|
|
276
|
+
return NotImplemented
|
|
277
|
+
|
|
278
|
+
def __gt__(self, other: Any) -> bool:
|
|
279
|
+
if isinstance(other, _EnumMemberProxy):
|
|
280
|
+
if self._enum_cls is not other._enum_cls:
|
|
281
|
+
raise TypeError(f"'>' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
282
|
+
return self._order > other._order
|
|
283
|
+
return NotImplemented
|
|
284
|
+
|
|
285
|
+
def __ge__(self, other: Any) -> bool:
|
|
286
|
+
if isinstance(other, _EnumMemberProxy):
|
|
287
|
+
if self._enum_cls is not other._enum_cls:
|
|
288
|
+
raise TypeError(f"'>=' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
289
|
+
return self._order >= other._order
|
|
290
|
+
return NotImplemented
|
|
291
|
+
|
|
292
|
+
def __hash__(self) -> int:
|
|
293
|
+
return hash((self._enum_cls, self._value))
|
|
294
|
+
|
|
295
|
+
def __repr__(self) -> str:
|
|
296
|
+
return f"{self._enum_cls.__name__}.{self._name}"
|
|
297
|
+
|
|
298
|
+
def __str__(self) -> str:
|
|
299
|
+
return self._name
|
|
300
|
+
|
|
301
|
+
def __format__(self, format_spec: str) -> str:
|
|
302
|
+
if not format_spec:
|
|
303
|
+
return self._name
|
|
304
|
+
if format_spec == "v":
|
|
305
|
+
return str(self._value)
|
|
306
|
+
if format_spec == "n":
|
|
307
|
+
return self._name
|
|
308
|
+
if format_spec == "r":
|
|
309
|
+
return repr(self)
|
|
310
|
+
if format_spec == "nv":
|
|
311
|
+
return f"{self._name}={self._value}"
|
|
312
|
+
return format(self._value, format_spec)
|
|
313
|
+
|
|
314
|
+
def __bool__(self) -> bool:
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
def __add__(self, other: Any) -> Any:
|
|
318
|
+
return self._value + (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
319
|
+
|
|
320
|
+
def __radd__(self, other: Any) -> Any:
|
|
321
|
+
return other + self._value
|
|
322
|
+
|
|
323
|
+
def __sub__(self, other: Any) -> Any:
|
|
324
|
+
return self._value - (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
325
|
+
|
|
326
|
+
def __rsub__(self, other: Any) -> Any:
|
|
327
|
+
return other - self._value
|
|
328
|
+
|
|
329
|
+
def __mul__(self, other: Any) -> Any:
|
|
330
|
+
return self._value * (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
331
|
+
|
|
332
|
+
def __rmul__(self, other: Any) -> Any:
|
|
333
|
+
return other * self._value
|
|
334
|
+
|
|
335
|
+
def __or__(self, other: Any) -> Any:
|
|
336
|
+
return self._value | (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
337
|
+
|
|
338
|
+
def __ror__(self, other: Any) -> Any:
|
|
339
|
+
return other | self._value
|
|
340
|
+
|
|
341
|
+
def __and__(self, other: Any) -> Any:
|
|
342
|
+
return self._value & (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
343
|
+
|
|
344
|
+
def __rand__(self, other: Any) -> Any:
|
|
345
|
+
return other & self._value
|
|
346
|
+
|
|
347
|
+
def __lshift__(self, other: Any) -> Any:
|
|
348
|
+
return self._value << (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
349
|
+
|
|
350
|
+
def __rlshift__(self, other: Any) -> Any:
|
|
351
|
+
return other << self._value
|
|
352
|
+
|
|
353
|
+
def __rshift__(self, other: Any) -> Any:
|
|
354
|
+
return self._value >> (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
355
|
+
|
|
356
|
+
def __rrshift__(self, other: Any) -> Any:
|
|
357
|
+
return other >> self._value
|
|
358
|
+
|
|
359
|
+
def __reduce__(self) -> tuple[Any, ...]:
|
|
360
|
+
return (_restore_member, (self._enum_cls, self._name))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _restore_member(cls: type, name: str) -> Any:
|
|
364
|
+
return getattr(cls, name)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class IotaEnum(metaclass=IotaEnumMeta):
|
|
368
|
+
|
|
369
|
+
@classmethod
|
|
370
|
+
def names(cls) -> list[str]:
|
|
371
|
+
return list(cls._members_.keys()) # type: ignore[attr-defined]
|
|
372
|
+
|
|
373
|
+
@classmethod
|
|
374
|
+
def values(cls) -> list[Any]:
|
|
375
|
+
return list(cls._members_.values()) # type: ignore[attr-defined]
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def items(cls) -> list[tuple[str, Any]]:
|
|
379
|
+
return list(cls._members_.items()) # type: ignore[attr-defined]
|
|
380
|
+
|
|
381
|
+
@classmethod
|
|
382
|
+
def from_value(cls, value: Any) -> str | None:
|
|
383
|
+
for name, val in cls._members_.items(): # type: ignore[attr-defined]
|
|
384
|
+
if val == value:
|
|
385
|
+
return name
|
|
386
|
+
for alias, target in cls._aliases_.items(): # type: ignore[attr-defined]
|
|
387
|
+
if cls._members_[target] == value: # type: ignore[attr-defined]
|
|
388
|
+
return alias
|
|
389
|
+
return None
|
|
390
|
+
|
|
391
|
+
@classmethod
|
|
392
|
+
def alias(cls, alias_name: str, target_name: str) -> None:
|
|
393
|
+
if target_name not in cls._members_: # type: ignore[attr-defined]
|
|
394
|
+
raise KeyError(f"'{target_name}' is not a member of {cls.__name__}")
|
|
395
|
+
cls._aliases_[alias_name] = target_name # type: ignore[attr-defined]
|
|
396
|
+
type.__setattr__(cls, alias_name, cls._members_[target_name]) # type: ignore[attr-defined]
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def to_dict(cls) -> dict[str, Any]:
|
|
400
|
+
return dict(cls._members_) # type: ignore[attr-defined]
|
|
401
|
+
|
|
402
|
+
@classmethod
|
|
403
|
+
def to_json(cls, **kwargs: Any) -> str:
|
|
404
|
+
d = {}
|
|
405
|
+
for k, v in cls._members_.items(): # type: ignore[attr-defined]
|
|
406
|
+
d[k] = int(v) if isinstance(v, (BitFlag, float)) and not isinstance(v, str) else v
|
|
407
|
+
return json.dumps(d, **kwargs)
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def from_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
|
|
411
|
+
result = {}
|
|
412
|
+
for name, value in data.items():
|
|
413
|
+
if name in cls._members_: # type: ignore[attr-defined]
|
|
414
|
+
result[name] = cls._members_[name] # type: ignore[attr-defined]
|
|
415
|
+
else:
|
|
416
|
+
raise KeyError(f"'{name}' is not a member of {cls.__name__}")
|
|
417
|
+
return result
|
|
418
|
+
|
|
419
|
+
@classmethod
|
|
420
|
+
def from_json(cls, s: str) -> dict[str, Any]:
|
|
421
|
+
return cls.from_dict(json.loads(s))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class IotaBitFlags(metaclass=IotaEnumMeta):
|
|
425
|
+
_is_bitflag_base = True
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def names(cls) -> list[str]:
|
|
429
|
+
return list(cls._members_.keys()) # type: ignore[attr-defined]
|
|
430
|
+
|
|
431
|
+
@classmethod
|
|
432
|
+
def values(cls) -> list[BitFlag]:
|
|
433
|
+
return list(cls._members_.values()) # type: ignore[attr-defined]
|
|
434
|
+
|
|
435
|
+
@classmethod
|
|
436
|
+
def items(cls) -> list[tuple[str, BitFlag]]:
|
|
437
|
+
return list(cls._members_.items()) # type: ignore[attr-defined]
|
|
438
|
+
|
|
439
|
+
@classmethod
|
|
440
|
+
def from_value(cls, value: int) -> str | None:
|
|
441
|
+
for name, flag in cls._members_.items(): # type: ignore[attr-defined]
|
|
442
|
+
if int(flag) == value:
|
|
443
|
+
return name
|
|
444
|
+
return None
|
|
445
|
+
|
|
446
|
+
@classmethod
|
|
447
|
+
def to_dict(cls) -> dict[str, int]:
|
|
448
|
+
return {k: int(v) for k, v in cls._members_.items()} # type: ignore[attr-defined]
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def to_json(cls, **kwargs: Any) -> str:
|
|
452
|
+
return json.dumps(cls.to_dict(), **kwargs)
|
|
453
|
+
|
|
454
|
+
@classmethod
|
|
455
|
+
def from_dict(cls, data: dict[str, Any]) -> dict[str, BitFlag]:
|
|
456
|
+
result = {}
|
|
457
|
+
for name in data:
|
|
458
|
+
if name in cls._members_: # type: ignore[attr-defined]
|
|
459
|
+
result[name] = cls._members_[name] # type: ignore[attr-defined]
|
|
460
|
+
else:
|
|
461
|
+
raise KeyError(f"'{name}' is not a member of {cls.__name__}")
|
|
462
|
+
return result
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def from_json(cls, s: str) -> dict[str, BitFlag]:
|
|
466
|
+
return cls.from_dict(json.loads(s))
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class IotaStringEnum(IotaEnum):
|
|
470
|
+
_is_string_enum_base = True
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class FlagScope:
|
|
474
|
+
"""Context manager for scoped bitflag operations."""
|
|
475
|
+
|
|
476
|
+
def __init__(self, initial: BitFlag | int = 0) -> None:
|
|
477
|
+
self._flags = initial if isinstance(initial, BitFlag) else BitFlag(initial)
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def flags(self) -> BitFlag:
|
|
481
|
+
return self._flags
|
|
482
|
+
|
|
483
|
+
def grant(self, *flags: BitFlag | int) -> None:
|
|
484
|
+
for f in flags:
|
|
485
|
+
self._flags = self._flags | f
|
|
486
|
+
|
|
487
|
+
def revoke(self, *flags: BitFlag | int) -> None:
|
|
488
|
+
for f in flags:
|
|
489
|
+
fv = int(f)
|
|
490
|
+
self._flags = BitFlag(int(self._flags) & ~fv)
|
|
491
|
+
|
|
492
|
+
def has(self, flag: BitFlag | int) -> bool:
|
|
493
|
+
return self._flags.has(flag)
|
|
494
|
+
|
|
495
|
+
def __enter__(self) -> FlagScope:
|
|
496
|
+
return self
|
|
497
|
+
|
|
498
|
+
def __exit__(self, *args: Any) -> None:
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
def __repr__(self) -> str:
|
|
502
|
+
return f"FlagScope({self._flags})"
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def iota_enum(cls: type | None = None, *, ordered: bool = False) -> Any:
|
|
506
|
+
"""Decorator to convert a plain class into an IotaEnum.
|
|
507
|
+
|
|
508
|
+
Usage:
|
|
509
|
+
@iota_enum
|
|
510
|
+
class Color:
|
|
511
|
+
Red = 0
|
|
512
|
+
Green = 1
|
|
513
|
+
Blue = 2
|
|
514
|
+
|
|
515
|
+
@iota_enum(ordered=True)
|
|
516
|
+
class Priority:
|
|
517
|
+
Low = 0
|
|
518
|
+
Medium = 1
|
|
519
|
+
High = 2
|
|
520
|
+
"""
|
|
521
|
+
def _wrap(klass: type) -> IotaEnumMeta:
|
|
522
|
+
ns = IotaNamespace()
|
|
523
|
+
if ordered:
|
|
524
|
+
ns["_ordered_"] = True
|
|
525
|
+
for key, val in vars(klass).items():
|
|
526
|
+
if key.startswith("_"):
|
|
527
|
+
continue
|
|
528
|
+
if callable(val) and not isinstance(val, (int, float, str)):
|
|
529
|
+
continue
|
|
530
|
+
ns[key] = val
|
|
531
|
+
ns._member_names.append(key)
|
|
532
|
+
return IotaEnumMeta(klass.__name__, (IotaEnum,), ns)
|
|
533
|
+
|
|
534
|
+
if cls is not None:
|
|
535
|
+
return _wrap(cls)
|
|
536
|
+
return _wrap
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
import builtins
|
|
540
|
+
builtins_enumerate = builtins.enumerate
|