diff --git a/zk/Pydantic.md b/zk/Pydantic.md new file mode 100644 index 0000000..432917c --- /dev/null +++ b/zk/Pydantic.md @@ -0,0 +1,98 @@ +--- +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. + +## 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"} + +```