Config-file strategy pattern enabler: easily create Pydantic-friendly Enums with a function to call for each member
Project description
ͱ Start with the "why"
DispatchEnum is the Pythonic way to deal with the "strategy in config" pattern, where we want choices in implementation details ("strategies") to be available outside Python code proper.
Consider this cfg file:
aggregation: mean
length: square
We see this typically when we want to allow for different aggregating functions (mean, median...) to be used in a functionality that meaningfully accepts them.
Not good
from numpy import mean, median, abs # rock the global namespace!
import yaml
square = lambda x: x*x
def excess(lst, cfg):
agg = eval(cfg['aggregation'])(lst) # OUCH executable YAML
return [eval(cfg['length'])(val - agg) for val in lst] # OOF right in the feels
cfg = yaml.safe_load(config.yaml)
print(excess([1,2,3], cfg)) # prints [1,0,1]
Much better, but still hella wobbly
import numpy as np, yaml
agg_dispatcher = {"mean": np.mean, "median": np.median}
len_dispatcher = {"square": lambda x: x*x, "abs": np.abs}
def excess(lst, cfg):
agg = agg_dispatcher[cfg['aggregation']](lst)
return [len_dispatcher[cfg['length']](val - agg) for val in lst]
cfg = yaml.safe_load(config.yaml)
print(excess([1,2,3], cfg)) # same as above
Safer with Pydantic but drowning in boilerplate
import numpy as np, yaml
from pydantic import BaseModel, field_validator
agg_dispatcher = {"mean": np.mean, "median": np.median}
len_dispatcher = {"square": lambda x: x*x, "abs": np.abs}
class Config(BaseModel):
aggregation: str
length: str
@field_validator('aggregation')
@classmethod
def agg_must_be_valid(cls, v: str) -> str:
if v not in agg_dispatcher:
raise ValueError('Invalid aggregation')
return v
@field_validator('length')
@classmethod
def len_must_be_valid(cls, v: str) -> str:
if v not in len_dispatcher:
raise ValueError('Invalid length')
return v
def excess(lst, cfg):
agg = agg_dispatcher[cfg.aggregation](lst)
return [len_dispatcher[cfg.length](val - agg) for val in lst]
cfg = yaml.safe_load(config.yaml)
print(excess([1,2,3], cfg)) # same as above
Class and quality
import numpy as np, yaml
from pydantic import BaseModel
from dispatcher import Dispatcher
# shortcut utility that creates a DispatchEnum object
AggregationStrategy = Dispatcher(
mean = np.mean,
median = np.median
)
LengthStrategy = Dispatcher(
square = lambda x: x*x,
abs = np.abs
)
class Config:
aggregation: AggregationStrategy = AggregationStrategy.MEAN
length: LengthStrategy
cfg = Config(yaml.safe_load(config.yaml))
def excess(lst, cfg):
agg = cfg.aggregation(lst) # ding ding ding ding
return [cfg.length(val - agg) for val in lst]
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
cfg = Parser({"check_parity": "odd" })
print(cfg.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.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:
AggregationStrategy = Dispatcher(
mean = np.mean,
median = np.median
)
which is shorthand for
class AggregationStrategy(DispatchEnum):
MEAN: "mean"
MEDIAN: "median"
AggregationStrategy.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
File details
Details for the file dispatcher-enum-0.1.1.tar.gz
.
File metadata
- Download URL: dispatcher-enum-0.1.1.tar.gz
- Upload date:
- Size: 7.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.0.0 CPython/3.9.18
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 264d463f060ec77ab766157f98506fd083a09690099e3240b813a142e96d70b9 |
|
MD5 | dbf20fecd450afa1b83100d731af4a2a |
|
BLAKE2b-256 | e37c51ace662361b79ebe15ede87af8cce903bd2045225c88bc14725b86deb86 |