from pydantic import BaseModel
 
 
class User(BaseModel):
    id: int
    name: str = 'Jane Doe'
 
 
user = User(id='123')
assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123
assert user.model_fields_set == {'id'}
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
 
Either `.model_dump()` or `dict(user)` will provide a dict of fields, but `.model_dump()` can take numerous other arguments. (Note that `dict(user)` will not recursively convert nested models into dicts, but `.model_dump()` will.)

nested models

class Foo(BaseModel):
    count: int
    size: Optional[float] = None
 
 
class Bar(BaseModel):
    apple: str = 'x'
    banana: str = 'y'
 
 
class Spam(BaseModel):
    foo: Foo
    bars: List[Bar]

Arbitrary class instances

支持 read the instance attrs and convert to model field names.

from typing import List
 
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated
 
from pydantic import BaseModel, ConfigDict, StringConstraints
 
Base = declarative_base()
 
 
class CompanyOrm(Base):
    __tablename__ = 'companies'
 
    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))
 
 
class CompanyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)
 
    id: int
    public_key: Annotated[str, StringConstraints(max_length=20)]
    name: Annotated[str, StringConstraints(max_length=63)]
    domains: List[Annotated[str, StringConstraints(max_length=255)]]
 
 
co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""

Error handling

Pydantic will raise ValidationError whenever it finds an error in the data it’s validating.

A single exception of type ValidationError will be raised regardless of the number of errors found, and that ValidationError will contain information about all of the errors and how they happened.

from typing import List
 
from pydantic import BaseModel, ValidationError
 
 
class Model(BaseModel):
    list_of_ints: List[int]
    a_float: float
 
 
data = dict(
    list_of_ints=['1', 2, 'bad'],
    a_float='not a float',
)
 
try:
    Model(**data)
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    list_of_ints.2
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
    a_float
      Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
    """
 

Helper functions

Pydantic provides two classmethod helper functions on models for parsing data:

  • model_validate(): this is very similar to the __init__ method of the model, except it takes a dict or an object rather than keyword arguments. If the object passed cannot be validated, or if it’s not a dictionary or instance of the model in question, a ValidationError will be raised.
  • model_validate_json(): this takes a str or bytes and parses it as json, then passes the result to model_validate().

If you’re passing in an instance of a model to model_validate, you will want to consider setting revalidate_instances in the model’s config. If you don’t set this value, then validation will be skipped on model instances. See the below example:

when use model_validate, set revalidate_instances='always'. if not, the second validate will not validate.

from pydantic import BaseModel, ConfigDict, ValidationError
 
 
class Model(BaseModel):
    a: int
 
    model_config = ConfigDict(revalidate_instances='always')
 
 
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
 
try:
    m2 = Model.model_validate(m)
except ValidationError as e:
    print(e)

Generic models

类似于 java 的泛型

Python - typing 模块 —— TypeVar 泛型 - 小菠萝测试笔记 - 博客园

from typing import Generic, TypeVar
 
from pydantic import BaseModel
 
TypeX = TypeVar('TypeX')
 
 
class BaseClass(BaseModel, Generic[TypeX]):
    X: TypeX
 
 
class ChildClass(BaseClass[TypeX], Generic[TypeX]):
    # Inherit from Generic[TypeX]
    pass
 
 
# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1

其他略

动态创建 Dynamic model creation

from pydantic import BaseModel, create_model
 
DynamicFoobarModel = create_model(
    'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)
 
 
class StaticFoobarModel(BaseModel):
    foo: str
    bar: int = 123
 
from pydantic import BaseModel, create_model
 
 
class FooModel(BaseModel):
    foo: str
    bar: int = 123
 
 
BarModel = create_model(
    'BarModel',
    apple=(str, 'russet'),
    banana=(str, 'yellow'),
    __base__=FooModel,
)
print(BarModel)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])