29  Pydantic

from pydantic import BaseModel, PositiveInt

29.1 Pydantic Example

https://docs.pydantic.dev/2.9/#pydantic-examples

29.1.1 Base

from datetime import datetime

class User(BaseModel):
    id: int  
    name: str = 'John Doe'  
    signup_ts: datetime | None  
    tastes: dict[str, PositiveInt]  
external_data = {
    'id': 123,
    'signup_ts': '2019-06-01 12:22',  
    'tastes': {
        'wine': 9,
        b'cheese': 7,  
        'cabbage': '1',  
    },
}
user = User(**external_data)
user
User(id=123, name='John Doe', signup_ts=datetime.datetime(2019, 6, 1, 12, 22), tastes={'wine': 9, 'cheese': 7, 'cabbage': 1})
# access fields as attributes of the model.
user.id
123

29.1.2 Serialization

# convert the model to a dictionary
user_dict = user.model_dump()
user_dict
{'id': 321,
 'name': 'John Doe',
 'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
 'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1}}
# Include
print(user.model_dump(include={"id", "name"}))

# Exclude
print(user.model_dump(exclude={"id", "name"}))
{'id': 321, 'name': 'John Doe'}
{'signup_ts': datetime.datetime(2019, 6, 1, 12, 22), 'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1}}
import json

# convert the model to a JSON
user_json_str = user.model_dump_json()
json.loads(user_json_str)
{'id': 321,
 'name': 'John Doe',
 'signup_ts': '2019-06-01T12:22:00',
 'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1}}

29.1.3 Validation Fail

If validation fails, Pydantic will raise an error with a breakdown of what was wrong

from pydantic import BaseModel, PositiveInt, ValidationError

external_data = {'id': 'not an int', 'tastes': {}}

try:
    User(**external_data)  
except ValidationError as e:
    print(e.errors())
[{'type': 'int_parsing', 'loc': ('id',), 'msg': 'Input should be a valid integer, unable to parse string as an integer', 'input': 'not an int', 'url': 'https://errors.pydantic.dev/2.9/v/int_parsing'}, {'type': 'missing', 'loc': ('signup_ts',), 'msg': 'Field required', 'input': {'id': 'not an int', 'tastes': {}}, 'url': 'https://errors.pydantic.dev/2.9/v/missing'}]

29.1.4 Mutable Model

By default, models are mutable and field values can be changed through attribute assignment:

user.id = 321
assert user.id == 321

29.2 Field

The Field function is used to customize and add metadata to fields of models.

29.2.1 Default values

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(default='John Doe')


user = User()
user
User(name='John Doe')

You can also use default_factory to define a callable that will be called to generate a default value.

from uuid import uuid4
from pydantic import BaseModel, Field


class User(BaseModel):
    id: str = Field(default_factory=lambda: uuid4().hex)

29.3 JSON Schema

29.3.1 Generating JSON Schema

import json
from enum import Enum

from typing import Annotated

from pydantic import BaseModel, Field
from pydantic.config import ConfigDict


class FooBar(BaseModel):
    count: int
    size: float | None = None


class Gender(str, Enum):
    male = 'male'
    female = 'female'
    other = 'other'
    not_given = 'not_given'


class MainModel(BaseModel):
    """
    This is the description of the main model
    """

    model_config = ConfigDict(title='Main')

    foo_bar: FooBar
    gender: Annotated[Gender | None, Field(alias='Gender')] = None
    snap: int = Field(
        42,
        title='The Snap',
        description='this is the value of snap',
        gt=30,
        lt=50,
    )


main_model_schema = MainModel.model_json_schema() 
# JSON String
print(json.dumps(main_model_schema, indent=2)) 
{
  "$defs": {
    "FooBar": {
      "properties": {
        "count": {
          "title": "Count",
          "type": "integer"
        },
        "size": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        }
      },
      "required": [
        "count"
      ],
      "title": "FooBar",
      "type": "object"
    },
    "Gender": {
      "enum": [
        "male",
        "female",
        "other",
        "not_given"
      ],
      "title": "Gender",
      "type": "string"
    }
  },
  "description": "This is the description of the main model",
  "properties": {
    "foo_bar": {
      "$ref": "#/$defs/FooBar"
    },
    "Gender": {
      "anyOf": [
        {
          "$ref": "#/$defs/Gender"
        },
        {
          "type": "null"
        }
      ],
      "default": null
    },
    "snap": {
      "default": 42,
      "description": "this is the value of snap",
      "exclusiveMaximum": 50,
      "exclusiveMinimum": 30,
      "title": "The Snap",
      "type": "integer"
    }
  },
  "required": [
    "foo_bar"
  ],
  "title": "Main",
  "type": "object"
}

29.3.2 Type Adapter

from typing import List

from pydantic import TypeAdapter

adapter = TypeAdapter(List[int])
print(adapter.json_schema())
{'items': {'type': 'integer'}, 'type': 'array'}