From 2bb402ad405b5d011357522f3aa90ca6450b4828 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Mon, 10 Aug 2026 17:08:27 -0500 Subject: [PATCH] Add Combine StrEnum With Pydantic For Union Type as a Python TIL --- README.md | 3 +- ...ne-strenum-with-pydantic-for-union-type.md | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 python/combine-strenum-with-pydantic-for-union-type.md diff --git a/README.md b/README.md index c569d07..4010cec 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1857 TILs and counting..._ +_1858 TILs and counting..._ See some of the other learning resources I work on: @@ -1087,6 +1087,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md) - [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md) - [Check Precondition Before Click Arg Parsing](python/check-precondition-before-click-arg-parsing.md) +- [Combine StrEnum With Pydantic For Union Type](python/combine-strenum-with-pydantic-for-union-type.md) - [Commit Writes From Executed SQLite Statements](python/commit-writes-from-executed-sqlite-statements.md) - [Configure Other Attributes Of Dataclass Field](python/configure-other-attributes-of-dataclass-field.md) - [Control Passing Of Time In Tests](python/control-passing-of-time-in-tests.md) diff --git a/python/combine-strenum-with-pydantic-for-union-type.md b/python/combine-strenum-with-pydantic-for-union-type.md new file mode 100644 index 0000000..171a18e --- /dev/null +++ b/python/combine-strenum-with-pydantic-for-union-type.md @@ -0,0 +1,54 @@ +# Combine StrEnum With Pydantic For Union Type + +I want a [Pydantic](https://pydantic.dev/docs/validation/latest/get-started/) +model that I can use to verify the shape and values of user-provided config +file. Let's say, for now, the only config value supported by the file is +`storage_format` and it can be one of three values (`json`, `sqlite`, +`inmemory`). + +I can start with a +[`StrEnum`](https://docs.python.org/3/library/enum.html#enum.StrEnum) class to +represent the set of valid values for the storage format field. + +```python +from enum import StrEnum +from typing import Literal + +class StorageFormat(StrEnum): + SQLITE = "sqlite" + JSON = "json" + INMEMORY = "inmemory" + + @classmethod + def default(cls) -> Literal[StorageFormat.SQLITE]: + return cls.SQLITE +``` + +Notice I've also provided a class method called `default` for serving up a +fallback default value. + +Here is the corresponding Pydantic `CliConfig` model which uses the +`StorageFormat` string enum class as the type signature for its `storage_format` +field. + +```python +from pydantic import BaseModel + +class CliConfig(BaseModel): + storage_format: StorageFormat = StorageFormat.default() +``` + +For `storage_format` to be valid in the context of `CliConfig`, it must be one +of the values defined by that `StrEnum`. I get auto-complete, I get type +checking, and I can validate a config file I've just read into memory (in this +case, using +[`model_validate_json`](https://pydantic.dev/docs/validation/dev/api/pydantic/base_model/#pydantic.BaseModel.model_validate_json)). + +```python +def _read_config(self) -> CliConfig: + if self.exists(): + raw_json = self.config_file.read_text() + return CliConfig.model_validate_json(raw_json) + + return CliConfig() +```