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/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 auto(int):
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.power > 0:
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) -> bool | None:
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
- will be returned."""
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(name, str) and enumeration.name == name:
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(value, str) and enumeration.name == value:
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)" % (self.__class__.__name__, other))
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
- if self is other:
1370
- return True
1591
+ equals = self is other
1592
+ elif self.name == other:
1593
+ equals = True
1594
+ elif self.value == other:
1595
+ equals = True
1371
1596
 
1372
- for attribute, enumeration in self._enumerations.items():
1373
- logger.info(
1374
- "%s.__eq__(other: %s) enumeration => %s"
1375
- % (self.__class__.__name__, other, enumeration)
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
- if isinstance(other, Enumeration):
1379
- if enumeration is other:
1380
- equals = True
1381
- break
1382
- elif enumeration.value == other:
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
- return equals
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(Colors._enumerations) => %s (%s)",
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.info(" >>> checking for alias: %s => %s", name, enumeration)
1657
+ logger.debug(" >>> checking for alias: %s => %s", name, enumeration)
1418
1658
 
1419
1659
  if isinstance(enumeration, Enumeration):
1420
- if name != enumeration.name:
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;