Pydantic v2 的使用

一、前言

Pydantic 是一个 Python 数据验证 和 设置管理 库,使用 Python 类型 注解。具有以下特点:

1.1 核心功能

  • 数据验证:自动验证数据类型和约束条件
  • 类型转换:自动将输入数据转换为声明类型
  • Schema 生成:自动生成 JSON Schema

1.2 主要优势

  • 使用直观,基于类型注解
  • 与 FastAPI 继承
  • 优秀的错误处理机制

1.3 常见使用场景

  • API 请求/响应验证
  • 配置管理
  • 数据序列化/反序列化
  • 数据库模型验证

二、基础用法

2.1 基本模型使用

from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel


class User(BaseModel):
    id: int  # 必填字段
    name: str = "Jack"  # 有默认值,选填字段
    signup_ts: Optional[datetime] = None
    friends: List[int] = []  # 列表元素是 int 类型 或者可以直接转化成 int 类型


external_data = {
    "id": "123",
    "signup_ts": "2022-12-22 12:00",
    "friends": [1, 2, "3"]
}

# 实例化
user = User(**external_data)
print(user.id, user.friends)
print(repr(user))
print(user.model_dump())  # 2.0 中 dict 修改为 model_dump

"""
输出
123 [1, 2, 3]
User(id=123, name='Jack', signup_ts=datetime.datetime(2022, 12, 22, 12, 0), friends=[1, 2, 3])
{'id': 123, 'name': 'Jack', 'signup_ts': datetime.datetime(2022, 12, 22, 12, 0), 'friends': [1, 2, 3]}
"""

2.2 类型注解

from typing import Union, List, Dict
from pydantic import BaseModel, constr, Field


class Product(BaseModel):
    name: constr(min_length=1, max_length=50)
    price: float = Field(gt=0)
    tags: List[str] = []
    metadata: Dict[str, Union[str, int]] = {}


data = {
    "name": "Apple",
    "price": 8999,
    "tags": ["时尚", "优雅"],
    "metadata": {
        "types": "Pro",
        "price": 19999
    }
}

product = Product(**data)
print(product.model_dump())

三、新特性

3.1 计算字段

@computed_field 的作用

  • ​ @computed_field 是 Pydantic V2 引入的新特性,用于声明一个字段为“计算字段”。
  • 计算字段不会直接存储在模型实例中,而是根据其他字段的值动态计算。
  • 在序列化时,计算字段会被包含在模型的输出中(例如,当调用 .model_dump() 或 .dict() 方法时)
from pydantic import computed_field, BaseModel


class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    def area(self) -> float:
        return self.width * self.height
  • widthheight: 表示矩形的宽度和高度,类型为浮点数 (float)。
  • area: 使用 @computed_field 装饰器定义的计算字段,表示矩形的面积。
  • 计算逻辑:area = width * height。
  • 返回值类型为 float。

案例使用

# 创建一个矩形对象
rectangle = Rectangle(width=5.0, height=3.0)

# 访问计算字段 area
print(rectangle.area)  # 输出: 15.0

# 更新矩形的宽度和高度
rectangle.width = 10.0
rectangle.height = 4.0

# 再次访问计算字段 area
print(rectangle.area)  # 输出: 40.0

# 将模型转换为字典
rectangle_dict = rectangle.model_dump()
print(rectangle_dict)

# 输出:
# {'width': 10.0, 'height': 4.0, 'area': 40.0}

# 将模型转换为 JSON 格式
rectangle_json = rectangle.model_dump_json()
print(rectangle_json)

# 输出:
# {"width": 10.0, "height": 4.0, "area": 40.0}

3.2 字段验证器

  • field_validator: 验证单个字段的装饰器(Pydantic V2 中使用)。
  • model_validator: 验证整个模型的装饰器(Pydantic V2 中使用)。
from pydantic import field_validator, model_validator

class Order(BaseModel):
    items: List[str]
    total: float
    
    @field_validator('total')
    def validate_total(cls, v):
        if v < 0:
            raise ValueError('Total must be positive')
        return v
    
    @model_validator(mode='after')
    def validate_order(self) -> 'Order':
        if not self.items and self.total > 0:
            raise ValueError('Cannot have total without items')
        return self
  • @field_validator(‘total’): 这是一个字段级别的验证器,专门验证 total 字段。
  • @model_validator(mode=‘after’): 这是一个模型级别的验证器,在所有字段验证完成后执行

案例使用

# 创建一个有效的订单
order = Order(items=["item1", "item2"], total=100.0)
print(order.model_dump())

# 输出
# {'items': ['item1', 'item2'], 'total': 100.0}

验证负数 total
try:
    invalid_order = Order(items=["item1"], total=-50.0)
except ValueError as e:
    print(e)

# 输出:
# Total must be positive

# 验证没有商品但有总价的情况
try:
    invalid_order = Order(items=[], total=50.0)
except ValueError as e:
    print(e)

# 输出:
# Cannot have total without items

# 验证空订单
valid_order = Order(items=[], total=0.0)
print(valid_order)

# 输出:
# items=[] total=0.0

四、高级特性

4.1 泛型模型

from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar("T")


class Response(BaseModel, Generic[T]):
    code: int
    data: T
    message: str


class User(BaseModel):
    id: int
    name: str


class UserResponse(Response[User]):
    pass
  • GenericTypeVar : 来自 Python 的 typing 模块,用于定义泛型类型
    • TypeVar('T'):定义一个类型变量 T ,表示可以是任意类型
    • Generic[T]:表示一个泛型类,允许使用类型变量 T
  • Response:是一个泛型类,继承了 BaseModelGeneric[T]
  • 泛型参数 T 表示 data 字段的类型,可以是任意类型(例如 intstr,自定义类等)
  • data:表示响应的数据,类型为泛型T

案例使用

# 创建一个 User 对象
user = User(id=1, name="Alice")

# 创建一个 UserResponse 对象
response = UserResponse(code=200, data=user, message="Success")

# 打印响应对象
print(response)

# 输出:
# code=200 data=User(id=1, name='Alice') message='Success'

# 转换为字典
response_dict = response.model_dump()
print(response_dict)

# 输出:
# {'code': 200, 'data': {'id': 1, 'name': 'Alice'}, 'message': 'Success'}

# 转换为 JSON
response_json = response.model_dump_json()
print(response_json)

# 输出:
# {"code": 200, "data": {"id": 1, "name": "Alice"}, "message": "Success"}

总结

  • 泛型类的作用

    • 使用 GenericTypeVar 可以定义通用的数据模型,适用于多种数据类型
  • 适用场景

    • API 响应:统一的响应格式
    • 不同类型的 data

4.2 自定义类型

from pydantic import GetCoreSchemaHandler, BaseModel
from pydantic_core import core_schema
from typing_extensions import Annotated, Any


class Color:
    def __init__(self, value: str):
        self.value = value

    @classmethod
    def __get_pydantic_core_schema__(cls, _source_type: type[Any],
                                     _handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
        # 使用 Pydantic 提供的 core_schema.str_schema 方法创建验证规则
        return core_schema.str_schema(
            pattern="^#[0-9a-fA-F]{6}$"
        )


# 定义类型别名
ColorType = Annotated[str, Color]
  • GetCoreSchemaHandler: Pydantic 提供的一个工具,用于动态生成核心模式(Core Schema)。
  • CoreSchema: Pydantic Core 的核心模式定义,用于描述数据验证逻辑。
  • Annotated: 来自 typing_extensions 模块,用于为类型添加元数据或额外的行为。
  • get_pydantic_core_schema 是 Pydantic 的特殊方法,用于定义自定义类型的验证逻辑。
    • 这里的逻辑是:
      • 验证输入是否是一个字符串 (type=‘str’)。
      • 字符串必须符合正则表达式 ^#[0-9a-fA-F]{6}$,即以 # 开头的 6 位十六进制颜色值(如 #FFFFFF 或 #123abc)
  • 使用 Annotated 将 str 类型与 Color 类关联起来。这样,ColorType 表示一个字符串类型,但会使用 Color 类中定义的验证逻辑。

案例使用

  1. 在 Pydantic 模型中使用 ColorType

    from pydantic import BaseModel
    
    class Product(BaseModel):
        name: str
        color: ColorType
    
    • 定义了一个 Product 模型,其中 color 字段使用了 ColorType。
    • color 字段的值会被验证为符合 # 开头的 6 位十六进制颜色值。
  2. 创建有效对象

    # 创建一个有效的 Product 对象
    product = Product(name="Laptop", color="#FFFFFF")
    print(product)
    
    # 输出:
    # name='Laptop' color='#FFFFFF'
    
  3. 验证无效颜色值

try:
    invalid_product = Product(name="Laptop", color="INVALID")
except ValueError as e:
    print(e)

4.3 嵌套模型

from pydantic import BaseModel
from typing import List


class Address(BaseModel):
    street: str
    city: str
    country: str


class User(BaseModel):
    name: str
    address: Address
    alternate_addresses: List[Address] = []

案例使用

创建 User 对象

# 创建主地址和备用地址
main_address = Address(street="123 Main St", city="New York", country="USA")
alt_address1 = Address(street="456 Elm St", city="Los Angeles", country="USA")
alt_address2 = Address(street="789 Oak St", city="Chicago", country="USA")

# 创建用户对象
user = User(
    name="Alice",
    address=main_address,
    alternate_addresses=[alt_address1, alt_address2]
)

# 打印用户对象
print(user)

"""
输出

name='Alice' address=Address(street='123 Main St', city='New York', country='USA') alternate_addresses=[Address(street='456 Elm St', city='Los Angeles', country='USA'), Address(street='789 Oak St', city='Chicago', country='USA')]
"""

访问嵌套字段

# 访问主地址的城市
print(user.address.city)  # 输出: New York

# 访问备用地址的第一个地址的街道
print(user.alternate_addresses[0].street)  # 输出: 456 Elm St

序列化为字典或 JSON

user_dict = user.model_dump()
print(user_dict)


# 转换为 JSON
user_json = user.model_dump_json(indent=4)
print(user_json)

4.4 ORM 模型

from pydantic import BaseModel, constr
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base  # 更新导入路径

Base = declarative_base()


# ORM 模型
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)))


