Python高级技术之:`FastAPI`的`Pydantic`:如何进行数据验证和类型强制。

各位观众老爷们,大家好! 今天咱们来聊聊 FastAPI 和 Pydantic 这对黄金搭档,看看它们是如何强强联合,在数据验证和类型强制方面搞事情的。

开场白:数据界的“照妖镜”和“整形医生”

在Web API 的世界里,数据就像是进城的农民工,质量参差不齐。你辛辛苦苦写了一个 API,结果前端传来的数据不是缺胳膊就是少腿,要么就是类型不对。这可咋办? 难道要我们自己手动写一堆 if-else 来验证? 那得写到猴年马月啊!

这时候,Pydantic 就闪亮登场了。它可以说是数据界的“照妖镜”和“整形医生”。 “照妖镜”是指它能帮你识别出数据里妖魔鬼怪,确保数据的结构和类型符合你的预期;“整形医生”是指它能帮你把数据转换成你想要的类型,让它们看起来更顺眼。 而 FastAPI 则把 Pydantic 集成得非常完美,让你用起来就像呼吸一样自然。

Pydantic 基础:定义数据模型

Pydantic 的核心是数据模型(Data Model)。 我们可以通过定义一个继承自 pydantic.BaseModel 的类来创建一个数据模型。这个类里的每个属性都代表着数据的一个字段,并且可以指定该字段的类型和验证规则。

举个栗子:

from pydantic import BaseModel, validator
from typing import Optional

class User(BaseModel):
    id: int
    name: str
    signup_ts: Optional[datetime] = None
    friends: List[int] = []

    @validator('name')
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v

在这个例子中,我们定义了一个 User 模型,它有 idnamesignup_tsfriends 四个字段。

  • id 是一个整数 (int),必填。
  • name 是一个字符串 (str),必填。
  • signup_ts 是一个可选的日期时间 (Optional[datetime]),可以为 None
  • friends 是一个整数列表 (List[int]),默认为空列表。

注意 @validator('name') 这个装饰器。它定义了一个自定义的验证器,用于检查 name 字段是否包含空格。如果 name 字段不包含空格,就会抛出一个 ValueError 异常。

Pydantic 的类型注解:让数据无所遁形

Pydantic 依赖于 Python 的类型注解(Type Hints)来确定数据字段的类型。 类型注解可以帮助 Pydantic 在运行时进行类型检查和转换。

常用的类型注解包括:

类型 描述 例子
int 整数 id: int
float 浮点数 price: float
str 字符串 name: str
bool 布尔值 is_active: bool
datetime 日期时间 created_at: datetime
date 日期 birthday: date
time 时间 start_time: time
list[T] 元素类型为 T 的列表 tags: List[str]
dict[K, V] 键类型为 K,值类型为 V 的字典 metadata: Dict[str, Any]
Optional[T] T 类型或 None description: Optional[str] = None
Union[T1, T2, ...] T1T2 等类型中的一个 status: Union[str, int]

FastAPI 与 Pydantic 的完美结合:API 的数据卫士

FastAPI 能够自动使用 Pydantic 模型来验证请求和响应的数据。 你只需要在 API 函数的参数或返回值中声明 Pydantic 模型, FastAPI 就会自动完成数据验证和类型转换。

from fastapi import FastAPI, HTTPException
from datetime import datetime
from typing import List, Optional

app = FastAPI()

@app.post("/users/")
async def create_user(user: User):
    # 在这里你可以安全地使用 user 对象,因为 FastAPI 已经验证过它的数据
    print(user.name)
    return user

在这个例子中, create_user 函数的参数 user 被声明为 User 类型。 当客户端发送一个 POST 请求到 /users/ 路径时, FastAPI 会自动使用 User 模型来验证请求体中的数据。

  • 如果数据验证成功, FastAPI 会将请求体中的数据转换成一个 User 对象,并传递给 create_user 函数。
  • 如果数据验证失败, FastAPI 会自动返回一个 HTTP 422 错误,其中包含详细的错误信息。

Pydantic 的验证器:自定义你的验证规则

