Pydantic验证
# Pydantic介绍
# 简介
Pydantic 是一个基于Python类型提示的数据验证、解析和序列化库。它被广泛用于现代Python项目中,尤其在 FastAPI、Django Ninja等框架中作为核心组件,用于自动校验请求/响应数据。
# 特点
自动数据验证:确保输入数据符合预期类型和约束。
类型安全:利用 Python 的
typing模块,IDE 自动补全、静态检查。错误信息友好:返回清晰的验证错误(含字段路径)。
数据转换:自动将字符串转为
int、datetime等。序列化支持:轻松将模型转为JSON兼容字典。
高性能:底层用 Rust 编写(v2 起),速度极快。
# 转换
Pydantic在验证数据时,如果传入的数据是字符串,并且声明的数据类型为int、bool、date,则会自动尝试将字符串转换为声明的数据类型。
"1"→1需声明类型为
int"true"/"True"→True需声明类型为
bool"2023-01-01"→datetime.date(2023, 1, 1)需声明类型为
datetime.date
# 安装
# 安装pydantic库
pip install pydantic
# email验证、JSON Schema生成等额外功能需要指定安装
pip install pydantic[email, json]
2
3
4
5
# Pydantic使用
# BaseModel
# 介绍
Pydantic的核心概念是BaseModel模型,所有Pydantic模型都继承自pydantic.BaseModel。
# 使用
from typing import Literal, List
from pydantic import BaseModel
# 使用Pydantic数据验证只需要定义类型时继承BaseModel类
class User(BaseModel):
# 定义属性时需要声明类型提示
# 字符串类型
name: str
# 整数类型
age: int
# 逻辑或实现多选类型,此处可为整数、字符串、列表,列表中可为整数、字符串
phone: int | str | List[int | str]
# 枚举类型,只能是指定字面值
gender: Literal["male", "female", "Unknown"]
# 指定默认值,存在默认值的参数实例化时可以留空
is_active: bool = True
# 创建实例,创建时会自动进行数据类型验证
user = User(name="Hello", age=18, phone=[13344333223], gender="male")
# 访问属性
print(user.name)
# 通过字典导出类中的数据
print(user.model_dump())
# 验证失败示例,传入错误的数据类型
try:
User(name=233, age="十八", phone=[13344333223], gender="male")
except Exception as e:
print(e)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 字段约束
使用 pydantic.Field() 可以给数据添加额外的验证规则。
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(min_length=2, max_length=7)
age: int = Field(gt=0, le=300)
description: str = Field(default="", alias="info", max_length=2000)
# 示例
User(name="Hello", age=18, info="What?") # 验证通过
User(name="A", age=-1) # 验证失败
2
3
4
5
6
7
8
9
10
常用约束:
gt,ge,lt,le:数值范围。
min_length,max_length:字符串、列表的长度。
default:指定默认值。
pattern:正则匹配(如pattern=r'^Hello')。
alias:外部字段名,在前后端命名规范不一致时使用。使用后创建实例时需要使用别名,model_dump导出也会使用别名,但是内部属性访问时使用原名。
...:表示字段没有默认值,为必填配置。如果
default和...都不指定,则隐式默认值为None。
# 额外类型
# 网络与格式
from pydantic import (
AnyUrl, # 任意 URL(支持 mailto:, ftp:// 等)
HttpUrl, # 仅 http/https,最大长度 2083
HttpsUrl, # 仅 https
FileUrl, # file:// 协议
WebsocketUrl, # ws:// 或 wss://
AnyHttpUrl, # 同 HttpUrl(别名)
IPvAnyAddress, # IPv4 或 IPv6 地址
IPv4Address, # 仅 IPv4
IPv6Address, # 仅 IPv6
EmailStr, # 邮箱字符串(简单正则验证)
NameEmail, # 带名称的邮箱(如 "Alice <a@example.com>")
SecretStr, # 敏感字符串(repr 时隐藏)
SecretBytes, # 敏感字节(repr 时隐藏)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 日期时间
from pydantic import (
AwareDatetime, # 带时区信息的 datetime(拒绝 naive)
NaiveDatetime, # 不带时区的 datetime(拒绝 aware)
PastDatetime, # 必须是过去的时间
FutureDatetime, # 必须是未来的时间
)
2
3
4
5
6
# 文件与路径
# 标准库,但Pydantic自动支持
from pathlib import Path
# 验证路径是否存在(需自定义或使用 extra-types)
from pydantic import DirectoryPath, FilePath
2
3
4
# 敏感数据处理
from pydantic import SecretStr, SecretBytes
class Login(BaseModel):
password: SecretStr # 敏感数据类型
user = Login(password="123456")
print(user.password) # SecretStr('**********')
print(user.password.get_secret_value()) # "123456"
2
3
4
5
6
7
8
# JSON与动态数据
from pydantic import Json
class Payload(BaseModel):
data: Json[dict] # 要求是 JSON 字符串,且解析为dict
raw: Json # 解析为任意JSON(dict/list/str/int...)
2
3
4
5
# 扩展类型
需要额外安装:
pip install pydantic-extra-types
from pydantic_extra_types import (
Color, # CSS 颜色("red", "#ff0000", "rgb(255,0,0)")
PaymentCardNumber, # 信用卡号(带 Luhn 校验)
PhoneNumber, # 电话号码(需 phonenumbers 库)
CountryAlpha2, CountryAlpha3, # 国家代码(ISO 3166)
LanguageAlpha2, # 语言代码(ISO 639-1)
)
2
3
4
5
6
7
# 自定义验证器
# @field_validator
@field_validator是用于对单个字段进行自定义验证或转换的核心装饰器。
装饰器参数是字段名字符串,可以传入多个字段名,但只是对传入的字段分别进行验证。
验证函数必须是
@classmethod。接收一个参数
v(字段值)。必须返回最终值,可以是原值,也可以是转换后的值。
from pydantic import BaseModel, field_validator
class User(BaseModel):
username: str
@field_validator('username')
@classmethod
def check_username(cls, v):
if len(v) < 3:
raise ValueError('Username must be at least 3 characters')
return v
User(username="ab") # 验证失败
2
3
4
5
6
7
8
9
10
11
12
13
mode 参数,用于控制在哪个阶段验证:
| mode | 说明 |
|---|---|
"before" | 在 Pydantic 类型转换之前(原始输入值)。 |
"after" | 在类型转换之后(默认)。 |
"plain" | 完全跳过内部验证,只用你的函数(只在@field_validator可用)。 |
# @model_validator
@model_validator 是用于对整个模型实例进行验证或转换的装饰器。它适用于需要多字段逻辑验证或整体数据修正的场景。
mode="before"
在任何字段验证之前执行,需要接收接收的是原始输入字典,适合预处理输入数据。
函数必须是
@classmethod,必须返回数据字典。
class Point(BaseModel):
x: float
y: float
@model_validator(mode='before')
@classmethod
def parse_tuple(cls, data):
# 如果数据格式并非标准数据,则将传入的数据转换为所需的数据
if isinstance(data, (list, tuple)) and len(data) == 2:
return {"x": data[0], "y": data[1]}
return data
# 支持多种输入格式
Point(x=1, y=2) # 标准数据
Point([1, 2]) # 自动转换
Point((3.5, -1.2)) # 自动转换
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mode="after"
在所有字段验证和赋值完成后执行,可以安全访问所有字段,适合业务逻辑校验(常用)。
函数必须是接收
self实例的方法,必须返回模型实例,通常是self原实例。
from pydantic import BaseModel, model_validator
class Signup(BaseModel):
password: str
confirm_password: str
@model_validator(mode='after')
def check_passwords_match(self):
# 如果两次密码不一致则报错
if self.password != self.confirm_password:
raise ValueError('Passwords do not match')
return self
# 测试示例
Signup(password="123456", confirm_password="123456") # 验证通过
Signup(password="123", confirm_password="456") # 验证失败
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 序列化方法
user = User(id=1, name="Alice")
# 转为Dict字典
print(user.model_dump())
# {'id': 1, 'name': 'Alice', 'age': None, 'is_active': True}
# 转为JSON字符串
print(user.model_dump_json())
# {"id":1,"name":"Alice","age":null,"is_active":true}
# 排除None值
print(user.model_dump(exclude_none=True))
# {'id': 1, 'name': 'Alice', 'is_active': True}
2
3
4
5
6
7
8
9
10
11
12
13
# 模型配置
在BaseModel中可以通过 model_config 字段控制行为。
from pydantic import BaseModel, ConfigDict
# ==================================
# extra='forbid': 禁止额外字段,传入未定义字段会报错
class Model_ExtraForbid(BaseModel):
model_config = ConfigDict(extra='forbid')
name: str
Model_ExtraForbid(name="Alice", age=30) # 验证失败
# ==================================
# extra='allow': 允许额外字段,并可通过model_extra访问
class Model_ExtraAllow(BaseModel):
model_config = ConfigDict(extra='allow')
name: str
obj = Model_ExtraAllow(name="Bob", city="NYC")
# 输出: {'city': 'NYC'}
print(obj.model_extra)
# ==================================
# str_strip_whitespace=True: 自动去除字符串首尾空格
class Model_Strip(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
email: str
user = Model_Strip(email=" alice@example.com ")
# "alice@example.com"
print(user.email)
# ==================================
# 4. str_to_lower=True: 字符串自动转小写
class Model_Lower(BaseModel):
model_config = ConfigDict(str_to_lower=True)
username: str
user = Model_Lower(username="ALICE")
# "alice"
print(user.username)
# ==================================
# alias_generator: 自动生成别名(如user_name → userName)
def to_camel(s: str) -> str:
import re
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), s)
class Model_AliasGenerator(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True # 允许同时用user_id和userId
)
user_id: int
is_active: bool
# 用别名创建实例
obj = Model_AliasGenerator(userId=1, isActive=True)
# 同时也支持用内部名(populate_by_name=True)
obj = Model_AliasGenerator(user_id=1, is_active=True)
# ==================================
# validate_assignment=True: 赋值时验证
class Model_ValidateAssignment(BaseModel):
model_config = ConfigDict(validate_assignment=True)
age: int
user = Model_ValidateAssignment(age=25)
user.age = "三十" # 赋值时验证失败
# ==================================
# 10. frozen=True: 创建不可变模型,其属性值不可变
class Model_Frozen(BaseModel):
model_config = ConfigDict(frozen=True)
id: int
obj = Model_Frozen(id=1)
obj.id = 2 # 验证失败因为是不可变模型
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# BaseSettings
# 介绍
BaseSettings 是 Pydantic 用于管理应用程序配置的核心类。让我们的配置类中的字段,也能实现数据验证、数据转换等功能。
# BaseSettings需要额外安装
pip install pydantic-settings
# 如果需要使用.env文件加载配置,则需要额外安装dotenv
pip install python-dotenv
2
3
4
5
BaseSettings的配置加载优先级:显式传参 > 环境变量 > .env 文件 > 字段默认值
# 使用
.env 文件
ADMIN_EMAIL=admin@example.com
DATABASE_URL=postgresql://user:pass@localhost/db
DEBUG=true
2
3
.env文件中的配置会覆盖Settings类中的配置。
.env文件一般在本地开发时使用,方便管理开发配置。需要将.env加入.gitignore,以忽略上传到代码库。
settings.py 文件
from pydantic_settings import BaseSettings
from pydantic import Field, ConfigDict
# 配置类需要继承BaseSettings类
class Settings(BaseSettings):
# 在加载配置时,会自动将字段名转换为大写,作为默认的环境变量名
# 下面的例子同等于: app_name: str = Field(..., env="APP_NAME")
app_name: str
#Field中的env参数可以指定在环境变量或.env文件中读取的环境变量名
admin_email: str = Field(..., env="EMAIL")
database_url: str = "sqlite:///./test.db"
debug: bool = False
# 当model_config中设置了env_file时,Pydantic会调用dotenv库动态加载env配置
model_config = ConfigDict(
# 也可以用列表指定多个路径,会按顺序查找
env_file=".env", # type: ignore
env_file_encoding="utf-8" # type: ignore
)
# 在实例化配置时,也可以显示传参,优先级最高。
settings = Settings(app_name="Override")
if __name__ == "__main__":
print(settings.admin_email)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
如果Pycharm提示ConfigDict传入了意外实参,可以在代码行后面添加
# type: ignore。