Pydantic 学习与使用
在 Fastapi 的 Web 开发中的数据验证通常都是在使用 Pydantic 来进行数据的校验,本文将对 Pydantic 的使用方法做记录与学习。
简介:Pydantic 是一个在 Python 中用于数据验证和解析的第三方库,它现在是 Python 使用最广泛的数据验证库。它利用声明式的方式定义数据模型和Python 类型提示的强大功能来执行数据验证和序列化,使您的代码更可靠、更可读、更简洁且更易于调试。它还可以从模型生成 JSON 架构,提供了自动生成文档等功能,从而轻松与其他工具集成。
参考文章:
作者:暴走的海鸽
链接:https://juejin.cn/post/7395080141130039305
1. Pydantic 快速上手
- # 安装
- pip install pydantic
- # 使用 email 验证的时候需要额外安装
- pip install pydantic[email]
复制代码 Pydantic 使用起来简单直观,需要最少的样板代码和配置。它适用于许多流行的 IDE 和静态分析工具,例如 PyCharm、VS Code、mypy 等。Pydantic 可以轻松与其他流行的 Python 库(如 Flask、Django、FastAPI 和 SQLAlchemy)集成,使其易于在现有项目中使用。
Pydantic 使用类型注解来定义模型的字段类型,以确保确保数据符合预期的类型和格式。你可以使用Python 内置的类型、自定义类型或者其他Pydantic 提供的验证类型。
Pydantic 提供了从各种数据格式(例如 JSON、字典)到模型实例的转换功能。它可以自动将输入数据解析成模型实例,并保留类型安全性和验证规则。
Pydantic 的核心验证逻辑是用 Rust 编写的,使其成为 Python 中最快的数据验证库之一。它还支持延迟验证和缓存,以提高效率。
1.1 数据验证
Pydantic 的核心验证逻辑是用 Rust 编写的,使其成为 Python 中最快的数据验证库之一。它还支持延迟验证和缓存,以提高效率。然后,您可以使用类型注释定义模型的属性,并选择性地提供默认值或验证器。- from enum import Enum
- from datetime import datetime
- from typing import Optional
- from pydantic import BaseModel, conint, EmailStr, constr
- class GenderEnum(str, Enum):
- """
- 性别类的枚举, 使用多继承创建;
- """
- male = "男"
- female = "女"
- class User(BaseModel):
- """
- 继承的基类;
- """
- id: int
- name: str
- age: conint(ge=0, le=100) # 使用 Pydantic 限制数字范围;
- gender: GenderEnum
- email: EmailStr # 使用 Pydantic 的邮件约束来限制约束;
- signup_ts: Optional[datetime] = None
- password: constr(min_length=8, max_length=16)
- if __name__ == '__main__':
- user_data = {
- "id": 1,
- "name": "傻狗",
- "age": 18,
- "gender": GenderEnum.male,
- "email": "123456788@qq.com",
- "signup_ts": datetime.now(),
- "password": "<PASSWORD>",
- }
- user = User.model_validate(user_data)
- print(user)
- print(f"User id: {user.id}, User name: {user.name}, User email: {user.email}")
- """
- >>> 输出:
- id=1 name='傻狗' age=18 gender=<GenderEnum.male: '男'> email='123456788@qq.com' signup_ts=datetime.datetime(2025, 5, 24, 14, 25, 6, 493690) password='<PASSWORD>'
- User id: 1, User name: 傻狗, User email: 123456788@qq.com
- """
复制代码 上述的代码就是针对字典中的数据完成数据的校验;
当我们的校验需求比较复杂的时候,我们就可以自己定义比较复杂的校验函数进行使用- from enum import Enum
- from datetime import datetime
- from typing import Optional, List
- from pydantic import BaseModel, conint, EmailStr, constr, field_validator
- class GenderEnum(str, Enum):
- """
- 性别类的枚举, 使用多继承创建;
- """
- male = "男"
- female = "女"
- def check_name(name: str) -> str:
- """
- :param name:
- :return:
- """
- if not name.startswith("小"): # 检查字符串是否以特定字符开头
- raise ValueError("must be startswith 小")
- return name
- class User(BaseModel):
- """
- 继承的基类;
- """
- id: int
- name: str
- age: conint(ge=0, le=100) # 使用 Pydantic 限制数字范围;
- # gender: GenderEnum
- email: EmailStr # 使用 Pydantic 的邮件约束来限制约束;
- signup_ts: Optional[datetime] = None
- # password: constr(min_length=8, max_length=16)
- friends: List[str] = []
- validate_fields = field_validator("name")(check_name)
- @field_validator("age")
- @classmethod # 装饰器的顺序不要放错, 否则可能不生效;
- def check_age(cls, age: int):
- if age < 18:
- raise ValueError("age must be >= 18")
- return age
- if __name__ == '__main__':
- user_data = {
- "id": 123,
- "name": "小卤蛋",
- "age": 28, # 小于18 就会报错;
- "email": "xiaoludan@example.com",
- 'signup_ts': '2024-07-19 00:22',
- 'friends': ["公众号:海哥python", '小天才', b''],
- }
- user = User(**user_data)
- print(user.age) # 取出模型中的数据
- print(user.model_dump()) # 字典的形式输出;
复制代码 特别注意:Pydantic 是强制的校验,不满足的时候程序会进行报错,注意和泛型进行区分;
上述的代码,我们是一个字段的进行的校验,那么我们能不能一次校验多个字段呢?- from datetime import datetime
- from typing import List, Optional
- from typing_extensions import Self # 如果python版本不低于3.11,则可以直接从typing中导入Self
- from pydantic import BaseModel, ValidationError, EmailStr, field_validator, model_validator
- def check_name(v: str) -> str:
- """Validator to be used throughout"""
- if not v.startswith("小"):
- raise ValueError("must be startswith 小")
- return v
- class User(BaseModel):
- id: int
- name: str = "小卤蛋"
- age: int
- email: EmailStr
- signup_ts: Optional[datetime] = None
- friends: List[str] = []
- validate_fields = field_validator("name")(check_name)
- @field_validator("age")
- @classmethod
- def check_age(cls, age):
- if age < 18:
- raise ValueError("用户年龄必须大于18岁")
- return age
- @model_validator(mode="after") # after 模式表示是所有字段完成验证之后再进行额外的验证;
- def check_age_and_name(self) -> Self:
- if self.age < 30 and self.name != "小卤蛋":
- raise ValueError("用户年龄必须小于30岁, 且名字必须为小卤蛋")
- return self
- if __name__ == '__main__':
- user_data = {
- "id": 123,
- "name": "小xi卤蛋",
- "age": 20,
- "email": "xiaoludan@example.com",
- 'signup_ts': '2024-07-19 00:22',
- 'friends': ["公众号:海哥python", '小天才', b''],
- }
- try:
- user = User(**user_data)
- print(user.model_dump())
- except ValidationError as e:
- print(f"Validation error: {e.json()}")
复制代码 上述的代码会同时对age和name字段进行验证,使用的是装饰器model_validator;
额外补充:上述的代码中用到了Enum枚举类,枚举类是一种特殊的类,用于定义一组固定的常量。在编程中,我们经常需要表示一些有限的、固定的状态或者选项。例如,一周的星期几(星期一、星期二……星期日),或者一个交通信号灯的状态(红灯、黄灯、绿灯)。使用枚举类可以清晰地表示这些固定的状态,避免使用容易出错的字符串或者整数来表示。
- 创建枚举类
- # Enum 是 Python 的标准库 enum 模块中的一个类,是创建枚举类的基础。
- from enum import Enum
- class Color(Enum):
- """ 枚举类;
- """
- RED = 1
- GREEN = 2
- BLUE = 3
复制代码 - 枚举类的基本属性和方法
- name 属性,表示枚举成员的名称
- print(Color.RED.name) # 输出 "RED"
复制代码 - value 属性:表示枚举成员的值。
- print(Color.RED.value) # 输出 1
复制代码 - __members__ 属性:返回一个包含枚举类中所有成员的字典,键是成员名称,值是成员本身。
- for name, member in Color.__members__.items():
- print(name, member)
- """
- RED Color.RED
- GREEN Color.GREEN
- BLUE Color.BLUE
- """
复制代码 - __iter__ 方法:可以对枚举类进行迭代,迭代的是枚举成员。
- for color in Color:
- print(color)
- """
- Color.RED
- Color.GREEN
- Color.BLUE
- """
复制代码
- 枚举类的应用场景
在开发软件时,很多对象都有状态。例如,一个订单的状态可以是“已下单”“已支付”“已发货”“已完成”等。使用枚举类来表示这些状态可以避免使用字符串或者整数来直接表示状态,减少错误。当需要从一组固定的选项中选择时,枚举类也很有用。比如在一个图形界面程序中,用户可以选择不同的字体样式,这些字体样式可以用枚举类来表示。使用枚举类可以使代码更清晰。例如,用枚举类表示星期几,相比直接使用数字或者字符串,代码更容易理解。
model_validator 是 Pydantic 中的一个装饰器,用于在模型实例创建后进行验证。它允许开发者在模型的所有字段都被解析和验证之后,执行额外的验证逻辑。这个装饰器可以用于确保模型的多个字段之间的关系或其他复杂的验证条件。- from typing import Annotated
- from pydantic import BaseModel, Field, validate_call
- class Person(BaseModel):
- name: str = Field(..., min_length=1, max_length=100)
- age: int = Field(..., gt=0, lt=20)
- # @validate_call
- def greet(person: Person, message: Annotated[str, Field(min_length=1, max_length=100)]):
- print(f"Hello, {person.name}! {message}")
- # 正确的调用
- greet(Person(name="公众号", age=18), "How are you?")
- greet(Person(name="公众号", age=18), 1) # 不使用装饰器会运行,但是与类型不符, 使用 @ 装饰器后会报错;
复制代码 然而,我们通常是希望定义和使用要符合我们的预期,以避免不可预见的错误。
此时validate_call装饰器就可以很好的为我们实现这一需求。
1.2 计算属性
核心的装饰器: @computed_field # 计算属性 @property 被装饰函数以值的形式返回;
- from datetime import datetime
- from typing import List, Optional
- from pydantic import BaseModel, ValidationError, EmailStr, computed_field
- class User(BaseModel):
- id: int
- name: str = "小卤蛋"
- age: int
- email: EmailStr
- signup_ts: Optional[datetime] = None
- friends: List[str] = []
- @computed_field # 计算属性
- @property
- def link(self) -> str:
- return f"尼古拉斯 · {self.name}"
- if __name__ == '__main__':
- user_data = {
- "id": 123,
- "name": "小卤蛋",
- "age": 20,
- "email": "xiaoludan@example.com",
- 'signup_ts': '2024-07-19 00:22',
- 'friends': ["公众号:海哥python", '小天才', b''],
- }
- #
- try:
- user = User(**user_data)
- print(f"{user.model_dump()} .... type: {type(user.model_dump())}")
- except ValidationError as e:
- print(f"Validation error: {e.json()}")
复制代码 当使用装饰link 属性会跟随着结果一起显示出来,
2. Pydantic 配置管理
- # 安装依赖
- pip install pydantic_settings
复制代码 2.1 直接设置
pydantic_settings可以很好的管理服务中的配置信息;使用的时候继承 BaseSettings 可以直接从环境变量中读取相对应的配置信息,也可以以手动填入的方式进行;- import os
- # 导入需要的配置;
- from pydantic import HttpUrl, Field
- from pydantic_settings import BaseSettings
- # 初始化环境变量
- os.environ['DATABASE_HOST'] = "http://baidu.com"
- os.environ['DATABASE_USER'] = "公众号:海哥python"
- os.environ['DATABASE_PASSWORD'] = "123456abcd"
- os.environ['API_KEY'] = "DHKSDsdh*(sdds"
- class AppConfig(BaseSettings):
- """
- 应用配置类, 用来管理应用程序的配置信息;
- """
- # 定义配置项
- # 定义 url 形式的配置;
- DATABASE_HOST: HttpUrl
- # 定义配置项database_user,类型为字符串,默认最小长度为5
- DATABASE_USER: str = Field(min_length=5)
- # 定义配置项database_password,类型为字符串,默认最小长度为10
- DATABASE_PASSWORD: str = Field(min_length=10)
- # 定义配置项api_key,类型为字符串,默认最小长度为8
- API_KEY: str = Field(min_length=8)
- # 打印配置类的实例化对象的模型信息,用于调试和确认配置的正确性
- print(AppConfig().model_dump())
复制代码
2.2 配置文件
在代码的同级目录下创建一个.env的配置文件;- DATABASE_HOST=http://baidu.com
- DATABASE_USER=公众号:海哥python
- DATABASE_PASSWORD=123456abcd
- API_KEY=DHKSDsdh*(sdds
复制代码 代码变化不大, 只有加载的部分发生一些变化;- # 导入需要的配置;
- from pydantic import HttpUrl, Field
- from pydantic_settings import BaseSettings, SettingsConfigDict
- class Settings(BaseSettings):
- """
- 定义模型的基本类;
- """
- # 定义配置模型的设置,包括.env文件位置、编码、大小写敏感性和额外参数策略
- model_config = SettingsConfigDict(
- env_file=".env",
- env_file_encoding="utf-8",
- case_sensitive=False,
- extra="forbid"
- )
- # 数据库主机的URL,必须是一个有效的HTTP或HTTPS URL
- database_host: HttpUrl
- # 数据库用户的名称,最小长度为5个字符
- database_user: str = Field(min_length=5)
- # 数据库用户的密码,最小长度为10个字符
- database_password: str = Field(min_length=10)
- # API的密钥,最小长度为8个字符
- api_key: str = Field(min_length=8)
- if __name__ == '__main__':
- # 打印配置类的实例化对象的模型信息,用于调试和确认配置的正确性
- print(Settings().model_dump())
复制代码 3. Pydantic 高级应用
3.1 数据嵌套
Pydantic 支持嵌套数的数据模型,方便管理复杂的数据结构。- from typing import List
- from pydantic import BaseModel, conint
- class Friend(BaseModel):
- name: str
- age: conint(gt=0, le=99)
- class User(BaseModel):
- name: str
- age: conint(gt=0, le=99)
- friends: List[Friend] # 设置字段嵌套 Friend 类.
- if __name__ == '__main__':
- # 创建并验证数据
- user_data = {
- 'name': '三体-章北海',
- 'age': 30,
- # 设置嵌套,
- 'friends': [{'name': '小卤蛋', 'age': 3}, {'name': '李元芳', 'age': 18}]
- }
- user = User(**user_data)
- print(user)
复制代码 3.2 Filed 的使用
Pydantic 的Field函数是一个强大的工具,它允许你在模型字段上设置额外的验证规则和默认值。Field 函数通常与模型字段一起使用,以提供更多的定制选项。
参数具体含义...表示该字段是必填项default用于定义字段的默认值default_factory用于定义字段的默认值函数alias字段定义别名validation_alias字段定义别名,只想将别名用于验证serialization_alias 字段定义别名,只想定义用于序列化的别名gt、lt、ge等约束数值,大于、小于、大于或等于等min_length、max_length等约束字符串min_items、max_items等元组、列表或集合约束validate_default 控制是否应验证字段的默认值,默认情况下,不验证字段的默认值。strict指定是否应在“严格模式”下验证字段frozen用于模拟冻结的数据类行为exclude用于控制导出模型时应从模型中排除哪些字段pattern对于字符串字段,您可以设置为 pattern 正则表达式以匹配该字段所需的任何模式。示例代码:- from datetime import datetime
- from typing import List, Optional
- from pydantic import BaseModel, Field, EmailStr, ValidationError, SecretStr
- class User(BaseModel):
- id: int = Field(..., alias="_id", frozen=True, strict=True)
- name: str = Field(default="小卤蛋", min_length=1, max_length=100) # 设置默认值,使用 min_length 和 max_length 来限制字符串长度
- age: int = Field(gt=0) # 支持各类条件验证,这里假设年龄必须大于0
- email: EmailStr
- signup_ts: Optional[datetime] = Field(default_factory=datetime.now, nullable=False, validate_default=True)
- friends: List[str] = Field(default=[], min_items=0)
- passwd: SecretStr = Field(min_length=6, max_length=20, exclude=True) # passwd不会被序列化
- if __name__ == '__main__':
- print(User.model_json_schema())
- user_data = {
- "_id": 123, # 使用别名 _id
- "name": "小卤蛋",
- "age": 20,
- "email": "xiaoludan@example.com",
- # 'signup_ts': '2024-07-19 00:22',
- 'friends': ["公众号:海哥python", '小天才', b''],
- "passwd": "123456"
- }
- try:
- user = User(**user_data)
- print(f"创建用户: {user}")
- print(f"转成字典形式: {user.model_dump()} .... type: {type(user.model_dump())}")
- print(f"转成json格式:{user.model_dump_json()} .... type: {type(user.model_dump_json())}")
- print(f"用户属性: User id: {user.id}, User name: {user.name}, User email: {user.email}")
- # user.id = 456 # 这里修改会报错
- except ValidationError as e:
- print(f"Validation error: {e.json()}")
复制代码
3.3 Config 配置
如果要对BaseModel中的某一基本型进行统一的格式要求,我们还可以使用Config类来实现。以下是一些 Config 类中常见的属性及其含义:
参数取值类型具体含义str_min_lengthintstr 类型的最小长度,默认值为Nonestr_max_lengthintstr 类型的最大长度。默认值为Noneextrastr在模型初始化期间是否忽略、允许或禁止额外的属性。默认值为 'ignore'。allow - 允许任何额外的属性。forbid - 禁止任何额外的属性。ignore - 忽略任何额外的属性。frozen bool模型是否可变str_to_upper bool是否将 str 类型的所有字符转换为大写。默认值为 False 。str_strip_whitespacebool是否去除 str 类型的前导和尾随空格。str_to_lowerbool是否将 str 类型的所有字符转换为小写。默认值为 False 。- #! -*-conding: UTF-8 -*-
- from pydantic import BaseModel
- class User(BaseModel):
- name: str
- age: int
- class Config:
- str_min_length = 10 # 字符串最小长度
- str_max_length = 20 # 字符串最大长度
- user = User(name="John Doe", age=30) # 此时字段不满足约束会报错;
复制代码 3.4 数据序列化
使用模型类.model_dump()方法可以将一个模型类型实例对象转换为字典类型的数据。
- #! -*-conding: UTF-8 -*-
- # @公众号: 海哥python
- from datetime import datetime
- from typing import List, Optional
- from pydantic import BaseModel, ValidationError, EmailStr, field_validator, field_serializer
- from enum import Enum
- class GenderEnum(str, Enum):
- """
- 性别枚举
- """
- male = "男"
- female = "女"
- class User(BaseModel):
- id: int
- name: str = "小卤蛋"
- age: int
- email: EmailStr
- signup_ts: Optional[datetime] = datetime.now()
- friends: List[str] = []
- sex: GenderEnum
- @field_validator("age") # 装饰器的顺序不能变化;
- @classmethod
- def check_age(cls, age):
- if age < 18:
- raise ValueError("用户年龄必须大于18岁")
- return age
- @field_serializer('signup_ts', when_used="always")
- def serialize_signup_ts(self, value: datetime) -> str:
- """
- 默认情况下, datetime 对象被序列化为 ISO 8601 字符串。这里使用field_serializer自定义序列化规则。
- """
- return value.strftime('%Y-%m-%d %H:%M:%S')
- @field_serializer('sex', when_used="always")
- def serialize_sex(self, value) -> str:
- return value.value
- if __name__ == '__main__':
- user_data = {
- "id": 123,
- "name": "小卤蛋",
- "age": 20,
- "email": "xiaoludan@example.com",
- # 'signup_ts': '2024-07-19 00:22',
- 'friends': ["公众号:海哥python", '小天才', b''],
- "sex": "男",
- }
- try:
- user = User.model_validate(user_data)
- print(f"{user.model_dump()} .... type: {type(user.model_dump())}")
- except ValidationError as e:
- print(f"Validation error: {e.json()}")
复制代码使用模型类的.model_dump_json()方法可以将一个模型的实例转换成为 JSON 字符串。
- from datetime import datetime
- from typing import List, Optional, Any
- from pydantic import BaseModel, ValidationError, EmailStr, field_validator, field_serializer, model_serializer
- class User(BaseModel):
- id: int
- name: str = "小卤蛋"
- age: int
- email: EmailStr
- signup_ts: Optional[datetime] = datetime.now()
- friends: List[str] = []
- @field_validator("age")
- @classmethod
- def check_age(cls, age):
- if age < 18:
- raise ValueError("用户年龄必须大于18岁")
- return age
- @field_serializer('signup_ts', when_used="json")
- def serialize_signup_ts(self, value: datetime) -> str:
- return value.strftime('%Y-%m-%d %H:%M:%S')
- @model_serializer(when_used="json")
- def serialize_model(self) -> dict[str, Any]:
- return {
- 'id': self.id,
- 'name': self.name,
- 'age': self.age + 1,
- 'email': self.email,
- 'signup_ts': self.serialize_signup_ts(self.signup_ts),
- 'friends': self.friends,
- }
- if __name__ == '__main__':
- user_data = {
- "id": 123,
- "name": "小卤蛋",
- "age": 20,
- "email": "xiaoludan@example.com",
- # 'signup_ts': '2024-07-19 00:22',
- 'friends': ["公众号:海哥python", '小天才', b''],
- }
- try:
- user = User.model_validate(user_data)
- print(f"{user.model_dump_json()} .... type: {type(user.model_dump_json())}")
- print(f"{user.model_dump()} .... type: {type(user.model_dump())}")
- except ValidationError as e:
- print(f"Validation error: {e.json()}")
复制代码 也可以使用model_serializer对整体模型的序列化做定制。结果如下:- """
- {"id":123,"name":"小卤蛋","age":21,"email":"xiaoludan@example.com","signup_ts":"2024-07-24 14:17:42","friends":["公众号:海哥python","小天才",""]} .... type: <class 'str'>
- {'id': 123, 'name': '小卤蛋', 'age': 20, 'email': 'xiaoludan@example.com', 'signup_ts': datetime.datetime(2024, 7, 24, 14, 17, 42, 45474), 'friends': ['公众号:海哥python', '小天才', '']} .... type: <class 'dict'>
- """
复制代码 3.5 文档生成
Pydantic 可以自动生成 API 文档。- #! -*-conding: UTF-8 -*-
- from datetime import datetime
- from typing import List, Optional
- from pydantic import BaseModel, EmailStr, field_validator
- class User(BaseModel):
- id: int
- name: str = "小卤蛋"
- age: int
- email: EmailStr
- signup_ts: Optional[datetime] = None
- friends: List[str] = []
- @field_validator("age")
- def check_age(cls, age):
- if age < 18:
- raise ValueError("用户年龄必须大于18岁")
- return age
- if __name__ == '__main__':
- print(User.model_json_schema())
复制代码 通过model_json_schema方法可以得到API文档。
4. Fastapi 中的 Pydantic
在 FastAPI 中,与普通的程序相同,可以通过定义 Pydantic 模型来指定请求参数的结构和类型。以下是一个简单的例子:- """ 定义参数模型;
- """
- from pydantic import BaseModel
- class Item(BaseModel):
- name: str
- description: str = None
- price: float
- tax: float = None
复制代码 在 Fastapi 中,你可以将 Pydantic 模型作为函数参数,Fastapi 会自动解析请求中的 json 数据,并将其转换成为 Pydantic 模型实例。如果数据不符合模型定义,FastApi 会自动返回一个 422 错误。- import uvicorn
- from fastapi import FastAPI, Request, Form
- from fastapi.params import Depends
- from pydantic import BaseModel
- from watchfiles import awatch
- app = FastAPI(
- title="测试小Demo",
- description="文档描述",
- version="1.0",
- )
- class QueryParam(BaseModel):
- q: str | None = None
- limit: int = 10
- offset: int = 0
- def get_query_params(params: QueryParam = Depends()):
- print(params)
- return params
- @app.get("/query", tags=["query"])
- async def query(params: QueryParam = Depends(get_query_params)):
- return {"params": params}
- @app.get("/filter", tags=["filter"])
- async def my_filter(params: QueryParam):
- print(params)
- return params
- if __name__ == '__main__':
- uvicorn.run(app, host="127.0.0.1", port=8000)
复制代码
上述返回的就是 422 错误表明,没有通过校验。
上述的函数并没有指定通过那种方式指定解析数据,如果不显式指定 Body 或 Query,FastAPI 会根据参数的位置和类型自动推断参数的来源:
- 如果参数是函数的路径参数(即在路径中定义的参数),则会从路径中提取。
- 如果参数是函数的普通参数(非路径参数),且没有显式指定 Body 或 Query,则会根据参数的类型来推断:
- 如果参数是复杂类型(如 Pydantic 模型),则会从请求体中提取。
- 如果参数是简单类型(如 str、int 等),则会从查询字符串中提取。
4.1 设置校验规则
- from pydantic import BaseModel, Field
- class Item(BaseModel):
- name: str = Field(..., min_length=3)
- description: str = Field(None, max_length=100)
- price: float = Field(..., gt=0, le=100)
- tax: float = Field(None, ge=0, le=100)
- """
- min_length=3:name 的长度至少为 3。
- max_length=100:description 的长度最多为 100。
- gt=0:price 必须大于 0。
- le=100:price 必须小于或等于 100。
- ge=0:tax 必须大于或等于 0。
- le=100:tax 必须小于或等于 100。
- """
复制代码 4.2 自定义校验方法
可以使用@validator装饰器来定义自定义校验方法。- from pydantic import BaseModel, validator
- class Item(BaseModel):
- name: str
- description: str = None
- price: float
- tax: float = None
- @validator("price")
- def check_price(cls, v):
- if v < 0:
- raise ValueError("price must be greater than 0")
- return v
复制代码 4.3 路径参数和查询参数
Pydantic 模型不仅可以用于请求体,还可以用于查询参数和路径参数。例如:- from fastapi import FastAPI, Query
- from pydantic import BaseModel
- app = FastAPI()
- class QueryParams(BaseModel):
- query: str = Query(..., min_length=3, max_length=50)
- @app.get("/items/")
- async def get_items(query_params: QueryParams):
- return {"query": query_params.query}
复制代码 在这个例子中,query_params 是一个 QueryParams 类型的 Pydantic 模型,它定义了一个查询参数 query。FastAPI 会自动解析查询参数,并将其转换为 QueryParams 实例。
4.4 响应模型
可以使用 Pydantic 模型来定义响应数据的结构。例如:- import uvicorn
- from fastapi import FastAPI, Request, Form
- from fastapi.params import Depends
- from pydantic import BaseModel
- from watchfiles import awatch
- app = FastAPI(
- title="测试小Demo",
- description="文档描述",
- version="1.0",
- )
- class Item(BaseModel):
- name: str
- description: str = None
- price: float
- tax: float = None
- @app.post("/items/", response_model=Item)
- async def create_item(item: Item):
- return item
- if __name__ == '__main__':
- uvicorn.run(app, host="127.0.0.1", port=8000)
复制代码
在这个例子中,response_model=Item 表示响应数据的结构将遵循 Item 模型的定义。FastAPI 会自动将返回的数据转换为 Item 实例,并确保其符合模型定义。
4.5 结合 Depends
Pydantic 和 Depends 可以结合使用,以实现更强大的参数校验和依赖注入功能。
- 模型校验
Depends 主要用于声明依赖,例如获取数据库连接、验证用户身份等。而 Pydantic 模型则用于数据验证和转换。你可以将 Pydantic 模型作为依赖函数的返回值,这样就可以在路由函数中直接使用经过校验的数据。- # !/usr/bin/env python
- # -*-coding:utf-8 -*-
- """
- # File : app.py
- # Time : 2025/5/27 15:49
- # Author : 紫青宝剑
- """
- import uvicorn
- from fastapi import FastAPI, Request, Form, HTTPException
- from fastapi.params import Depends
- from pydantic import BaseModel
- from watchfiles import awatch
- app = FastAPI(
- title="测试小Demo",
- description="文档描述",
- version="1.0",
- )
- class Item(BaseModel):
- name: str
- description: str = None
- price: float
- tax: float = None
- def val_item(item: Item):
- print("接收到了执行的逻辑....")
- if item.tax < 0.0:
- raise HTTPException(status_code=400, detail="Price must be greater than zero")
- else:
- return item
- @app.post("/items/", response_model=Item)
- async def create_item(item: Item = Depends(val_item)):
- return item
- if __name__ == '__main__':
- uvicorn.run(app, host="127.0.0.1", port=8000)
复制代码
此时,会直接在模型校验数据的类型之后执行视图函数之前,执行的校验步骤。
- 参数查询
- from fastapi import FastAPI, Depends, Query
- from pydantic import BaseModel
- app = FastAPI()
- class QueryParameters(BaseModel):
- q: str = Query(..., min_length=3, max_length=50)
- @app.get("/items/")
- async def read_items(query_params: QueryParameters = Depends()):
- return {"q": query_params.q}
复制代码 QueryParameters 是一个 Pydantic 模型,它定义了一个查询参数 q。通过将 query_params 作为依赖项,FastAPI 会自动解析查询参数并将其转换为 QueryParameters 实例。
- 前置校验
在路由函数中使用 Depends,创建一个前置依赖函数来执行复杂的验证逻辑。- from fastapi import FastAPI, Depends, HTTPException
- app = FastAPI()
- def complex_query_validator(query: str):
- if "forbidden" in query:
- raise HTTPException(status_code=400, detail="Forbidden query")
- return query
- @app.get("/items/")
- async def read_items(query: str = Depends(complex_query_validator)):
- return {"query": query}
复制代码 complex_query_validator 是一个依赖函数,它接收一个查询参数 query 并对其进行校验。如果校验失败,它会抛出一个 HTTPException。
- 用户认证
Depends 也可以用于用户认证和授权。你可以定义一个依赖函数来验证用户身份,并返回用户信息。然后在路由函数中使用这个依赖函数。- from fastapi import FastAPI, Depends, HTTPException, status
- from fastapi.security import HTTPBasic, HTTPBasicCredentials
- from pydantic import BaseModel
- app = FastAPI()
- security = HTTPBasic()
- class User(BaseModel):
- """ 定义用户模型的类。
- """
- username: str
- password: str
- def get_current_user(credentials: HTTPBasicCredentials = Depends(security)) -> User:
- """ 设置校验的函数;
- """
- valid_users = {"Alice": "alice_passwd"}
- if credentials.username not in valid_users or valid_users[credentials.username] != credentials.password:
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
- return User(username=credentials.username, password=credentials.password)
- @app.post("/users/")
- async def read_users(user: User = Depends(get_current_user)):
- return user
复制代码 在这个例子中,get_current_user 是一个依赖函数,它接收 HTTP 基本认证的凭据,并验证用户身份。如果验证成功,它会返回一个 User 实例。
- 总结
Pydantic 和 Depends 的结合使用可以让你在 FastAPI 中实现强大的参数校验和依赖注入功能。你可以通过定义 Pydantic 模型来校验请求数据,同时使用 Depends 来声明依赖项,从而实现复杂的业务逻辑和数据校验。这种组合不仅提高了代码的可维护性和可读性,还增强了 API 的安全性。
继续努力,终成大器!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |