No project description provided
Project description
NamespacedEnums
Library for defining containers within Enum Python classes.
Installation
Run
$ pip install namespaced_enums
Using mypy? See mypy support to enable the bundled plugin.
Usage
Library provides two special descriptors for Enum classes:
EnumContainerStrictEnumContainer
as well as an additional enum-subclass:
NamespacedEnum
EnumContainer
This allows for defining arbitrary containers within Enum classes, e.g.
from enum import Enum
from namespaced_enum import EnumContainer
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
LIGHT_RED = 4
LIGHT_GREEN = 5
LIGHT_BLUE = 6
DARK_RED = 7
DARK_GREEN = 8
DARK_BLUE = 9
light_colors = EnumContainer([LIGHT_RED, LIGHT_GREEN, LIGHT_BLUE])
dark_colors = EnumContainer([DARK_RED, DARK_GREEN, DARK_BLUE])
---
>>> print(Color.RED.value in Color.light_colors) # False
>>> print(Color.DARK_BLUE.value in Color.dark_colors) # True
# Caution!
# Values within containers are of enum value types
>>> print(Color.dark_colors) # prints [7, 8, 9] and not [DARK_RED, DARK_GREEN, DARK_BLUE]!
StrictEnumContainer and NamespacedEnum
StrictEnumContainer accepts only dict containers. Using this descriptor within
a class inheriting from NamespacedEnum will enforce that the provided
dictionary contains all enum possible values:
from namespaced_enums import NamespacedEnum, StrictEnumContainer
class Food(NamespacedEnum):
spam = "spam"
eggs = "eggs"
foo = "foo"
reactions = StrictEnumContainer(
{
spam: "I like it",
eggs: "I don't like it...",
foo: "I like?",
}
)
---
>>> print(Food.reactions[Food.spam]) # "I like it"
# Caution!
# Unlike the `EnumContainer` this one converts dict keys to enums!
>>> print(list[Food.reactions.keys()]) # prints [<Food.spam: spam>, <Food.eggs: eggs>, <Food.foo: foo>]
Forgetting to provide all possible enum values within a strict container will
raise a RuntimeError:
from namespaced_enums import NamespacedEnum, StrictEnumContainer
class Food(NamespacedEnum):
spam = "spam"
eggs = "eggs"
foo = "foo"
reactions = StrictEnumContainer(
{
spam: "I like it",
eggs: "I don't like it...",
# missing foo in the dict
}
)
---
>>> # Trying to start the program raises a `RuntimeError`:
# The following Food fields do not contain all possible enum values: ['reactions']
mypy support
The library ships with a mypy plugin (and a
py.typed marker). Without it, mypy sees container attributes as enum members
and rejects indexing them ("Enum" is not indexable).
Enabling the plugin
mypy does not load plugins automatically, so enabling support is two steps:
1. Install the optional dependency (requires mypy >= 1.0):
$ pip install namespaced_enums[mypy]
2. Register the plugin in your mypy configuration. Add the following to whichever config file your project uses:
mypy.ini (or the [mypy] section of setup.cfg):
[mypy]
plugins = namespaced_enums.plugin
pyproject.toml:
[tool.mypy]
plugins = ["namespaced_enums.plugin"]
That's it — no import changes are needed in your own code.
What you get
With the plugin enabled, containers are typed correctly instead of being seen as enum members:
class DogBreed(NamespacedEnum):
BULLDOG = 'bulldog'
PUG = 'pug'
SHIBA = 'shiba'
size = StrictEnumContainer({BULLDOG: 40, PUG: 30, SHIBA: 35})
reveal_type(DogBreed.size) # dict[DogBreed, int]
n: int = DogBreed.size[DogBreed.PUG] # ok, no "not indexable" error
More importantly, the plugin turns the runtime RuntimeError for an incomplete
StrictEnumContainer into a static error (error code namespaced-enum),
so a forgotten member is caught by mypy instead of at import time:
class DogBreed(NamespacedEnum):
BULLDOG = 'bulldog'
PUG = 'pug'
SHIBA = 'shiba'
size = StrictEnumContainer({BULLDOG: 40, PUG: 30})
# error: StrictEnumContainer "size" is missing keys for enum members: SHIBA [namespaced-enum]
Rationale
It's a common practice to use enums in a project as a way to denote "characteristics" of certain objects.
Consider the following example:
from enum import Enum
class DogBreed(Enum):
BULLDOG = 'bulldog'
PUG = 'pug'
SHIBA = 'shiba'
class Dog:
def __init__(self, breed: DogBreed) -> None:
self.breed = breed
@property
def size(self) -> int:
"""Returns size of the dog (in centimeters)."""
if self.breed == DogBreed.BULLDOG:
return 40
elif self.breed == DogBreed.PUG:
return 30
elif self.breed == DogBreed.SHIBA:
return 35
else: # pragma: no cover
raise ValueError(f"Unknown dog breed: {self.breed}")
The Dog.size property implementation poses a threat when it comes to further
additions to the DogBreed enum:
- programmers would have to remember to update it every time they add a new breed support. What if there are more such properties like color, weight or tail length?
- the if-elif-else chain is hard to test as the last
elseclause would require injecting some arbitrary value into theDogBreedenum during runtime in order to ensure proper coverage. In my experience, most developers would prefer to add# pragma: no coverinstead. - the size characteristic should arguably be not part of the
Dogclass as it is more specific to theDogBreedenum - theDogclass should be aware of how to retrieve that data.
That's why I decided to create a NamespacedEnum class that allows for writing
self-contained enums, that can help in maintaining the integrity of the written
code. The example above could be rewritten in a following manner:
from namespaced_enums import NamespacedEnum, StrictEnumContainer
class DogBreed(NamespacedEnum):
BULLDOG = 'bulldog'
PUG = 'pug'
SHIBA = 'shiba'
size = StrictEnumContainer({
BULLDOG: 40,
PUG: 30,
SHIBA: 35,
})
class Dog:
def __init__(self, breed: DogBreed) -> None:
self.breed = breed
@property
def size(self) -> int:
"""Returns size of the dog (in centimeters)."""
return DogBreed.size[self.breed]
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file namespaced_enums-0.1.0.tar.gz.
File metadata
- Download URL: namespaced_enums-0.1.0.tar.gz
- Upload date:
- Size: 12.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a7dde383662d5f50ddbabff50b0e96a002b6a4fe9a13390cebbc310006fa583
|
|
| MD5 |
3bbafd6512708f86c6c03fade3c02817
|
|
| BLAKE2b-256 |
b61549f40c784c8d39b5122ce5a82fa901c2d5e3feb1d3e3a319c3ea7d22a523
|
Provenance
The following attestation bundles were made for namespaced_enums-0.1.0.tar.gz:
Publisher:
publish.yml on Waszker/namespaced_enums
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namespaced_enums-0.1.0.tar.gz -
Subject digest:
9a7dde383662d5f50ddbabff50b0e96a002b6a4fe9a13390cebbc310006fa583 - Sigstore transparency entry: 2211642959
- Sigstore integration time:
-
Permalink:
Waszker/namespaced_enums@9dacb999a296a6918cc23aeec2fe0c1dc323e9aa -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Waszker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9dacb999a296a6918cc23aeec2fe0c1dc323e9aa -
Trigger Event:
release
-
Statement type:
File details
Details for the file namespaced_enums-0.1.0-py3-none-any.whl.
File metadata
- Download URL: namespaced_enums-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c3ab6ce924f83dfc12f1564dd4f59f065dabe04fad43268914854afef7dad86
|
|
| MD5 |
3f1271a226469a0ad4eea37c2cf9b163
|
|
| BLAKE2b-256 |
cd47e6f2b7b9db37e14fc758e9f47e6199b46771cc54fd98496b08c32620d55b
|
Provenance
The following attestation bundles were made for namespaced_enums-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Waszker/namespaced_enums
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namespaced_enums-0.1.0-py3-none-any.whl -
Subject digest:
2c3ab6ce924f83dfc12f1564dd4f59f065dabe04fad43268914854afef7dad86 - Sigstore transparency entry: 2211642997
- Sigstore integration time:
-
Permalink:
Waszker/namespaced_enums@9dacb999a296a6918cc23aeec2fe0c1dc323e9aa -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Waszker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9dacb999a296a6918cc23aeec2fe0c1dc323e9aa -
Trigger Event:
release
-
Statement type: