enumerific 1.0.1__tar.gz → 1.0.3__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. enumerific-1.0.3/PKG-INFO +862 -0
  2. enumerific-1.0.3/README.md +832 -0
  3. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific/__init__.py +9 -1
  4. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific/exceptions.py +4 -0
  5. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific/extensible.py +501 -60
  6. enumerific-1.0.3/source/enumerific/version.txt +1 -0
  7. enumerific-1.0.3/source/enumerific.egg-info/PKG-INFO +862 -0
  8. {enumerific-1.0.1 → enumerific-1.0.3}/tests/test_extensible_enums.py +675 -7
  9. enumerific-1.0.1/PKG-INFO +0 -198
  10. enumerific-1.0.1/README.md +0 -168
  11. enumerific-1.0.1/source/enumerific/version.txt +0 -1
  12. enumerific-1.0.1/source/enumerific.egg-info/PKG-INFO +0 -198
  13. {enumerific-1.0.1 → enumerific-1.0.3}/LICENSE.md +0 -0
  14. {enumerific-1.0.1 → enumerific-1.0.3}/pyproject.toml +0 -0
  15. {enumerific-1.0.1 → enumerific-1.0.3}/requirements.development.txt +0 -0
  16. {enumerific-1.0.1 → enumerific-1.0.3}/requirements.distribution.txt +0 -0
  17. {enumerific-1.0.1 → enumerific-1.0.3}/requirements.txt +0 -0
  18. {enumerific-1.0.1 → enumerific-1.0.3}/setup.cfg +0 -0
  19. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific/logging.py +0 -0
  20. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific/standard.py +0 -0
  21. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific.egg-info/SOURCES.txt +0 -0
  22. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific.egg-info/dependency_links.txt +0 -0
  23. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific.egg-info/requires.txt +0 -0
  24. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific.egg-info/top_level.txt +0 -0
  25. {enumerific-1.0.1 → enumerific-1.0.3}/source/enumerific.egg-info/zip-safe +0 -0
  26. {enumerific-1.0.1 → enumerific-1.0.3}/tests/test_enumerific_library.py +0 -0
