eolas/zk/Pydantic.md
2026-01-11 18:27:51 +00:00

2.6 KiB

tags
python

Pydantic provides runtime type-checking and schema validation, similar to the Zod library for TypeScript.

It integrates seamlessly with native Python type hinting.

Basic usage

We define our model which must always inherit from the Pydantic BaseModel:

class User(BaseModel):
    id: int
    name: str = 'John Doe'
    signup_ts: datetime | None

Then we validate against data:

external_data = {
    'id': 123,
    'signup_ts': '2019-06-01 12:22',
}

user = User(**external_data)

Note here we can set values when we define the schema, as with name: str = 'John Doe'.

Extending schemas

In scenarios where you have common as well as variant values between different schemas you can extend simply by passing the shared schema to each variant in a composable manner.

class CommonProperties(BaseModel):
    category: str
    meta_id: str


class VariantProperties(CommonProperties):
    not_shared_field: int

Note that the BaseModel must "drill-down"; it must be inherited at each stage.

Transform incoming values (before validation)

In the following example I convert an incoming str value to a list of strings:

class MySchema(BaseModel)
    sub_genres: Optional[List[str]] = None)
        @field_validator("sub_genres", mode="before")
        @classmethod
        def string_to_list(cls, v):
            if isinstance(v, str):
                return [v]
            return v

Aliasing

Sometimes the incoming data that you wish to validate will have field names different to those that you want to use internally within your program.

You can use aliases to handle these values. This way, Pydantic won't error when it receives them, whilst also representing the data in the format that you want to use.

class IncomingRecord:
    sub_genres: Optional[str] = Field(default=None, validation_alias="subgenres")

This schema will accept both sub_genres and subgenres as the key for the field but once instantiated, it will represent the field as sub_genres.

Methods

You can add methods to the Pydantic schemas. These will execute whenever the schema is instantiated. I used a method recently to transform the data of the base schema into a dictionary that could be consumed by a particular API, e.g:

class User(BaseModel):
    id: int
    name: str

    @property
    def to_alt_format(self):
        alternative_id: self.id,
        alternative_name: self.name

Then:

raw_user_data = {"id": "1234", name: "Thomas"}
user = User(**raw_user_data)
print(user.to_alt_format)

# {"alternative_id": "1234", "name": "Thomas"}