2.6 KiB
| tags | |
|---|---|
|
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
BaseModelmust "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"}