enumerific 1.0.1__py3-none-any.whl → 1.0.2__py3-none-any.whl
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.
- enumerific/__init__.py +1 -0
- enumerific/extensible.py +381 -39
- enumerific/version.txt +1 -1
- enumerific-1.0.2.dist-info/METADATA +794 -0
- enumerific-1.0.2.dist-info/RECORD +12 -0
- enumerific-1.0.1.dist-info/METADATA +0 -198
- enumerific-1.0.1.dist-info/RECORD +0 -12
- {enumerific-1.0.1.dist-info → enumerific-1.0.2.dist-info}/WHEEL +0 -0
- {enumerific-1.0.1.dist-info → enumerific-1.0.2.dist-info}/licenses/LICENSE.md +0 -0
- {enumerific-1.0.1.dist-info → enumerific-1.0.2.dist-info}/top_level.txt +0 -0
- {enumerific-1.0.1.dist-info → enumerific-1.0.2.dist-info}/zip-safe +0 -0
enumerific/extensible.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
|
+
import collections
|
|
4
5
|
|
|
5
6
|
from enumerific.logging import logger
|
|
6
7
|
|
|
@@ -17,7 +18,71 @@ from types import MappingProxyType
|
|
|
17
18
|
logger = logger.getChild(__name__)
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class
|
|
21
|
+
class anno(collections.abc.Mapping):
|
|
22
|
+
"""The annotations class supports adding annotations to an Enumeration option."""
|
|
23
|
+
|
|
24
|
+
_value: object = None
|
|
25
|
+
_annotations: dict[str, object] = None
|
|
26
|
+
|
|
27
|
+
def __init__(self, value: object, **annotations: dict[str, object]):
|
|
28
|
+
self._value: object = value
|
|
29
|
+
|
|
30
|
+
for key, value in annotations.items():
|
|
31
|
+
if not isinstance(key, str):
|
|
32
|
+
raise TypeError("All annotation values must have string keys!")
|
|
33
|
+
|
|
34
|
+
self._annotations: dict[str, object] = annotations
|
|
35
|
+
|
|
36
|
+
def __len__(self) -> int:
|
|
37
|
+
return len(self._annotations)
|
|
38
|
+
|
|
39
|
+
def __iter__(self) -> str:
|
|
40
|
+
for key in self._annotations.keys():
|
|
41
|
+
yield key
|
|
42
|
+
|
|
43
|
+
def __contains__(self, other: object) -> bool:
|
|
44
|
+
return other in self._annotations
|
|
45
|
+
|
|
46
|
+
def __getitem__(self, key: str) -> object | None:
|
|
47
|
+
if key in self._annotations:
|
|
48
|
+
return self._annotations[key]
|
|
49
|
+
else:
|
|
50
|
+
raise KeyError(f"The annotation does not have an '{key}' item!")
|
|
51
|
+
|
|
52
|
+
def __setitem__(self, key: str, value: object):
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
|
|
55
|
+
def __delitem__(self, key: str, value: object):
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
|
|
58
|
+
def __getattr__(self, name: str) -> object | None:
|
|
59
|
+
if name.startswith("_"):
|
|
60
|
+
return super().__getattr__(name)
|
|
61
|
+
elif name in self._annotations:
|
|
62
|
+
return self._annotations[name]
|
|
63
|
+
else:
|
|
64
|
+
raise AttributeError(f"The annotation does not have an '{name}' attribute!")
|
|
65
|
+
|
|
66
|
+
def __setattr__(self, name: str, value: object):
|
|
67
|
+
if name.startswith("_"):
|
|
68
|
+
return super().__setattr__(name, value)
|
|
69
|
+
else:
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
|
|
72
|
+
def __detattr__(self, name: str):
|
|
73
|
+
raise NotImplementedError
|
|
74
|
+
|
|
75
|
+
def get(self, name: str, default: object = None) -> object | None:
|
|
76
|
+
if name in self._annotations:
|
|
77
|
+
return self._annotations[name]
|
|
78
|
+
else:
|
|
79
|
+
return default
|
|
80
|
+
|
|
81
|
+
def unwrap(self) -> object:
|
|
82
|
+
return self._value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class auto(int, anno):
|
|
21
86
|
"""Generate an automatically inrementing integer each time the class is instantiated
|
|
22
87
|
based on the previously supplied configuration, which allows the start and steps to
|
|
23
88
|
be configured as well as if the integers should be generated as powers/flags."""
|
|
@@ -32,6 +97,7 @@ class auto(int):
|
|
|
32
97
|
cls,
|
|
33
98
|
start: int = None,
|
|
34
99
|
steps: int = None,
|
|
100
|
+
times: int = None,
|
|
35
101
|
power: int | bool = None,
|
|
36
102
|
flags: bool = None,
|
|
37
103
|
):
|
|
@@ -57,6 +123,15 @@ class auto(int):
|
|
|
57
123
|
"The 'steps' argument, if specified, must have a positive integer value!"
|
|
58
124
|
)
|
|
59
125
|
|
|
126
|
+
if times is None:
|
|
127
|
+
times = 0
|
|
128
|
+
elif isinstance(times, int) and times >= 0:
|
|
129
|
+
pass
|
|
130
|
+
else:
|
|
131
|
+
raise TypeError(
|
|
132
|
+
"The 'times' argument, if specified, must have a positive integer value!"
|
|
133
|
+
)
|
|
134
|
+
|
|
60
135
|
if power is None:
|
|
61
136
|
power = 0
|
|
62
137
|
elif isinstance(power, bool):
|
|
@@ -90,17 +165,21 @@ class auto(int):
|
|
|
90
165
|
|
|
91
166
|
cls.steps = steps
|
|
92
167
|
|
|
168
|
+
cls.times = times
|
|
169
|
+
|
|
93
170
|
cls.power = power
|
|
94
171
|
|
|
95
172
|
cls.value = cls.start
|
|
96
173
|
|
|
97
|
-
def __new__(cls):
|
|
174
|
+
def __new__(cls, **annotations: dict[str, object]):
|
|
98
175
|
"""Create a new integer (int) instance upon each call, incrementing the value as
|
|
99
176
|
per the configuration defined before this method is called; the configuration
|
|
100
177
|
can be changed at any time and the next call to this method will generate the
|
|
101
178
|
next value based on the most recently specified configuration options."""
|
|
102
179
|
|
|
103
|
-
if cls.
|
|
180
|
+
if cls.times > 0:
|
|
181
|
+
value = cls.value * cls.times
|
|
182
|
+
elif cls.power > 0:
|
|
104
183
|
value = pow(cls.power, (cls.value - 1))
|
|
105
184
|
else:
|
|
106
185
|
value = cls.value
|
|
@@ -109,6 +188,9 @@ class auto(int):
|
|
|
109
188
|
|
|
110
189
|
return super().__new__(cls, value)
|
|
111
190
|
|
|
191
|
+
def __init__(self, **annotations: dict[str, object]):
|
|
192
|
+
super().__init__(self.value, **annotations)
|
|
193
|
+
|
|
112
194
|
|
|
113
195
|
class EnumerationConfiguration(object):
|
|
114
196
|
"""The EnumerationConfiguration class holds the Enumeration configuration options"""
|
|
@@ -121,6 +203,8 @@ class EnumerationConfiguration(object):
|
|
|
121
203
|
_raises: bool = None
|
|
122
204
|
_flags: bool = None
|
|
123
205
|
_start: int = None
|
|
206
|
+
_steps: int = None
|
|
207
|
+
_times: int = None
|
|
124
208
|
_typecast: bool = None
|
|
125
209
|
|
|
126
210
|
def __init__(
|
|
@@ -133,6 +217,8 @@ class EnumerationConfiguration(object):
|
|
|
133
217
|
raises: bool = None,
|
|
134
218
|
flags: bool = None,
|
|
135
219
|
start: int = None,
|
|
220
|
+
steps: int = None,
|
|
221
|
+
times: int = None,
|
|
136
222
|
typecast: bool = None,
|
|
137
223
|
):
|
|
138
224
|
self.unique = unique
|
|
@@ -143,6 +229,8 @@ class EnumerationConfiguration(object):
|
|
|
143
229
|
self.raises = raises
|
|
144
230
|
self.flags = flags
|
|
145
231
|
self.start = start
|
|
232
|
+
self.steps = steps
|
|
233
|
+
self.times = times
|
|
146
234
|
self.typecast = typecast
|
|
147
235
|
|
|
148
236
|
def __dir__(self) -> list[str]:
|
|
@@ -155,6 +243,8 @@ class EnumerationConfiguration(object):
|
|
|
155
243
|
"raises",
|
|
156
244
|
"flags",
|
|
157
245
|
"start",
|
|
246
|
+
"steps",
|
|
247
|
+
"times",
|
|
158
248
|
"typecast",
|
|
159
249
|
]
|
|
160
250
|
|
|
@@ -291,7 +381,7 @@ class EnumerationConfiguration(object):
|
|
|
291
381
|
self._flags = flags
|
|
292
382
|
|
|
293
383
|
@property
|
|
294
|
-
def start(self) ->
|
|
384
|
+
def start(self) -> int | None:
|
|
295
385
|
return self._start
|
|
296
386
|
|
|
297
387
|
@start.setter
|
|
@@ -304,6 +394,34 @@ class EnumerationConfiguration(object):
|
|
|
304
394
|
)
|
|
305
395
|
self._start = start
|
|
306
396
|
|
|
397
|
+
@property
|
|
398
|
+
def steps(self) -> int | None:
|
|
399
|
+
return self._steps
|
|
400
|
+
|
|
401
|
+
@steps.setter
|
|
402
|
+
def steps(self, steps: int | None):
|
|
403
|
+
if steps is None:
|
|
404
|
+
pass
|
|
405
|
+
elif not (isinstance(steps, int) and steps >= 0):
|
|
406
|
+
raise TypeError(
|
|
407
|
+
"The 'steps' argument, if specified, must have a positive integer value!"
|
|
408
|
+
)
|
|
409
|
+
self._steps = steps
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def times(self) -> int | None:
|
|
413
|
+
return self._times
|
|
414
|
+
|
|
415
|
+
@times.setter
|
|
416
|
+
def times(self, times: int | None):
|
|
417
|
+
if times is None:
|
|
418
|
+
pass
|
|
419
|
+
elif not (isinstance(times, int) and times >= 0):
|
|
420
|
+
raise TypeError(
|
|
421
|
+
"The 'times' argument, if specified, must have a positive integer value!"
|
|
422
|
+
)
|
|
423
|
+
self._times = times
|
|
424
|
+
|
|
307
425
|
@property
|
|
308
426
|
def typecast(self) -> bool | None:
|
|
309
427
|
return self._typecast
|
|
@@ -351,6 +469,8 @@ class EnumerationMetaClass(type):
|
|
|
351
469
|
raises: bool = None,
|
|
352
470
|
flags: bool = None,
|
|
353
471
|
start: int = None,
|
|
472
|
+
steps: int = None,
|
|
473
|
+
times: int = None,
|
|
354
474
|
typecast: bool = None,
|
|
355
475
|
**kwargs,
|
|
356
476
|
) -> dict:
|
|
@@ -361,7 +481,7 @@ class EnumerationMetaClass(type):
|
|
|
361
481
|
any other keyword arguments that are included in the class signature call."""
|
|
362
482
|
|
|
363
483
|
logger.debug(
|
|
364
|
-
"[EnumerationMetaClass] %s.__prepare__(name: %s, bases: %s, unique: %s, aliased: %s, overwritable: %s, subclassable: %s, removable: %s, raises: %s, flags: %s, start: %s, typecast: %s, kwargs: %s)",
|
|
484
|
+
"[EnumerationMetaClass] %s.__prepare__(name: %s, bases: %s, unique: %s, aliased: %s, overwritable: %s, subclassable: %s, removable: %s, raises: %s, flags: %s, start: %s, steps: %s, times: %s, typecast: %s, kwargs: %s)",
|
|
365
485
|
name,
|
|
366
486
|
name,
|
|
367
487
|
bases,
|
|
@@ -373,6 +493,8 @@ class EnumerationMetaClass(type):
|
|
|
373
493
|
raises,
|
|
374
494
|
flags,
|
|
375
495
|
start,
|
|
496
|
+
steps,
|
|
497
|
+
times,
|
|
376
498
|
typecast,
|
|
377
499
|
kwargs,
|
|
378
500
|
)
|
|
@@ -430,9 +552,27 @@ class EnumerationMetaClass(type):
|
|
|
430
552
|
"The 'start' argument, if specified, must have a positive integer value!"
|
|
431
553
|
)
|
|
432
554
|
|
|
555
|
+
if steps is None:
|
|
556
|
+
pass
|
|
557
|
+
elif isinstance(steps, int) and steps >= 1:
|
|
558
|
+
pass
|
|
559
|
+
else:
|
|
560
|
+
raise TypeError(
|
|
561
|
+
"The 'steps' argument, if specified, must have a positive integer value!"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
if times is None:
|
|
565
|
+
pass
|
|
566
|
+
elif isinstance(times, int) and times >= 1:
|
|
567
|
+
pass
|
|
568
|
+
else:
|
|
569
|
+
raise TypeError(
|
|
570
|
+
"The 'times' argument, if specified, must have a positive integer value!"
|
|
571
|
+
)
|
|
572
|
+
|
|
433
573
|
# Configure the auto() class for subsequent use, resetting the sequence, setting
|
|
434
574
|
# the new start value, and whether values should be flag values (powers of 2)
|
|
435
|
-
auto.configure(start=start, flags=flags)
|
|
575
|
+
auto.configure(start=start, steps=steps, times=times, flags=flags)
|
|
436
576
|
|
|
437
577
|
return dict()
|
|
438
578
|
|
|
@@ -447,6 +587,8 @@ class EnumerationMetaClass(type):
|
|
|
447
587
|
raises: bool = None, # False
|
|
448
588
|
flags: bool = None, # False
|
|
449
589
|
start: int = None, # None
|
|
590
|
+
steps: int = None, # None
|
|
591
|
+
times: int = None, # None
|
|
450
592
|
typecast: bool = None, # True
|
|
451
593
|
**kwargs,
|
|
452
594
|
):
|
|
@@ -513,6 +655,20 @@ class EnumerationMetaClass(type):
|
|
|
513
655
|
"The 'start' argument, if specified, must have a positive integer value!"
|
|
514
656
|
)
|
|
515
657
|
|
|
658
|
+
if steps is None:
|
|
659
|
+
pass
|
|
660
|
+
elif not (isinstance(steps, int) and steps >= 0):
|
|
661
|
+
raise TypeError(
|
|
662
|
+
"The 'steps' argument, if specified, must have a positive integer value!"
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if times is None:
|
|
666
|
+
pass
|
|
667
|
+
elif not (isinstance(times, int) and times >= 0):
|
|
668
|
+
raise TypeError(
|
|
669
|
+
"The 'times' argument, if specified, must have a positive integer value!"
|
|
670
|
+
)
|
|
671
|
+
|
|
516
672
|
if typecast is None:
|
|
517
673
|
pass
|
|
518
674
|
elif not isinstance(typecast, bool):
|
|
@@ -529,6 +685,8 @@ class EnumerationMetaClass(type):
|
|
|
529
685
|
raises=raises,
|
|
530
686
|
flags=flags,
|
|
531
687
|
start=start,
|
|
688
|
+
steps=steps,
|
|
689
|
+
times=times,
|
|
532
690
|
typecast=typecast,
|
|
533
691
|
)
|
|
534
692
|
|
|
@@ -543,6 +701,7 @@ class EnumerationMetaClass(type):
|
|
|
543
701
|
return super().__new__(cls, *args, **kwargs)
|
|
544
702
|
|
|
545
703
|
enumerations: dict[str, object] = {} # Keep track of the enumeration options
|
|
704
|
+
annotations: dict[str, dict] = {} # Keep track of the enumeration annotations
|
|
546
705
|
|
|
547
706
|
names: list[object] = [] # Keep track of the option names to check uniqueness
|
|
548
707
|
values: list[object] = [] # Keep track of the option values to check uniqueness
|
|
@@ -605,6 +764,12 @@ class EnumerationMetaClass(type):
|
|
|
605
764
|
logger.debug(
|
|
606
765
|
" >>> start => %s", base_configuration.start
|
|
607
766
|
)
|
|
767
|
+
logger.debug(
|
|
768
|
+
" >>> steps => %s", base_configuration.steps
|
|
769
|
+
)
|
|
770
|
+
logger.debug(
|
|
771
|
+
" >>> times => %s", base_configuration.times
|
|
772
|
+
)
|
|
608
773
|
logger.debug(
|
|
609
774
|
" >>> typecast => %s", base_configuration.typecast
|
|
610
775
|
)
|
|
@@ -646,6 +811,12 @@ class EnumerationMetaClass(type):
|
|
|
646
811
|
logger.debug(
|
|
647
812
|
" >>> (updated) start => %s", configuration.start
|
|
648
813
|
)
|
|
814
|
+
logger.debug(
|
|
815
|
+
" >>> (updated) steps => %s", configuration.steps
|
|
816
|
+
)
|
|
817
|
+
logger.debug(
|
|
818
|
+
" >>> (updated) times => %s", configuration.times
|
|
819
|
+
)
|
|
649
820
|
logger.debug(
|
|
650
821
|
" >>> (updated) typecast => %s", configuration.typecast
|
|
651
822
|
)
|
|
@@ -681,6 +852,8 @@ class EnumerationMetaClass(type):
|
|
|
681
852
|
raises=False,
|
|
682
853
|
flags=False,
|
|
683
854
|
start=1,
|
|
855
|
+
steps=1,
|
|
856
|
+
times=None,
|
|
684
857
|
typecast=True,
|
|
685
858
|
)
|
|
686
859
|
|
|
@@ -698,6 +871,8 @@ class EnumerationMetaClass(type):
|
|
|
698
871
|
logger.debug(" >>> (after defaults) raises => %s", configuration.raises)
|
|
699
872
|
logger.debug(" >>> (after defaults) flags => %s", configuration.flags)
|
|
700
873
|
logger.debug(" >>> (after defaults) start => %s", configuration.start)
|
|
874
|
+
logger.debug(" >>> (after defaults) steps => %s", configuration.steps)
|
|
875
|
+
logger.debug(" >>> (after defaults) times => %s", configuration.times)
|
|
701
876
|
logger.debug(" >>> (after defaults) typecast => %s", configuration.typecast)
|
|
702
877
|
|
|
703
878
|
# Iterate over the class attributes, looking for any enumeration options
|
|
@@ -709,6 +884,12 @@ class EnumerationMetaClass(type):
|
|
|
709
884
|
% (index, attribute, value, type(value))
|
|
710
885
|
)
|
|
711
886
|
|
|
887
|
+
if isinstance(value, auto):
|
|
888
|
+
annotations[attribute] = value
|
|
889
|
+
elif isinstance(value, anno):
|
|
890
|
+
annotations[attribute] = value
|
|
891
|
+
value = value.unwrap() # unwrap the annotated value
|
|
892
|
+
|
|
712
893
|
if attribute.startswith("_") or attribute in cls._special:
|
|
713
894
|
continue
|
|
714
895
|
elif attribute in names:
|
|
@@ -730,8 +911,8 @@ class EnumerationMetaClass(type):
|
|
|
730
911
|
)
|
|
731
912
|
else:
|
|
732
913
|
raise EnumerationNonUniqueError(
|
|
733
|
-
"The enumeration option, '%s', has a non-unique value, %r, however, unless either the keyword argument 'unique=False' or 'aliased=True' are passed during class construction, all enumeration options must have unique values!"
|
|
734
|
-
% (attribute, value)
|
|
914
|
+
"The enumeration option, '%s', has a non-unique value, %r, however, unless either the keyword argument 'unique=False' or 'aliased=True' are passed during class construction, all enumeration options must have unique values; existing values: %s!"
|
|
915
|
+
% (attribute, value, values)
|
|
735
916
|
)
|
|
736
917
|
else:
|
|
737
918
|
logger.debug(
|
|
@@ -763,6 +944,8 @@ class EnumerationMetaClass(type):
|
|
|
763
944
|
if isinstance(_enumerations, dict):
|
|
764
945
|
attributes["base_enumerations"] = _enumerations
|
|
765
946
|
|
|
947
|
+
attributes["annotations"] = annotations
|
|
948
|
+
|
|
766
949
|
# If the new enumeration class is not subclassing an existing enumeration class
|
|
767
950
|
if configuration.typecast is True and (
|
|
768
951
|
(baseclass is None) or (baseclass is Enumeration)
|
|
@@ -840,6 +1023,9 @@ class EnumerationMetaClass(type):
|
|
|
840
1023
|
logger.debug(" >>> removable => %s", configuration.removable)
|
|
841
1024
|
logger.debug(" >>> raises => %s", configuration.raises)
|
|
842
1025
|
logger.debug(" >>> flags => %s", configuration.flags)
|
|
1026
|
+
logger.debug(" >>> start => %s", configuration.start)
|
|
1027
|
+
logger.debug(" >>> steps => %s", configuration.steps)
|
|
1028
|
+
logger.debug(" >>> times => %s", configuration.times)
|
|
843
1029
|
logger.debug(" >>> typecast => %s", configuration.typecast)
|
|
844
1030
|
|
|
845
1031
|
# Store the enumeration class configuration options for future reference
|
|
@@ -876,6 +1062,8 @@ class EnumerationMetaClass(type):
|
|
|
876
1062
|
logger.debug("+" * 100)
|
|
877
1063
|
|
|
878
1064
|
if isinstance(enumerations := attributes.get("enumerations"), dict):
|
|
1065
|
+
annotations: dict[str, anno] = attributes.get("annotations") or {}
|
|
1066
|
+
|
|
879
1067
|
for attribute, value in enumerations.items():
|
|
880
1068
|
if attribute in self._enumerations:
|
|
881
1069
|
continue
|
|
@@ -913,6 +1101,7 @@ class EnumerationMetaClass(type):
|
|
|
913
1101
|
enumeration=self,
|
|
914
1102
|
name=attribute,
|
|
915
1103
|
value=value,
|
|
1104
|
+
annotations=annotations.get(attribute),
|
|
916
1105
|
)
|
|
917
1106
|
|
|
918
1107
|
logger.debug(
|
|
@@ -935,8 +1124,9 @@ class EnumerationMetaClass(type):
|
|
|
935
1124
|
elif self._enumerations and name in self._enumerations:
|
|
936
1125
|
return self._enumerations[name]
|
|
937
1126
|
else:
|
|
1127
|
+
# EnumerationOptionError subclasses AttributeError so we adhere to convention
|
|
938
1128
|
raise EnumerationOptionError(
|
|
939
|
-
"The '%s' enumeration class, has no '%s' enumeration option!"
|
|
1129
|
+
"The '%s' enumeration class, has no '%s' enumeration option nor annotation property!"
|
|
940
1130
|
% (self.__name__, name)
|
|
941
1131
|
)
|
|
942
1132
|
|
|
@@ -953,6 +1143,10 @@ class EnumerationMetaClass(type):
|
|
|
953
1143
|
return members
|
|
954
1144
|
|
|
955
1145
|
def __contains__(self, other: Enumeration | object) -> bool:
|
|
1146
|
+
logger.debug(
|
|
1147
|
+
"%s(%s).__contains__(other: %s)", self.__class__.__name__, self, other
|
|
1148
|
+
)
|
|
1149
|
+
|
|
956
1150
|
contains: bool = False
|
|
957
1151
|
|
|
958
1152
|
for name, enumeration in self._enumerations.items():
|
|
@@ -1175,11 +1369,12 @@ class EnumerationMetaClass(type):
|
|
|
1175
1369
|
self,
|
|
1176
1370
|
value: Enumeration | object = None,
|
|
1177
1371
|
name: str = None,
|
|
1372
|
+
caselessly: bool = False,
|
|
1178
1373
|
) -> Enumeration | None:
|
|
1179
1374
|
"""The 'reconcile' method can be used to reconcile Enumeration type, enumeration
|
|
1180
1375
|
values, or enumeration names to their matching Enumeration type instances. If a
|
|
1181
|
-
match is found the Enumeration type instance will be returned otherwise None
|
|
1182
|
-
|
|
1376
|
+
match is found the Enumeration type instance will be returned otherwise None will
|
|
1377
|
+
be returned, unless the class is configured to raise an error for mismatches."""
|
|
1183
1378
|
|
|
1184
1379
|
if name is None and value is None:
|
|
1185
1380
|
raise ValueError(
|
|
@@ -1197,14 +1392,20 @@ class EnumerationMetaClass(type):
|
|
|
1197
1392
|
reconciled: Enumeration = None
|
|
1198
1393
|
|
|
1199
1394
|
for attribute, enumeration in self._enumerations.items():
|
|
1200
|
-
if isinstance(
|
|
1201
|
-
reconciled = enumeration
|
|
1202
|
-
break
|
|
1203
|
-
elif isinstance(value, Enumeration):
|
|
1395
|
+
if isinstance(value, Enumeration):
|
|
1204
1396
|
if enumeration is value:
|
|
1205
1397
|
reconciled = enumeration
|
|
1206
1398
|
break
|
|
1207
|
-
elif isinstance(
|
|
1399
|
+
elif isinstance(name, str) and (
|
|
1400
|
+
(enumeration.name == name)
|
|
1401
|
+
or (caselessly and (enumeration.name.casefold() == name.casefold()))
|
|
1402
|
+
):
|
|
1403
|
+
reconciled = enumeration
|
|
1404
|
+
break
|
|
1405
|
+
elif isinstance(value, str) and (
|
|
1406
|
+
(enumeration.name == value)
|
|
1407
|
+
or (caselessly and (enumeration.name.casefold() == value.casefold()))
|
|
1408
|
+
):
|
|
1208
1409
|
reconciled = enumeration
|
|
1209
1410
|
break
|
|
1210
1411
|
elif enumeration.value == value:
|
|
@@ -1238,6 +1439,11 @@ class EnumerationMetaClass(type):
|
|
|
1238
1439
|
|
|
1239
1440
|
return not self.reconcile(value=value, name=name) is None
|
|
1240
1441
|
|
|
1442
|
+
def options(self) -> MappingProxyType[str, Enumeration]:
|
|
1443
|
+
"""The 'options' method returns a read-only mapping proxy of the options."""
|
|
1444
|
+
|
|
1445
|
+
return MappingProxyType(self._enumerations)
|
|
1446
|
+
|
|
1241
1447
|
|
|
1242
1448
|
class Enumeration(metaclass=EnumerationMetaClass):
|
|
1243
1449
|
"""The Enumeration class is the subclass of all enumerations and their subtypes."""
|
|
@@ -1245,6 +1451,7 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1245
1451
|
_metaclass: EnumerationMetaClass = None
|
|
1246
1452
|
_enumeration: Enumeration = None
|
|
1247
1453
|
_enumerations: dict[str, Enumeration] = None
|
|
1454
|
+
_annotations: anno = None
|
|
1248
1455
|
_name: str = None
|
|
1249
1456
|
_value: object = None
|
|
1250
1457
|
_aliased: Enumeration = None
|
|
@@ -1257,6 +1464,7 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1257
1464
|
name: str = None,
|
|
1258
1465
|
value: object = None,
|
|
1259
1466
|
aliased: Enumeration = None,
|
|
1467
|
+
annotations: anno = None,
|
|
1260
1468
|
**kwargs,
|
|
1261
1469
|
) -> Enumeration | None:
|
|
1262
1470
|
# Supports reconciling enumeration options via their name/value via __new__ call
|
|
@@ -1264,12 +1472,14 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1264
1472
|
value = args[0]
|
|
1265
1473
|
|
|
1266
1474
|
logger.debug(
|
|
1267
|
-
"[Enumeration] %s.__new__(args: %s, enumeration: %s, name: %s, value: %s, kwargs: %s)",
|
|
1475
|
+
"[Enumeration] %s.__new__(args: %s, enumeration: %s, name: %s, value: %s, aliased: %s, annotations: %s, kwargs: %s)",
|
|
1268
1476
|
cls.__name__,
|
|
1269
1477
|
args,
|
|
1270
1478
|
enumeration,
|
|
1271
1479
|
name,
|
|
1272
1480
|
value,
|
|
1481
|
+
aliased,
|
|
1482
|
+
annotations,
|
|
1273
1483
|
kwargs,
|
|
1274
1484
|
)
|
|
1275
1485
|
|
|
@@ -1298,15 +1508,18 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1298
1508
|
name: str = None,
|
|
1299
1509
|
value: object = None,
|
|
1300
1510
|
aliased: Enumeration = None,
|
|
1511
|
+
annotations: anno = None,
|
|
1301
1512
|
**kwargs,
|
|
1302
1513
|
) -> None:
|
|
1303
1514
|
logger.debug(
|
|
1304
|
-
"[Enumeration] %s.__init__(args: %s, enumeration: %s, name: %s, value: %s, kwargs: %s)",
|
|
1515
|
+
"[Enumeration] %s.__init__(args: %s, enumeration: %s, name: %s, value: %s, aliased: %s, annotations: %s, kwargs: %s)",
|
|
1305
1516
|
self.__class__.__name__,
|
|
1306
1517
|
args,
|
|
1307
1518
|
enumeration,
|
|
1308
1519
|
name,
|
|
1309
1520
|
value,
|
|
1521
|
+
aliased,
|
|
1522
|
+
annotations,
|
|
1310
1523
|
kwargs,
|
|
1311
1524
|
)
|
|
1312
1525
|
|
|
@@ -1340,6 +1553,15 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1340
1553
|
"The 'aliased' argument, if specified, must reference an Enumeration class instance!"
|
|
1341
1554
|
)
|
|
1342
1555
|
|
|
1556
|
+
if annotations is None:
|
|
1557
|
+
pass
|
|
1558
|
+
elif isinstance(annotations, anno):
|
|
1559
|
+
self._annotations = annotations
|
|
1560
|
+
else:
|
|
1561
|
+
raise TypeError(
|
|
1562
|
+
"The 'annotations' argument, if specified, must reference an anno class instance!"
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1343
1565
|
# NOTE: This method is only called if the instance is called via instance(..) syntax
|
|
1344
1566
|
def __call__(self, *args, **kwargs) -> Enumeration | None:
|
|
1345
1567
|
logger.debug(
|
|
@@ -1361,32 +1583,49 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1361
1583
|
return id(self)
|
|
1362
1584
|
|
|
1363
1585
|
def __eq__(self, other: Enumeration | object) -> bool:
|
|
1364
|
-
logger.debug("%s.__eq__(other: %s)"
|
|
1586
|
+
logger.debug("%s(%s).__eq__(other: %s)", self.__class__.__name__, self, other)
|
|
1365
1587
|
|
|
1366
1588
|
equals: bool = False
|
|
1367
1589
|
|
|
1368
1590
|
if isinstance(other, Enumeration):
|
|
1369
|
-
|
|
1370
|
-
|
|
1591
|
+
equals = self is other
|
|
1592
|
+
elif self.name == other:
|
|
1593
|
+
equals = True
|
|
1594
|
+
elif self.value == other:
|
|
1595
|
+
equals = True
|
|
1371
1596
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1597
|
+
return equals
|
|
1598
|
+
|
|
1599
|
+
def __getattr__(self, name) -> object:
|
|
1600
|
+
"""The '__getattr__' method provides support for accessing attribute values that
|
|
1601
|
+
have been assigned to the current enumeration option. If a matching attribute can
|
|
1602
|
+
be found, its value will be returned, otherwise an exception will be raised."""
|
|
1603
|
+
|
|
1604
|
+
logger.debug("%s.__getattr__(name: %s)", self.__class__.__name__, name)
|
|
1605
|
+
|
|
1606
|
+
if name.startswith("_") or name in self.__class__._special or name in dir(self):
|
|
1607
|
+
return object.__getattribute__(self, name)
|
|
1608
|
+
elif self._enumerations and name in self._enumerations:
|
|
1609
|
+
return self._enumerations[name]
|
|
1610
|
+
elif self._annotations and name in self._annotations:
|
|
1611
|
+
return self._annotations[name]
|
|
1612
|
+
else:
|
|
1613
|
+
# EnumerationOptionError subclasses AttributeError so we adhere to convention
|
|
1614
|
+
raise EnumerationOptionError(
|
|
1615
|
+
"The '%s' enumeration class, has no '%s' enumeration option nor annotation property!"
|
|
1616
|
+
% (self.__class__.__name__, name)
|
|
1376
1617
|
)
|
|
1377
1618
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
equals = True
|
|
1384
|
-
break
|
|
1385
|
-
elif enumeration.name == other:
|
|
1386
|
-
equals = True
|
|
1387
|
-
break
|
|
1619
|
+
def get(self, name: str, default: object = None) -> object | None:
|
|
1620
|
+
"""The 'get' method provides support for accessing annotation values that may
|
|
1621
|
+
have been assigned to the current enumeration option. If a matching annotation
|
|
1622
|
+
can be found, its value will be returned, otherwise the default value will be
|
|
1623
|
+
returned, which defaults to None, but may be specified as any value."""
|
|
1388
1624
|
|
|
1389
|
-
|
|
1625
|
+
if name in self._annotations:
|
|
1626
|
+
return self._annotations[name]
|
|
1627
|
+
else:
|
|
1628
|
+
return default
|
|
1390
1629
|
|
|
1391
1630
|
@property
|
|
1392
1631
|
def enumeration(self) -> Enumeration:
|
|
@@ -1407,21 +1646,43 @@ class Enumeration(metaclass=EnumerationMetaClass):
|
|
|
1407
1646
|
@property
|
|
1408
1647
|
def aliased(self) -> bool:
|
|
1409
1648
|
logger.debug(
|
|
1410
|
-
"%s.aliased() >>> id(
|
|
1649
|
+
"%s.aliased() >>> id(%s) => %s (%s)",
|
|
1411
1650
|
self.__class__.__name__,
|
|
1651
|
+
self,
|
|
1412
1652
|
id(self._enumerations),
|
|
1413
1653
|
type(self._enumerations),
|
|
1414
1654
|
)
|
|
1415
1655
|
|
|
1416
1656
|
for name, enumeration in self._enumerations.items():
|
|
1417
|
-
logger.
|
|
1657
|
+
logger.debug(" >>> checking for alias: %s => %s", name, enumeration)
|
|
1418
1658
|
|
|
1419
1659
|
if isinstance(enumeration, Enumeration):
|
|
1420
|
-
if
|
|
1660
|
+
if self is enumeration and enumeration.name != name:
|
|
1421
1661
|
return True
|
|
1422
1662
|
|
|
1423
1663
|
return False
|
|
1424
1664
|
|
|
1665
|
+
@property
|
|
1666
|
+
def aliases(self) -> list[Enumeration]:
|
|
1667
|
+
logger.debug(
|
|
1668
|
+
"%s.aliases() >>> id(%s) => %s (%s)",
|
|
1669
|
+
self.__class__.__name__,
|
|
1670
|
+
self,
|
|
1671
|
+
id(self._enumerations),
|
|
1672
|
+
type(self._enumerations),
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
aliases: list[Enumeration] = []
|
|
1676
|
+
|
|
1677
|
+
for name, enumeration in self._enumerations.items():
|
|
1678
|
+
logger.debug(" >>> checking for alias: %s => %s", name, enumeration)
|
|
1679
|
+
|
|
1680
|
+
if isinstance(enumeration, Enumeration):
|
|
1681
|
+
if self is enumeration and enumeration.name != name:
|
|
1682
|
+
aliases.append(enumeration)
|
|
1683
|
+
|
|
1684
|
+
return aliases
|
|
1685
|
+
|
|
1425
1686
|
|
|
1426
1687
|
class EnumerationType(Enumeration, typecast=False):
|
|
1427
1688
|
"""The EnumerationType class represents the type of value held by an enumeration."""
|
|
@@ -1462,6 +1723,15 @@ class EnumerationInteger(int, Enumeration):
|
|
|
1462
1723
|
def __repr__(self) -> str:
|
|
1463
1724
|
return Enumeration.__repr__(self)
|
|
1464
1725
|
|
|
1726
|
+
def __hash__(self) -> id:
|
|
1727
|
+
return super().__hash__()
|
|
1728
|
+
|
|
1729
|
+
def __eq__(self, other: object) -> bool:
|
|
1730
|
+
if isinstance(other, (int, self.__class__)):
|
|
1731
|
+
return super().__eq__(other)
|
|
1732
|
+
else:
|
|
1733
|
+
return Enumeration.__eq__(self, other)
|
|
1734
|
+
|
|
1465
1735
|
|
|
1466
1736
|
class EnumerationFloat(float, Enumeration):
|
|
1467
1737
|
"""An Enumeration subclass where all values are float values."""
|
|
@@ -1505,6 +1775,15 @@ class EnumerationComplex(complex, Enumeration):
|
|
|
1505
1775
|
def __repr__(self) -> str:
|
|
1506
1776
|
return Enumeration.__repr__(self)
|
|
1507
1777
|
|
|
1778
|
+
def __hash__(self) -> id:
|
|
1779
|
+
return super().__hash__()
|
|
1780
|
+
|
|
1781
|
+
def __eq__(self, other: object) -> bool:
|
|
1782
|
+
if isinstance(other, (float, self.__class__)):
|
|
1783
|
+
return super().__eq__(other)
|
|
1784
|
+
else:
|
|
1785
|
+
return Enumeration.__eq__(self, other)
|
|
1786
|
+
|
|
1508
1787
|
|
|
1509
1788
|
class EnumerationString(str, Enumeration):
|
|
1510
1789
|
"""An Enumeration subclass where all values are string values."""
|
|
@@ -1528,6 +1807,15 @@ class EnumerationString(str, Enumeration):
|
|
|
1528
1807
|
def __repr__(self) -> str:
|
|
1529
1808
|
return Enumeration.__repr__(self)
|
|
1530
1809
|
|
|
1810
|
+
def __hash__(self) -> id:
|
|
1811
|
+
return super().__hash__()
|
|
1812
|
+
|
|
1813
|
+
def __eq__(self, other: object) -> bool:
|
|
1814
|
+
if isinstance(other, (str, self.__class__)):
|
|
1815
|
+
return super().__eq__(other)
|
|
1816
|
+
else:
|
|
1817
|
+
return Enumeration.__eq__(self, other)
|
|
1818
|
+
|
|
1531
1819
|
|
|
1532
1820
|
class EnumerationBytes(bytes, Enumeration):
|
|
1533
1821
|
"""An Enumeration subclass where all values are bytes values."""
|
|
@@ -1548,6 +1836,15 @@ class EnumerationBytes(bytes, Enumeration):
|
|
|
1548
1836
|
def __repr__(self) -> str:
|
|
1549
1837
|
return Enumeration.__repr__(self)
|
|
1550
1838
|
|
|
1839
|
+
def __hash__(self) -> id:
|
|
1840
|
+
return super().__hash__()
|
|
1841
|
+
|
|
1842
|
+
def __eq__(self, other: object) -> bool:
|
|
1843
|
+
if isinstance(other, (bytes, self.__class__)):
|
|
1844
|
+
return super().__eq__(other)
|
|
1845
|
+
else:
|
|
1846
|
+
return Enumeration.__eq__(self, other)
|
|
1847
|
+
|
|
1551
1848
|
|
|
1552
1849
|
class EnumerationTuple(tuple, Enumeration):
|
|
1553
1850
|
"""An Enumeration subclass where all values are tuple values."""
|
|
@@ -1568,6 +1865,15 @@ class EnumerationTuple(tuple, Enumeration):
|
|
|
1568
1865
|
def __repr__(self) -> str:
|
|
1569
1866
|
return Enumeration.__repr__(self)
|
|
1570
1867
|
|
|
1868
|
+
def __hash__(self) -> id:
|
|
1869
|
+
return super().__hash__()
|
|
1870
|
+
|
|
1871
|
+
def __eq__(self, other: object) -> bool:
|
|
1872
|
+
if isinstance(other, (tuple, self.__class__)):
|
|
1873
|
+
return super().__eq__(other)
|
|
1874
|
+
else:
|
|
1875
|
+
return Enumeration.__eq__(self, other)
|
|
1876
|
+
|
|
1571
1877
|
|
|
1572
1878
|
class EnumerationSet(set, Enumeration):
|
|
1573
1879
|
"""An Enumeration subclass where all values are set values."""
|
|
@@ -1588,6 +1894,15 @@ class EnumerationSet(set, Enumeration):
|
|
|
1588
1894
|
def __repr__(self) -> str:
|
|
1589
1895
|
return Enumeration.__repr__(self)
|
|
1590
1896
|
|
|
1897
|
+
def __hash__(self) -> id:
|
|
1898
|
+
return super().__hash__()
|
|
1899
|
+
|
|
1900
|
+
def __eq__(self, other: object) -> bool:
|
|
1901
|
+
if isinstance(other, (set, self.__class__)):
|
|
1902
|
+
return super().__eq__(other)
|
|
1903
|
+
else:
|
|
1904
|
+
return Enumeration.__eq__(self, other)
|
|
1905
|
+
|
|
1591
1906
|
|
|
1592
1907
|
class EnumerationList(list, Enumeration):
|
|
1593
1908
|
"""An Enumeration subclass where all values are list values."""
|
|
@@ -1608,6 +1923,15 @@ class EnumerationList(list, Enumeration):
|
|
|
1608
1923
|
def __repr__(self) -> str:
|
|
1609
1924
|
return Enumeration.__repr__(self)
|
|
1610
1925
|
|
|
1926
|
+
def __hash__(self) -> id:
|
|
1927
|
+
return super().__hash__()
|
|
1928
|
+
|
|
1929
|
+
def __eq__(self, other: object) -> bool:
|
|
1930
|
+
if isinstance(other, (list, self.__class__)):
|
|
1931
|
+
return super().__eq__(other)
|
|
1932
|
+
else:
|
|
1933
|
+
return Enumeration.__eq__(self, other)
|
|
1934
|
+
|
|
1611
1935
|
|
|
1612
1936
|
class EnumerationDictionary(dict, Enumeration):
|
|
1613
1937
|
"""An Enumeration subclass where all values are dictionary values."""
|
|
@@ -1631,6 +1955,15 @@ class EnumerationDictionary(dict, Enumeration):
|
|
|
1631
1955
|
def __repr__(self) -> str:
|
|
1632
1956
|
return Enumeration.__repr__(self)
|
|
1633
1957
|
|
|
1958
|
+
def __hash__(self) -> id:
|
|
1959
|
+
return super().__hash__()
|
|
1960
|
+
|
|
1961
|
+
def __eq__(self, other: object) -> bool:
|
|
1962
|
+
if isinstance(other, (dict, self.__class__)):
|
|
1963
|
+
return super().__eq__(other)
|
|
1964
|
+
else:
|
|
1965
|
+
return Enumeration.__eq__(self, other)
|
|
1966
|
+
|
|
1634
1967
|
|
|
1635
1968
|
class EnumerationFlag(int, Enumeration):
|
|
1636
1969
|
"""An Enumeration subclass where all values are integer values to the power of 2."""
|
|
@@ -1772,6 +2105,15 @@ class EnumerationFlag(int, Enumeration):
|
|
|
1772
2105
|
def __repr__(self) -> str:
|
|
1773
2106
|
return Enumeration.__repr__(self)
|
|
1774
2107
|
|
|
2108
|
+
def __hash__(self) -> id:
|
|
2109
|
+
return super().__hash__()
|
|
2110
|
+
|
|
2111
|
+
def __eq__(self, other: object) -> bool:
|
|
2112
|
+
if isinstance(other, (int, self.__class__)):
|
|
2113
|
+
return super().__eq__(other)
|
|
2114
|
+
else:
|
|
2115
|
+
return Enumeration.__eq__(self, other)
|
|
2116
|
+
|
|
1775
2117
|
def __or__(self, other: EnumerationFlag): # called for: "a | b" (bitwise or)
|
|
1776
2118
|
"""Support performing a bitwise or between the current EnumerationFlag
|
|
1777
2119
|
instance's bitmask and the 'other' provided EnumerationFlag's bitmask;
|