Pydantic and singledispatch ━━━━━━━━━━━━━━━━━━━━━━━━━━━ 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... Date: May 3, 2023 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 / 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. ```{.python 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) ``` ```{.console 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. ```{.python 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. ```{.python where_am_i(settings) ``` results in ```{.console I am in dev ``` Let’s check prod ```{.python where_am_i(Settings.parse_obj({'env': 'prod'}).__root__) ``` results in ```{.console 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. ```{.python 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) ``` FIN ─── I’m really digging pydantic lately and excited to get it built into markata . Not 100% sure if I have a