--- 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. ## 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 ``` ## 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"} ```