Tags
I was reading about pydantic-singledispatch from Giddeon's blog and found it very intersting. I'm getting ready to implement pydantic on my static site generator markata, and I think there are so uses for this idea, so I want to try it out.
The Idea
Let's set up some pydantic settings. We will need separate Models for each
environment that we want to support for this to work. The whole idea is to use
functools.singledispatch
and type hints to provide unique execution for each
environment. We might want something like a path_prefix in prod for
environments like GithubPages that deploy to /<name-of-repo>
while keeping
the root at /
in dev.
Settings Model
Here is our model for our settings. We will create a CommonSettings model
that will be used by all environments. We will also create a DevSettings
model that will be used in dev and ProdSettings
that will be used in prod.
We will use env
as the discriminator so pydantic knows which model to use.
from typing import Literal, Union import pydantic from pydantic import Field from rich import print from typing_extensions import Annotated class CommonSettings(pydantic.BaseSettings): """Common settings for all environments""" debug: bool = False secret_key: str = "secret" algorithm: str = "HS256" access_token_expire_minutes: int = 60 class DevSettings(CommonSettings): """Settings for dev""" env: Literal["dev"] class ProdSettings(CommonSettings): """Settings for prod""" env: Literal["prod"] class Settings(pydantic.BaseSettings): """Settings for all environments""" __root__: Annotated[Union[DevSettings, ProdSettings], Field(discriminator="env")] class Config: env_prefix = "APP_" # Create our settings settings = Settings(__root__={"env": "dev"}).__root__ # or settings = Settings.parse_obj({"env": "dev"}).__root__ print(settings)
DevSettings(debug=False, secret_key='secret', algorithm='HS256', access_token_expire_minutes=60, env='dev')
Singledispatch
Now let's create our where_am_i
function. We will use functools.singledispatch
to provide a unique execution for each environment. It will leverage type
hints to provide a unique execution for each environment.
from functools import singledispatch @singledispatch def where_am_i(obj): ''' Where am I? ''' @where_am_i.register def dev(obj: DevSettings): ''' Where am I? ''' print('I am in dev') @where_am_i.register def prod(obj: ProdSettings): ''' Where am I? ''' print('I am in prod')
Output
Let's call our eample function where_am_i
with our settings and see the
results.
where_am_i(settings)
results in
I am in dev
Let's check prod
where_am_i(Settings.parse_obj({'env': 'prod'}).__root__)
results in
I am in prod
Environment Variables
So far one down side to the TaggedUnion technique is that I am unable to pull env from environment variables. I'm sure there is a way around this with a different model design. Maybe following exactly what Giddeon did.
os.environ.clear() os.environ['APP_ENV'] = 'prod' where_am_i(Settings().__root__)
results in
ValidationError: 1 validation error for Settings __root__ field required (type=value_error.missing) <darkmark.darkmark.DarkMark object at 0x7fcc58bec5d0>
FIN
I'm really digging pydantic lately and excited to get it built into markata. Not 100% sure if I have a