Config-file strategy pattern enabler: easily create Pydantic-friendly Enums with a function to call for each member
Project description
ͱ Start with the "why"
Consider this config file:
aggregation: mean
Not good
from numpy import mean, median
import yaml
config = yaml.safe_load('aggregation: mean')
runner = eval(config['aggregation']) # OUCH executable YAML
Much better, but still hella wobbly
import numpy as np, yaml
dispatcher = {"mean": np.mean, "median": np.median}
config = yaml.safe_load('aggregation: mean')
runner = dispatcher[config['aggregation']]
Tidier
import numpy as np, yaml
from pydantic import BaseModel, field_validator
dispatcher = {"mean": np.mean, "median": np.median}
class Config(BaseModel):
aggregation: str
@field_validator('aggregation')
@classmethod
def agg_must_be_valid(cls, v: str) -> str:
if v not in dispatcher:
raise ValueError('Invalid aggregation')
return v.title()
config = Config(yaml.safe_load('aggregation: mean'))
runner = dispatcher[config.aggregation]
Very much better
import numpy as np, yaml
from pydantic import BaseModel
from dispatcher import Dispatcher
# shortcut utility that creates a DispatchEnum object
Aggregation = Dispatcher(
mean = np.mean,
median = np.median
)
class Config:
aggregation: Aggregation = Aggregation.MEAN
config = Config(yaml.safe_load('aggregation: mean'))
runner = config.aggregation # ding
# now the "aggregation" YAML field is parsed by pydantic into
# an Aggregation object derived from an Enum that's also callable!
runner0 = lambda xs: config.aggregation(xs) # ding ding ding ding.
The "what"
This code provides a DispatchEnum
class that subclasses from Enum but holds an
additional value for each member. This is most useful in combination with Pydantic,
which is able to parse Enum-valued fields received as strings, i.e.
class Parity(Enum):
ODD = "odd"
EVEN = "even"
class Parser(BaseModel):
check_parity: Parity
config = Parser({"check_parity": "odd" })
print(config.check_parity) # prints Parity.ODD
With DispatchEnum
we're able to assign an additional property to each Enum member:
class Parity(DispatchEnum):
ODD = "odd"
EVEN = "even"
Parity.assign(Parity.ODD, lambda x: x % 2 == 1)
Parity.assign(Parity.EVEN, lambda x: x % 2 == 0)
# or
Parity.from_dict({"ODD": lambda x: x % 2 == 1, "EVEN": lambda x: x % 2 == 0})
print(Parity.ODD(2)) # prints False
Therefore DispatchEnum
is both a "dispatcher" (mapping a string identifier to a function)
and an Enum
(enabling Pydantic goodness).
For further convenience, the Dispatcher
function creates a DispatchEnum filling in member names:
Aggregation = Dispatcher(
mean = np.mean,
median = np.median
)
# does the same as
class Aggregation(DispatchEnum):
MEAN: "mean"
MEDIAN: "median"
Aggregation.from_dict({"mean": np.mean, "median": np.median})
Installation
Right now you should download dispatch.py
and vendor it in. Soonishly a more mature
version will be hitting PyPI too.
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
Hashes for dispatcher_enum-0.1.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2ffb3c4e84e6db109003bc0e9a61ae6c6fd02c834ec1eff65e40cf686746af11 |
|
MD5 | 94eeb07c71a97d74e62e9a9d25f55166 |
|
BLAKE2b-256 | 0b8b9dcb5e6b11330eba761b7a898048f448caeff5706e84d5e330ce0569a049 |