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.
Files changed (26) hide show
  1. {pythoniota-1.0.0 → pythoniota-1.2.0}/PKG-INFO +1 -1
  2. {pythoniota-1.0.0 → pythoniota-1.2.0}/pyproject.toml +1 -1
  3. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/PKG-INFO +1 -1
  4. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/SOURCES.txt +7 -1
  5. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/__init__.py +10 -2
  6. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/_bitflag.py +28 -0
  7. pythoniota-1.2.0/src/pythoniota/enum.py +540 -0
  8. pythoniota-1.2.0/src/pythoniota/recipes.py +236 -0
  9. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/sequence.py +138 -0
  10. pythoniota-1.2.0/tests/test_advanced.py +377 -0
  11. pythoniota-1.2.0/tests/test_enum_enhanced.py +89 -0
  12. {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_integration.py +1 -1
  13. pythoniota-1.2.0/tests/test_recipes.py +130 -0
  14. pythoniota-1.2.0/tests/test_serialization.py +86 -0
  15. pythoniota-1.2.0/tests/test_string_enum.py +114 -0
  16. pythoniota-1.0.0/src/pythoniota/enum.py +0 -239
  17. {pythoniota-1.0.0 → pythoniota-1.2.0}/setup.cfg +0 -0
  18. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/dependency_links.txt +0 -0
  19. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/PythonIota.egg-info/top_level.txt +0 -0
  20. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/_compat.py +0 -0
  21. {pythoniota-1.0.0 → pythoniota-1.2.0}/src/pythoniota/_safe_eval.py +0 -0
  22. {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_bitflags.py +0 -0
  23. {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_compat.py +0 -0
  24. {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_enum.py +0 -0
  25. {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_safe_eval.py +0 -0
  26. {pythoniota-1.0.0 → pythoniota-1.2.0}/tests/test_sequence.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PythonIota
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Go-style iota enumerations and flexible sequence generators for Python
5
5
  Author: Equinox
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PythonIota"
7
- version = "1.0.0"
7
+ version = "1.2.0"
8
8
  description = "Go-style iota enumerations and flexible sequence generators for Python"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PythonIota
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Go-style iota enumerations and flexible sequence generators for Python
5
5
  Author: Equinox
6
6
  License: MIT
@@ -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 IotaEnum, IotaBitFlags
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.0.0"
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