# Pydantic 模型
class CompanyMode(BaseModel):
    id: int
    public_key: constr(max_length=20)  # 修正拼写错误
    name: constr(max_length=63)
    domains: List[constr(max_length=255)]

    class Config:
        from_attributes = True  # 替代 orm_mode = True


# 测试数据
co_orm = CompanyOrm(
    id=123,
    public_key="footbar",  # 修正拼写错误
    name="Testing",
    domains=["example.com", "imooc.com"]
)

# 将 ORM 对象转换为 Pydantic 模型
print(CompanyMode.model_validate(co_orm).model_dump())  # 使用 model_validate 方法

五、数据验证

5.1 基础数据验证

from pydantic import EmailStr, conint, confloat, Field
from pydantic import BaseModel


class Employee(BaseModel):
    email: EmailStr
    age: conint(ge=18, le=65)
    salary: confloat(gt=0)
    department: str = Field(pattern='^[A-Z]{2,}$')
  • email: 表示员工的电子邮件地址,类型为 EmailStr,会自动验证是否符合电子邮件格式。
  • age: 表示员工的年龄,类型为整数 (int),并使用 conint 约束其范围:
    • ge=18: 年龄必须大于或等于 18。
    • le=65: 年龄必须小于或等于 65。
  • salary: 表示员工的薪水,类型为浮点数 (float),并使用 confloat 约束其范围:
    • gt=0: 薪水必须大于 0。
  • department: 表示员工所属部门,类型为字符串 (str),并通过 Field 添加正则表达式约束:
    • pattern=‘1{2,}$’: 部门名称必须由至少两个大写字母组成。

5.2 自定义验证

from pydantic import ValidationError

class Transaction(BaseModel):
    amount: float
    currency: str
    
    @field_validator('currency')
    def validate_currency(cls, v):
        if v not in ['USD', 'EUR', 'GBP']:
            raise ValueError('Invalid currency')
        return v.upper()
  • @field_validator(‘currency’):
    • 这是一个字段级别的验证器,专门用于验证 currency 字段。
    • 如果 currency 不在允许的列表 [‘USD’, ‘EUR’, ‘GBP’] 中,则抛出 ValueError 异常,提示 “Invalid currency”。
    • 如果验证通过,则将 currency 转换为大写形式并返回。

六、序列化

6.1 Json 处理

from pydantic import BaseModel
from typing import Dict, Any
from datetime import datetime


class Config(BaseModel):
    name: str
    data: Dict[str, Any]

    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat()
        }


# 序列化
config = Config(name="test", data={"time": datetime.now()})
json_str = config.model_dump_json()
print(json_str)
# 反序列化
config = Config.model_validate_json(json_str)
print(config)

6.2 导出选项

from pydantic import BaseModel, Field


class User(BaseModel):
    id: int
    password: str
    email: str

    class Config:
        json_schema_extra = {
            "example": {
                "id": 1,
                "password": "secret",
                "email": "user@example.com"
            }
        }
  • Config 类:
    • json_schema_extra 是一个配置选项,用于在生成 JSON Schema 时添加额外的信息。
    • 在这里,我们提供了一个示例数据结构(example),包含字段 id、password 和 email 的示例值。

案例使用

# 创建用户对象
user = User(id=1, password="secret", email="user@example.com")

# 打印用户对象
print(user)
# 排除字段
print(user.model_dump(exclude={'password'}))

  1. A-Z ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值