@@ -0,0 +1,862 @@
1
+ Metadata-Version: 2.4
2
+ Name: enumerific
3
+ Version: 1.0.3
4
+ Summary: Simplifies working with Python enums.
5
+ Author: Daniel Sissman
6
+ License-Expression: MIT
7
+ Project-URL: documentation, https://github.com/bluebinary/enumerific/blob/main/README.md
8
+ Project-URL: changelog, https://github.com/bluebinary/enumerific/blob/main/CHANGELOG.md
9
+ Project-URL: repository, https://github.com/bluebinary/enumerific
10
+ Project-URL: issues, https://github.com/bluebinary/enumerific/issues
11
+ Project-URL: homepage, https://github.com/bluebinary/enumerific
12
+ Keywords: enum,enumeration,enumerations
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.md
21
+ Provides-Extra: development
22
+ Requires-Dist: black==24.10.*; extra == "development"
23
+ Requires-Dist: pytest==8.3.*; extra == "development"
24
+ Requires-Dist: pytest-codeblocks==0.17.0; extra == "development"
25
+ Provides-Extra: distribution
26
+ Requires-Dist: build; extra == "distribution"
27
+ Requires-Dist: twine; extra == "distribution"
28
+ Requires-Dist: wheel; extra == "distribution"
29
+ Dynamic: license-file
30
+
31
+ # Enumerific Enums
32
+
33
+ The Enumerific library provides a greenfield implementation of enumerations with many unique features through the library's own `Enumeration` class, as well as separately offering several useful extra methods for the built-in standard library `enums.Enum` type.
34
+
35
+ The Enumerific library's `Enumeration` class offers the following features:
36
+
37
+ * A greenfield implementation of enumerations compatible with Python 3.10 and later versions;
38
+ * Enumerific enumerations can hold option values of the same or mixed types, including `int`, `float`, `complex`, `str`, `bytes`, `set`, `tuple`, `list`, `dict` as well as arbitrary `object` types;
39
+ * Enumerific enumeration options can be accessed directly as native data types and enumeration options can be used anywhere that the corresponding native data types can be used;
40
+ * Support for automatic typecasting of the `Enumeration` base class to support the use of enumeration option values interchangeably with native data type values;
41
+ * Enumerific enumerations options can be added after an `Enumeration` class has been created either through extending an existing enumerations class by subclassing or by registering new options directly on an existing enumerations class via the `.register()` method; this is especially useful for cases where enumeration options may not all be known prior to runtime;
42
+ * Enumerific enumerations options can be removed after an `Enumeration` class has been created via the `.unregister()` method; this specialised behaviour is prevented by default, but can be enabled for advanced use cases;
43
+ * Enforcement of unique values for all options within an enumeration, unless overridden;
44
+ * Support for aliasing enumeration options, and control over this behaviour;
45
+ * Support for backfilling enumeration options on a superclass when subclassing, and control over this behaviour
46
+ * Support for redefining enumeration options, and control over this behaviour;
47
+ * Support for automatically generating unique number sequences for enumeration options, including powers of two for bitwise enumeration flags, as well as other sequences such as powers of other numbers and factoring;
48
+ * Support for annotating enumeration options with additional arbitrary key-value pairs, which can be particularly useful for associating additional data with a given enumeration option, which may be accessed later anywhere in code that the enumeration option is available;
49
+ * Simple one-line reconciliation of `Enumeration` class options to the corresponding `enums.Enum` class instance that represents the corresponding option; reconciliation by enumeration option name, value and enumeration class instance reference are all supported through the `.reconcile()` class method;
50
+ * Simple one-line validation of `Enumeration` class options; validation by enumeration option name, value and enumeration class instance reference are all supported through the `.validate()` class method;
51
+ * Simple access to all of the options provided by an `Enumeration` class instance through the `.options()` class method;
52
+ * Access to all of the names of the `Enumeration` class options via the `.names()` method;
53
+ * Access to all of the names/keys of the `Enumeration` class options via the `.keys()` method;
54
+ * Access to all of the values of the `Enumeration` class options via the `.values()` method;
55
+ * Access to all of the key-value pairs of the `Enumeration` class options via the `.items()` method;
56
+ * Ability to determine if an enumeration option is an alias of another option, or is an option in its own right via the `Enumeration` class' `.aliased` property.
57
+ * Ability to obtain the aliases for an enumeration option via the `.aliases` property.
58
+
59
+ Furthermore, as noted, the Enumerific library also offers extended functionality for the built-in standard library `enums.Enum` type:
60
+
61
+ * Simple one-line reconciliation of `enums.Enum` options to the corresponding `enums.Enum` class instance that represents the corresponding option; reconciliation by enumeration option name, value and enumeration class instance reference are all supported through the `.reconcile()` class method;
62
+ * Simple one-line validation of `enums.Enum` options; validation by enumeration option name, value and enumeration class instance reference are all supported through the `.validate()` class method;
63
+ * Simple access to all of the options provided by an `enums.Enum` class instance through the `.options()` class method.
64
+
65
+ ### Requirements
66
+
67
+ The Enumerific library has been tested with Python 3.10, 3.11, 3.12 and 3.13, and is not
68
+ compatible with Python 3.9 or earlier.
69
+
70
+ ### Installation
71
+
72
+ Enumerific is available from the PyPI, so may be added to a project's dependencies via
73
+ its `requirements.txt` file or similar by referencing the library's name, `enumerific`,
74
+ or the library may be installed directly into your local runtime environment using `pip`
75
+ by entering the following command, and following any prompts:
76
+
77
+ $ pip install enumerific
78
+
79
+ ### Usage
80
+
81
+ To use the Enumerific library's implementation of enumerations import the `Enumeration` class from the Enumerific library:
82
+
83
+ ```python
84
+ from enumerific import Enumeration
85
+
86
+ class Colors(Enumeration):
87
+ RED = 1
88
+ GREEN = 2
89
+ BLUE = 3
90
+
91
+ assert issubclass(Colors, Enumeration)
92
+
93
+ # Note that as all of the Colors enumeration options have integer values, the class was
94
+ # typecast to int so that its option values can be used interchangeably with integers:
95
+ assert issubclass(Colors, int)
96
+
97
+ color = Colors.RED
98
+
99
+ # Enumeration class options are instances of the class
100
+ assert isinstance(color, Colors)
101
+
102
+ # They can also be instances of the raw value that was assigned to the option
103
+ assert isinstance(color, int)
104
+
105
+ # Each enumeration class option has a name (the name used to define the option)
106
+ assert color.name == "RED"
107
+
108
+ # Each enumeration class option has a value (the value used when defining the option)
109
+ assert color.value == 1
110
+
111
+ # The identity of an enumeration option matches the option
112
+ assert color is Colors.RED
113
+
114
+ # The equality of an enumeration option can also be compared against the enumeration
115
+ # option directly, against the name of the option, or against the value:
116
+ assert color == Colors.RED
117
+ assert color == "RED"
118
+ assert color == 1
119
+ assert color != 2
120
+ ```
121
+
122
+ #### Example 1: Reconciling a Value
123
+
124
+ ```python
125
+ from enumerific import Enumeration
126
+
127
+ class Colors(Enumeration):
128
+ RED = 1
129
+ GREEN = 2
130
+ BLUE = 3
131
+
132
+ # Given a string value in this case
133
+ value = 1
134
+
135
+ # Reconcile it to the associated enumeration option
136
+ color = Colors.reconcile(value)
137
+
138
+ assert color == Colors.RED # asserts successfully
139
+ assert color is Colors.RED # asserts successfully as Enumeration class options are singletons
140
+ ```
141
+
142
+ #### Example 2: Reconciling an Enumeration Option Name
143
+
144
+ ```python
145
+ from enumerific import Enumeration
146
+
147
+ class Colors(Enumeration):
148
+ RED = 1
149
+ GREEN = 2
150
+ BLUE = 3
151
+
152
+ # Given a string value in this case
153
+ value = "RED"
154
+
155
+ # Reconcile it to the associated enumeration option
156
+ color = Colors.reconcile(value)
157
+
158
+ assert color == Colors.RED # asserts successfully
159
+ assert color is Colors.RED # asserts successfully as Enumeration class options are singletons
160
+
161
+ # Given a string value in this case
162
+ value = "red"
163
+
164
+ # Reconcile it to the associated enumeration option;
165
+ # values can be reconciled caselessly too:
166
+ color = Colors.reconcile(value, caselessly=True)
167
+
168
+ assert color == Colors.RED # asserts successfully
169
+ assert color is Colors.RED # asserts successfully as Enumeration class options are singletons
170
+ ```
171
+
172
+ #### Example 3: Validating a Value
173
+
174
+ ```python
175
+ from enumerific import Enumeration
176
+
177
+ class Colors(Enumeration):
178
+ RED = 1
179
+ GREEN = 2
180
+ BLUE = 3
181
+
182
+ # The value can be an enumeration option's name, its value, or the enumeration option
183
+ value = "RED"
184
+ value = 1
185
+ value = Colors.RED
186
+
187
+ if Colors.validate(value) is True:
188
+ # do something if the value could be validated
189
+ pass
190
+ else:
191
+ # do something else if the value could not be validated
192
+ pass
193
+ ```
194
+
195
+ #### Example 4: Iterating Over Enumeration Options
196
+
197
+ ```python
198
+ from enumerific import Enumeration
199
+
200
+ class Colors(Enumeration):
201
+ RED = 1
202
+ GREEN = 2
203
+ BLUE = 3
204
+
205
+ options = Colors.options()
206
+
207
+ for name, option in options.items():
208
+ # do something with each option
209
+ print(option.name, option.value)
210
+ ```
211
+
212
+ #### Example 5: Iterating Over Enumeration Names
213
+
214
+ ```python
215
+ from enumerific import Enumeration
216
+
217
+ class Colors(Enumeration):
218
+ RED = 1
219
+ GREEN = 2
220
+ BLUE = 3
221
+
222
+ for name in Colors.names():
223
+ # do something with each option name
224
+ print(name)
225
+ ```
226
+
227
+ #### Example 6: Iterating Over Enumeration Keys
228
+
229
+ ```python
230
+ from enumerific import Enumeration
231
+
232
+ class Colors(Enumeration):
233
+ RED = 1
234
+ GREEN = 2
235
+ BLUE = 3
236
+
237
+ for key in Colors.keys():
238
+ # do something with each option key
239
+ print(key)
240
+ ```
241
+
242
+ #### Example 7: Iterating Over Enumeration Values
243
+
244
+ ```python
245
+ from enumerific import Enumeration
246
+
247
+ class Colors(Enumeration):
248
+ RED = 1
249
+ GREEN = 2
250
+ BLUE = 3
251
+
252
+ for value in Colors.values():
253
+ # do something with each option key
254
+ print(value)
255
+ ```
256
+
257
+ #### Example 8: Registering New Options
258
+
259
+ ```python
260
+ from enumerific import Enumeration
261
+
262
+ class Colors(Enumeration):
263
+ RED = 1
264
+ GREEN = 2
265
+ BLUE = 3
266
+
267
+ Colors.register("PURPLE", 4)
268
+ Colors.register("GOLD", 5)
269
+
270
+ assert "PURPLE" in Colors
271
+ assert Colors.PURPLE.name == "PURPLE"
272
+ assert Colors.PURPLE.value == 4
273
+ assert Colors.PURPLE == 4
274
+
275
+ assert "GOLD" in Colors
276
+ assert Colors.GOLD.name == "GOLD"
277
+ assert Colors.GOLD.value == 5
278
+ assert Colors.GOLD == 5
279
+ ```
280
+
281
+ #### Example 9: Subclassing
282
+
283
+ ```python
284
+ from enumerific import Enumeration
285
+
286
+ class Colors(Enumeration):
287
+ RED = 1
288
+ GREEN = 2
289
+ BLUE = 3
290
+
291
+ # Ensure that Colors has the expected options
292
+ assert "RED" in Colors
293
+ assert "GREEN" in Colors
294
+ assert "BLUE" in Colors
295
+
296
+ # Create a subclass of Colors, inheriting its options
297
+ class MoreColors(Colors):
298
+ PURPLE = 4
299
+ GOLD = 5
300
+
301
+ # Ensure that MoreColors inherited the options from Colors, as well as adding its own
302
+ assert "RED" in MoreColors
303
+ assert "GREEN" in MoreColors
304
+ assert "BLUE" in MoreColors
305
+ assert "PURPLE" in MoreColors
306
+ assert "GOLD" in MoreColors
307
+
308
+ # As backfilling is off by default subclass options won't be available on the superclass
309
+ assert not "PURPLE" in Colors
310
+ assert not "GOLD" in Colors
311
+ ```
312
+
313
+ #### Example 10: Subclassing with Backfilling
314
+
315
+ ```python
316
+ from enumerific import Enumeration
317
+
318
+ # To override the default behaviour and to allow backfilling of options from subclasses,
319
+ # the `backfill` keyword argument can be set to `True` when creating the class. This
320
+ # effectively creates another way to extend an existing enumeration class through
321
+ # subclassing and its side-effect of backfilling, compared to using the `.register()`
322
+ # method to add new options to an existing enumeration class:
323
+ class Colors(Enumeration, backfill=True):
324
+ RED = 1
325
+ GREEN = 2
326
+ BLUE = 3
327
+
328
+ assert "RED" in Colors
329
+ assert "GREEN" in Colors
330
+ assert "BLUE" in Colors
331
+
332
+ class MoreColors(Colors):
333
+ PURPLE = 4
334
+ GOLD = 5
335
+
336
+ assert "RED" in MoreColors
337
+ assert "GREEN" in MoreColors
338
+ assert "BLUE" in MoreColors
339
+ assert "PURPLE" in MoreColors
340
+ assert "GOLD" in MoreColors
341
+
342
+ # As backfilling has been enabled for the superclass, subclass options are available on
343
+ # both the subclass as seen above as well as on the superclass through backfilling:
344
+ assert "PURPLE" in Colors
345
+ assert "GOLD" in Colors
346
+ ```
347
+
348
+ #### Example 11: Subclassing Over
349
+
350
+ ```python
351
+ from enumerific import Enumeration
352
+
353
+ class Colors(Enumeration):
354
+ RED = 1
355
+ GREEN = 2
356
+ BLUE = 3
357
+
358
+ assert "RED" in Colors
359
+ assert Colors.RED == 1
360
+
361
+ assert "GREEN" in Colors
362
+ assert Colors.GREEN == 2
363
+
364
+ assert "BLUE" in Colors
365
+ assert Colors.BLUE == 3
366
+
367
+ # Subclasses of Enumerations classes can be given the same name as the parent class, so
368
+ # within this scope, the subclass shadows the superclass; the subclass inherits all the
369
+ # enumeration options of its parent(s) superclasses:
370
+ class Colors(Colors):
371
+ PURPLE = 4
372
+ GOLD = 5
373
+
374
+ assert "RED" in Colors
375
+ assert "GREEN" in Colors
376
+ assert "BLUE" in Colors
377
+
378
+ assert "PURPLE" in Colors
379
+ assert Colors.PURPLE == 4
380
+
381
+ assert "GOLD" in Colors
382
+ assert Colors.GOLD == 5
383
+ ```
384
+
385
+ #### Example 12: Unregistering Existing Option
386
+
387
+ ```python
388
+ from enumerific import Enumeration
389
+
390
+ # Note that unregistering options is prevented by default; to all options to be removed
391
+ # the `removable` argument needs to be set to `True` when the class is created:
392
+ class Colors(Enumeration, removable=True):
393
+ RED = 1
394
+ GREEN = 2
395
+ BLUE = 3
396
+
397
+ Colors.unregister("GREEN")
398
+
399
+ assert "RED" in Colors
400
+ assert "GREEN" not in Colors
401
+ assert "BLUE" in Colors
402
+ ```
403
+
404
+ #### Example 13: Preventing Subclassing of Enumeration Classes
405
+
406
+ ```python
407
+ from enumerific import Enumeration, EnumerationSubclassingError
408
+ import pytest
409
+
410
+ # To prevent an enumeration class from being extended through subclassing, the
411
+ # `subclassable` keyword argument can be set when creating the class; this will
412
+ # result in an `EnumerationSubclassingError` exception being raised on subclassing:
413
+ class Colors(Enumeration, subclassable=False):
414
+ RED = 1
415
+ GREEN = 2
416
+ BLUE = 3
417
+
418
+ with pytest.raises(EnumerationSubclassingError):
419
+ class MoreColors(Colors):
420
+ PURPLE = 4
421
+ ```
422
+
423
+ #### Example 14: Aliasing Options
424
+
425
+ ```python
426
+ from enumerific import Enumeration
427
+
428
+ # Note that aliasing options is prevented by default to ensure that all options have
429
+ # unique values; to allow aliasing, the `aliased` argument needs to be set to `True`
430
+ # when the class is created; aliases can be added by referencing the original option's
431
+ # name or its value as demonstrated below with the ROUGE and VERTE aliases:
432
+ class Colors(Enumeration, aliased=True):
433
+ RED = 1
434
+ GREEN = 2
435
+ BLUE = 3
436
+ ROUGE = RED
437
+ VERTE = 2
438
+
439
+ assert "RED" in Colors
440
+ assert "GREEN" in Colors
441
+ assert "BLUE" in Colors
442
+ assert "ROUGE" in Colors
443
+ assert "VERTE" in Colors
444
+
445
+ # Note that aliases are just different names for the same exact option, so the aliases
446
+ # can be used interchangeably with the original option, and they have the same identity:
447
+ assert Colors.RED is Colors.ROUGE
448
+ assert Colors.GREEN is Colors.VERTE
449
+
450
+ # All of the other properties of aliased options are also identical because the alias is
451
+ # just another reference to the same exact object in memory:
452
+ assert Colors.RED.name == Colors.ROUGE.name
453
+ assert Colors.RED.value == Colors.ROUGE.value
454
+
455
+ # Different options have their own distinct identities
456
+ assert not Colors.RED is Colors.VERTE
457
+
458
+ # Aliased options report that they have been aliased:
459
+ assert Colors.RED.aliased is True
460
+ assert Colors.GREEN.aliased is True
461
+ assert Colors.ROUGE.aliased is True
462
+ assert Colors.VERTE.aliased is True
463
+
464
+ # Non-aliased options do not report that they have been aliased:
465
+ assert Colors.BLUE.aliased is False
466
+
467
+ # The aliases for an option can be obtained via the .aliases property:
468
+ assert Colors.RED.aliases == [Colors.ROUGE]
469
+ assert Colors.GREEN.aliases == [Colors.VERTE]
470
+ assert Colors.BLUE.aliases == [] # BLUE has not been aliased
471
+ ```
472
+
473
+ #### Example 15: Non-Unique Options
474
+
475
+ ```python
476
+ from enumerific import Enumeration
477
+
478
+ # Note that non-unique options are prevented by default to ensure that all options have
479
+ # unique values; to allow non-unique option values, the `unique` argument needs to be
480
+ # set to `False` when the class is created:
481
+ class Colors(Enumeration, unique=False):
482
+ RED = 1
483
+ GREEN = 1
484
+ BLUE = 3
485
+
486
+ assert "RED" in Colors
487
+ assert Colors.RED.name == "RED"
488
+ assert Colors.RED.value == 1
489
+ assert Colors.RED == 1
490
+
491
+ assert "GREEN" in Colors
492
+ assert Colors.GREEN.name == "GREEN"
493
+ assert Colors.GREEN.value == 1
494
+ assert Colors.GREEN == 1
495
+
496
+ assert "BLUE" in Colors
497
+ assert Colors.BLUE.name == "BLUE"
498
+ assert Colors.BLUE.value == 3
499
+ assert Colors.BLUE == 3
500
+
501
+ # Note that although options can use the same values when the class has been configured
502
+ # to allow it, the enumeration options still maintain their own distinct identities:
503
+ assert not Colors.RED is Colors.GREEN
504
+ assert not Colors.BLUE is Colors.RED
505
+
506
+ # However, when enumeration options share values, options with the same values will
507
+ # compare as equal via equality checking (which is different than identity checking):
508
+ assert Colors.RED == Colors.GREEN
509
+ assert Colors.BLUE != Colors.RED
510
+ ```
511
+
512
+ #### Example 16: Bit Wise Flags
513
+
514
+ ```python
515
+ from enumerific import Enumeration
516
+
517
+ class Permissions(Enumeration, flags=True):
518
+ READ = 1
519
+ WRITE = 2
520
+ EXECUTE = 4
521
+ DELETE = 8
522
+
523
+ assert "READ" in Permissions
524
+ assert Permissions.READ.name == "READ"
525
+ assert Permissions.READ.value == 1
526
+ assert Permissions.READ == 1
527
+
528
+ assert "WRITE" in Permissions
529
+ assert Permissions.WRITE.name == "WRITE"
530
+ assert Permissions.WRITE.value == 2
531
+ assert Permissions.WRITE == 2
532
+
533
+ assert "EXECUTE" in Permissions
534
+ assert Permissions.EXECUTE.name == "EXECUTE"
535
+ assert Permissions.EXECUTE.value == 4
536
+ assert Permissions.EXECUTE == 4
537
+
538
+ # OR (add/merge) the READ and WRITE permission flags into the 'permissions' variable
539
+ permissions = Permissions.READ | Permissions.WRITE
540
+
541
+ assert str(permissions) == "Permissions.READ|WRITE"
542
+ assert Permissions.READ in permissions
543
+ assert Permissions.WRITE in permissions
544
+ assert not Permissions.EXECUTE in permissions
545
+
546
+ # Raises an exception as DELETE doesn't exist
547
+ assert not Permissions.DELETE in permissions
548
+
549
+ assert (permissions & Permissions.READ) == Permissions.READ
550
+ assert (permissions & Permissions.WRITE) == Permissions.WRITE
551
+
552
+ # XOR (remove) the WRITE permission from the 'permissions' variable
553
+ permissions = permissions ^ Permissions.WRITE
554
+
555
+ assert Permissions.READ in permissions
556
+ assert not Permissions.WRITE in permissions
557
+ assert not Permissions.EXECUTE in permissions
558
+
559
+ assert (permissions & Permissions.READ) == Permissions.READ
560
+ assert not (permissions & Permissions.WRITE) == Permissions.WRITE
561
+
562
+ assert not Permissions.WRITE in permissions
563
+ assert str(permissions) == "Permissions.READ"
564
+
565
+ # The order of the name components follows the order the underlaying flags were declared
566
+ assert str(Permissions.READ | Permissions.WRITE) == "Permissions.READ|WRITE"
567
+ assert str(Permissions.WRITE | Permissions.READ) == "Permissions.READ|WRITE"
568
+ assert (
569
+ str(Permissions.WRITE | Permissions.READ | Permissions.EXECUTE)
570
+ == "Permissions.READ|WRITE|EXECUTE"
571
+ )
572
+
573
+ # Assign 'permissions' to the (~) inverse (opposite) of EXECUTE,
574
+ # i.e. all Permissions options except EXECUTE
575
+ permissions = ~Permissions.EXECUTE
576
+
577
+ assert Permissions.READ in permissions
578
+ assert Permissions.WRITE in permissions
579
+ assert not Permissions.EXECUTE in permissions
580
+ assert Permissions.DELETE in permissions
581
+ assert str(permissions) == "Permissions.READ|WRITE|DELETE"
582
+ ```
583
+
584
+ #### Example 17: Annotating Enumeration Option Values
585
+
586
+ ```python
587
+ from enumerific import Enumeration, anno
588
+
589
+ # The 'anno' (annotation) class can be used to add annotations to enumeration options;
590
+ # these are arbitrary key-value pairs that can be used to hold any additional data that
591
+ # is useful to keep associated with the enumeration option; the annotation values are
592
+ # then accessible anywhere that the enumeration is, and can be accessed as attributes:
593
+ class Colors(Enumeration):
594
+ RED = anno(1, rgb=(255, 0, 0), primary=True)
595
+ GREEN = anno(2, rgb=(0, 255, 0), primary=True)
596
+ BLUE = anno(3, rgb=(0, 0, 255), primary=True)
597
+ PURPLE = anno(4, rgb=(255, 0, 255), primary=False)
598
+
599
+ assert "RED" in Colors
600
+ assert Colors.RED.name == "RED"
601
+ assert Colors.RED.value == 1
602
+ assert Colors.RED == 1
603
+ assert Colors.RED.rgb == (255, 0, 0)
604
+ assert Colors.RED.primary is True
605
+
606
+ assert "GREEN" in Colors
607
+ assert Colors.GREEN.name == "GREEN"
608
+ assert Colors.GREEN.value == 2
609
+ assert Colors.GREEN == 2
610
+ assert Colors.GREEN.rgb == (0, 255, 0)
611
+ assert Colors.GREEN.primary is True
612
+
613
+ assert "BLUE" in Colors
614
+ assert Colors.BLUE.name == "BLUE"
615
+ assert Colors.BLUE.value == 3
616
+ assert Colors.BLUE == 3
617
+ assert Colors.BLUE.rgb == (0, 0, 255)
618
+ assert Colors.BLUE.primary is True
619
+
620
+ assert "PURPLE" in Colors
621
+ assert Colors.PURPLE.name == "PURPLE"
622
+ assert Colors.PURPLE.value == 4
623
+ assert Colors.PURPLE == 4
624
+ assert Colors.PURPLE.rgb == (255, 0, 255)
625
+ assert Colors.PURPLE.primary is False
626
+ ```
627
+
628
+ #### Example 18: Annotating Enumeration Option Values with Automatic Sequencing
629
+
630
+ ```python
631
+ from enumerific import Enumeration, auto
632
+
633
+ # The 'auto' (automatic) class can be used to generate unique numeric sequence numbers
634
+ # for enumeration options and to optionally add annotations to those same options; the
635
+ # annotation key-value pairs can be used to hold any additional data that is useful to
636
+ # keep associated with the enumeration option; the annotation values are then accessible
637
+ # anywhere that the enumeration is, and can be accessed as attributes:
638
+ class Colors(Enumeration):
639
+ RED = auto(rgb=(255, 0, 0), primary=True)
640
+ GREEN = auto(rgb=(0, 255, 0), primary=True)
641
+ BLUE = auto(rgb=(0, 0, 255), primary=True)
642
+ PURPLE = auto(rgb=(255, 0, 255), primary=False)
643
+
644
+ assert "RED" in Colors
645
+ assert Colors.RED.name == "RED"
646
+ assert Colors.RED.value == 1
647
+ assert Colors.RED == 1
648
+ assert Colors.RED.rgb == (255, 0, 0)
649
+ assert Colors.RED.primary is True
650
+
651
+ assert "GREEN" in Colors
652
+ assert Colors.GREEN.name == "GREEN"
653
+ assert Colors.GREEN.value == 2
654
+ assert Colors.GREEN == 2
655
+ assert Colors.GREEN.rgb == (0, 255, 0)
656
+ assert Colors.GREEN.primary is True
657
+
658
+ assert "BLUE" in Colors
659
+ assert Colors.BLUE.name == "BLUE"
660
+ assert Colors.BLUE.value == 3
661
+ assert Colors.BLUE == 3
662
+ assert Colors.BLUE.rgb == (0, 0, 255)
663
+ assert Colors.BLUE.primary is True
664
+
665
+ assert "PURPLE" in Colors
666
+ assert Colors.PURPLE.name == "PURPLE"
667
+ assert Colors.PURPLE.value == 4
668
+ assert Colors.PURPLE == 4
669
+ assert Colors.PURPLE.rgb == (255, 0, 255)
670
+ assert Colors.PURPLE.primary is False
671
+ ```
672
+
673
+ # Enumerific Library Enumerations: Classes & Methods
674
+
675
+ The Enumerific library's `Enumeration` class is a greenfield implementation of enumerations
676
+ and does not inherit from any of the standard library enumeration classes, but offers equivalent
677
+ and extended functionality implemented from scratch. Enumerific library enumerations can be used
678
+ in any situation that enumerations are needed, and can replace the use of standard library
679
+ enumerations in almost every case unless some very specific functionality or underlying behaviour
680
+ of standard library enumerations are relied upon in user code. For the majority of cases, the
681
+ functionality is sufficiently equivalent from an application binary interface (ABI) perspective
682
+ that the two implementations can be used interchangeably.
683
+
684
+ The Enumerific library's extended enumerations module offers the following classes:
685
+
686
+ * `EnumerationConfiguration` – The `EnumerationConfiguration` class is used internally by the library
687
+ to hold configuration information for an `Enumeration` class instance.
688
+
689
+ * `EnumerationMetaClass` – The `EnumerationMetaClass` metaclass is responsible for creating instances of the `Enumeration` class for use, and provides an interface between the class definition and some of the special behaviours needed to facilitate enumerations, such as each enumeration option being an instance of the enumeration class.
690
+
691
+ * `Enumeration` – The `Enumeration` class is the base class for all Enumerific library extended enumerations; the `Enumeration` class defines shared functionality used by all `Enumeration` class instances.
692
+
693
+ * `EnumerationType` – The `EnumerationType` class is actually a subclass of `Enumeration` and is used internally by the library to track the data type of the options assigned to an enumeration class.
694
+
695
+ * `EnumerationInteger` – The `EnumerationInteger` class is a subclass of `int` and `Enumeration` and supports interacting with enumeration options natively as `int` (integer) data types.
696
+
697
+ * `EnumerationFloat` – The `EnumerationFloat` class is a subclass of `float` and `Enumeration` and supports interacting with enumeration options natively as `float` (floating point) data types.
698
+
699
+ * `EnumerationComplex` – The `EnumerationComplex` class is a subclass of `complex` and `Enumeration` and supports interacting with enumeration options natively as `complex` (complex number) data types.
700
+
701
+ * `EnumerationBytes` – The `EnumerationBytes` class is a subclass of `bytes` and `Enumeration` and supports interacting with enumeration options natively as `bytes` data types.
702
+
703
+ * `EnumerationString` – The `EnumerationString` class is a subclass of `str` and `Enumeration` and supports interacting with enumeration options natively as `str` (string) data types.
704
+
705
+ * `EnumerationTuple` – The `EnumerationTuple` class is a subclass of `tuple` and `Enumeration` and supports interacting with enumeration options natively as `tuple` data types.
706
+
707
+ * `EnumerationSet` – The `EnumerationSet` class is a subclass of `set` and `Enumeration` and supports interacting with enumeration options natively as `set` data types.
708
+
709
+ * `EnumerationList` – The `EnumerationList` class is a subclass of `list` and `Enumeration` and supports interacting with enumeration options natively as `list` data types.
710
+
711
+ * `EnumerationDictionary` – The `EnumerationDictionary` class is a subclass of `dict` and `Enumeration` and supports interacting with enumeration options natively as `dict` (dictionary) data types.
712
+
713
+ * `EnumerationFlag` – The `EnumerationFlag` class is a special subclass of `int` and `Enumeration` and supports interacting with enumeration options natively as `int` (integer) data types and supports bitwise operations on the enumeration options so that enumeration options can be used as bitwise flags.
714
+
715
+ The extended enumerations module also offers the following classes for annotating enumeration
716
+ options and for automatically generating sequence numbers for annotation options:
717
+
718
+ * `anno` – The `anno` class provides support for annotating an enumeration option's value, allowing an enumeration option to carry both a value of any data type, and optional additional annotations of key-value pairs that can be accessed as properties on the annotated enumeration option.
719
+
720
+ * `auto` – The `auto` class is a subclass of `anno` and provides support both for annotating and enumeration option's value as per the functionality supported by its superclass, and additionally provides support for automatically generating unique number sequences to use for enumeration options, obviating the need to manually assign unique values to each enumeration option; number sequences can be configured to suit a range of needs, including generating number sequences as powers of two, which is particularly useful for the enumeration of bitwise flags as supported by the `EnumerationFlag` subclass.
721
+
722
+ # Standard Library Enumerations: Classes & Methods
723
+
724
+ The Enumerific library's own `Enum` class is a subclass of the built-in `enum.Enum` class,
725
+ so all of the built-in functionality of `enum.Enum` is available, as well as several additional class methods:
726
+
727
+ * `reconcile(value: object, default: Enum = None, raises: bool = False)` (`Enum`) – The `reconcile()` method allows for an enumeration's value or an enumeration option's name to be reconciled against a matching enumeration option. If the provided value can be matched against one of the enumeration's available options, that option will be returned, otherwise there are two possible behaviours: if the `raises` keyword argument has been set to or left as `False` (its default), the value assigned to the `default` keyword argument will be returned, which may be `None` if no default value has been specified; if the `raises` argument has been set to `True` an `EnumValueError` exception will be raised as an alert that the provided value could not be matched. One can also provide an enumeration option as the input value to the `reconcile` method, and these will be validated and returned as-is.
728
+
729
+ * `validate(value: object)` (`bool`) – The `validate()` method takes the same range of input values as the `reconcile` method, and returns `True` when the provided value can be reconciled against an enumeration option, or `False` otherwise.
730
+
731
+ * `options()` (`list[Enum]`) – The `options()` method provides easy access to the list of the enumeration's available options.
732
+
733
+ The benefits of being able to validate and reconcile various input values against an enumeration, include allowing for a controlled vocabulary of options to be checked against, and the ability to convert enumeration values into their corresponding enumeration option. This can be especially useful when working with input data where you need to convert those values to their corresponding enumeration options, and to be able to do so without maintaining boilerplate code to perform the matching and assignment.
734
+
735
+ To make use of the extra functionality for the standard library's `Enum` class, import the `Enum` class from the Enumerific library:
736
+
737
+ ```python
738
+ from enumerific import Enum
739
+
740
+ class Colors(Enum):
741
+ RED = 1
742
+ GREEN = 2
743
+
744
+ val = Colors.RED
745
+ ```
746
+
747
+ You can also import the `Enum` class directly from the `enumerific` library and use it directly:
748
+
749
+ ```python
750
+ from enumerific import Enum
751
+
752
+ class Colors(Enum):
753
+ RED = 1
754
+ ```
755
+
756
+ Some examples of use include the following code samples, where each make use of the example `Colors` class, defined as follows:
757
+
758
+ ```python
759
+ from enumerific import Enum
760
+
761
+ class Colors(Enum):
762
+ RED = 1
763
+ GREEN = 2
764
+ ```
765
+
766
+ #### Example 1: Reconciling a Value
767
+
768
+ ```python
769
+ from enumerific import Enum
770
+
771
+ class Colors(Enum):
772
+ RED = 1
773
+ GREEN = 2
774
+
775
+ # Given a string value in this case
776
+ value = 1
777
+
778
+ # Reconcile it to the associated enumeration option
779
+ color = Colors.reconcile(value)
780
+
781
+ assert color == Colors.RED # asserts successfully
782
+ assert color is Colors.RED # asserts successfully as enums are singletons
783
+ ```
784
+
785
+ #### Example 2: Reconciling an Enumeration Option Name
786
+
787
+ ```python
788
+ from enumerific import Enum
789
+
790
+ class Colors(Enum):
791
+ RED = 1
792
+ GREEN = 2
793
+
794
+ # Given a string value in this case
795
+ value = "GREEN"
796
+
797
+ # Reconcile it to the associated enumeration option
798
+ color = Colors.reconcile(value)
799
+
800
+ assert color == Colors.GREEN # asserts successfully
801
+ assert color is Colors.GREEN # asserts successfully as enums are singletons
802
+ ```
803
+
804
+ #### Example 3: Validating a Value
805
+
806
+ ```python
807
+ from enumerific import Enum
808
+
809
+ class Colors(Enum):
810
+ RED = 1
811
+ GREEN = 2
812
+
813
+ # The value can be an enumeration option's name, its value, or the enumeration option
814
+ value = "RED"
815
+ value = 1
816
+ value = Colors.RED
817
+
818
+ if Colors.validate(value) is True:
819
+ # do something if the value could be validated
820
+ pass
821
+ else:
822
+ # do something else if the value could not be validated
823
+ pass
824
+ ```
825
+
826
+ #### Example 4: Iterating Over Enumeration Options
827
+
828
+ ```python
829
+ from enumerific import Enum
830
+
831
+ class Colors(Enum):
832
+ RED = 1
833
+ GREEN = 2
834
+
835
+ for option in Colors.options():
836
+ # do something with each option
837
+ print(option.name, option.value)
838
+ ```
839
+
840
+ ### Unit Tests
841
+
842
+ The Enumerific library includes a suite of comprehensive unit tests which ensure that the
843
+ library functionality operates as expected. The unit tests were developed with and are run via `pytest`.
844
+
845
+ To ensure that the unit tests are run within a predictable runtime environment where all of the necessary dependencies are available, a [Docker](https://www.docker.com) image is created within which the tests are run. To run the unit tests, ensure Docker and Docker Compose is [installed](https://docs.docker.com/engine/install/), and perform the following commands, which will build the Docker image via `docker compose build` and then run the tests via `docker compose run` – the output of running the tests will be displayed:
846
+
847
+ ```shell
848
+ $ docker compose build
849
+ $ docker compose run tests
850
+ ```
851
+
852
+ To run the unit tests with optional command line arguments being passed to `pytest`, append the relevant arguments to the `docker compose run tests` command, as follows, for example passing `-vv` to enable verbose output:
853
+
854
+ ```shell
855
+ $ docker compose run tests -vv
856
+ ```
857
+
858
+ See the documentation for [PyTest](https://docs.pytest.org/en/latest/) regarding available optional command line arguments.
859
+
860
+ ### Copyright & License Information
861
+
862
+ Copyright © 2024–2025 Daniel Sissman; licensed under the MIT License.