Pydantic 提供了多种验证器,可以让你自定义数据验证的规则。

  • 字段验证器(Field Validators): 使用 @validator 装饰器,可以验证单个字段的值。
  • 模型验证器(Model Validators): 使用 @root_validator 装饰器,可以验证整个模型的值。
from pydantic import BaseModel, validator, root_validator

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

    @validator('price')
    def price_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError('price must be positive')
        return v

    @root_validator
    def tax_must_be_less_than_price(cls, values):
        price = values.get('price')
        tax = values.get('tax')
        if tax is not None and price is not None and tax >= price:
            raise ValueError('tax must be less than price')
        return values

在这个例子中,我们定义了两个验证器:

  • price_must_be_positive 是一个字段验证器,用于检查 price 字段的值是否为正数。
  • tax_must_be_less_than_price 是一个模型验证器,用于检查 tax 字段的值是否小于 price 字段的值。

Pydantic 的类型转换:让数据更听话

Pydantic 不仅可以验证数据,还可以将数据转换成你想要的类型。 例如,你可以将一个字符串转换成一个整数,或者将一个日期字符串转换成一个 datetime 对象。

from pydantic import BaseModel, validator
from datetime import datetime

class Event(BaseModel):
    start_time: datetime

    @validator('start_time', pre=True)
    def parse_start_time(cls, v):
        if isinstance(v, str):
            try:
                return datetime.fromisoformat(v)
            except ValueError:
                raise ValueError('invalid datetime format')
        return v

在这个例子中,我们定义了一个 parse_start_time 验证器,用于将 start_time 字段的值转换成一个 datetime 对象。 pre=True 表示这个验证器会在其他验证器之前运行。

Pydantic 的配置:定制你的数据模型

Pydantic 允许你通过 Config 类来配置数据模型的行为。 例如,你可以指定数据模型的别名、是否忽略大小写、是否允许额外的字段等等。

from pydantic import BaseModel, Field

class Product(BaseModel):
    product_id: int = Field(alias="productId")
    name: str
    price: float

    class Config:
        allow_population_by_field_name = True
        extra = 'forbid'

在这个例子中,我们配置了 Product 模型的以下行为:

  • allow_population_by_field_name = True 表示允许使用字段名来填充数据模型。 这意味着你可以使用 product_idproductId 来设置 product_id 字段的值。
  • extra = 'forbid' 表示禁止在数据模型中出现额外的字段。 如果客户端发送的请求体中包含额外的字段, Pydantic 会抛出一个错误。

高级用法:嵌套模型和泛型

Pydantic 还支持嵌套模型和泛型,让你能够处理更复杂的数据结构。

  • 嵌套模型: 你可以在一个数据模型中嵌套其他的 Pydantic 模型。
  • 泛型: 你可以使用泛型来定义可以处理不同类型的数据模型。
from pydantic import BaseModel
from typing import List, Generic, TypeVar

class Image(BaseModel):
    url: str
    name: str

class Item(BaseModel):
    name: str
    description: str
    price: float
    images: List[Image]

T = TypeVar('T')

class Response(BaseModel, Generic[T]):
    data: T
    message: str = "Success"

# 使用
item_data = {
    "name": "Awesome Product",
    "description": "A very cool product",
    "price": 99.99,
    "images": [
        {"url": "http://example.com/image1.jpg", "name": "Image 1"},
        {"url": "http://example.com/image2.jpg", "name": "Image 2"}
    ]
}

item = Item(**item_data)

response_data = Response[Item](data=item)

print(response_data.json(indent=4))

在这个例子中, Item 模型嵌套了 Image 模型, Response 模型使用了泛型来表示不同类型的数据。

总结:Pydantic + FastAPI = 数据安全感

总而言之, Pydantic 和 FastAPI 的结合,就像是给你的 API 穿上了一层坚不可摧的盔甲。 它们能够自动验证和转换数据,让你不再为数据质量而烦恼。 这样你就可以把更多的时间和精力放在业务逻辑上,而不是浪费在无聊的数据验证上。

记住, 好的 API 就像一辆好车,不仅要跑得快,还要足够安全可靠。 而 Pydantic 和 FastAPI 就是你打造安全可靠 API 的最佳搭档。

好了,今天的讲座就到这里。 感谢各位的收听! 咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注