eolas/zk/Pydantic.md

114 lines
2.6 KiB
Markdown
Raw Permalink Normal View History

2026-01-09 09:18:51 +00:00
---
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](/zk/Type_hinting.md).
## Basic usage
We define our model which must always inherit from the Pydantic `BaseModel`:
```py
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
```
Then we validate against data:
```py
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.
```python
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.
2026-01-11 18:27:51 +00:00
## Transform incoming values (before validation)
In the following example I convert an incoming `str` value to a list of strings:
```py
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
```
2026-01-09 09:18:51 +00:00
## 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.
```py
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:
```py
class User(BaseModel):
id: int
name: str
@property
def to_alt_format(self):
alternative_id: self.id,
alternative_name: self.name
```
Then:
```py
raw_user_data = {"id": "1234", name: "Thomas"}
user = User(**raw_user_data)
print(user.to_alt_format)
# {"alternative_id": "1234", "name": "Thomas"}
```