简介
python类型注释的数据校验和设置管理 pydantic在运行时强制执行类型提示,并在数据无效时提供友好的错误 换句话说,pydantic保证输出模型的类型和约束,而不是输入数据 在pydantic中定义对象的主要方法是通过继承自BaseModel类的模型 官方文档:https://pydantic-docs.helpmanual.io/
QuickStart
安装
pip install pydantic
case ```python from datetime import datetime from typing import List, Optional from pydantic import BaseModel, ValidationError
class User(BaseModel): id: int name = “zaygee” signup_ts: Optional[datetime] = None friends: List[int] = []
external_data = { ‘id’: ‘123’, ‘signup_ts’: ‘2019-06-01 12:22’, ‘friends’: [1, 2, ‘3’], }
字典解包
user = User(**external_data) print(user.id) # 123
try: User(signup_ts = “broken”, friends=[1, 2, “not number”]) except ValidationError as e: print(e.json())
[
{
“loc”: [
“id”
],
“msg”: “field required”,
“type”: “value_error.missing”
},
{
“loc”: [
“signup_ts”
],
“msg”: “invalid datetime format”,
“type”: “value_error.datetime”
},
{
“loc”: [
“friends”,
2
],
“msg”: “value is not a valid integer”,
“type”: “type_error.integer”
}
]
<a name="LydxY"></a>
### 模型&模型属性
基本模型继承自类 BaseModel
<a name="GsCif"></a>
#### dict(): 返回模型字段和值的字典
> - include: 包含在返回字典中的字段
> - exclude:要从返回的字典中排除的字段
> - by_alias: 是否应使用字段别名作为返回字典中的键
> - exclude_unset: 创建模型时未明确设置的字段是否应从返回的字典中排除, 默认 False
> - exclude_defaults:是否应从返回的字典中排除等于其默认值(无论是否设置)的字段,默认 False
> - exclude_none: 是否None应该从返回的字典中排除等于的字段;默认 False
```python
print("返回模型字段和值的字典", user.dict(include={"name"}, exclude_unset=True)) # {'name': 'zaygee'}
print(user.dict(exclude_defaults=True))
print(user.dict(exclude_defaults=False))
json(): 会将模型序列化为 JSON
- include: 包含在返回字典中的字段
- exclude:要从返回的字典中排除的字段
- by_alias: 是否应使用字段别名作为返回字典中的键
- exclude_unset: 创建模型时未明确设置的字段是否应从返回的字典中排除, 默认 False
- exclude_defaults:是否应从返回的字典中排除等于其默认值(无论是否设置)的字段,默认 False
- exclude_none: 是否None应该从返回的字典中排除等于的字段;默认 False
- encoder:传递给default参数的自定义编码器函数json.dumps();默认为自定义编码器,旨在处理所有常见类型
- dumps_kwargs: 任何其他关键字参数都传递给json.dumps(),例如indent
print("返回json字符串表示dict():", user.json(indent=4), type(user.json()))
copy(): 浅复制模型副本
- 典中的字段
- exclude:要从返回的字典中排除的字段
- update:创建复制模型时要更改的值字典
- deep: 是否对新模型进行深拷贝;默认False
print("浅复制模型副本:", user.copy(include={"name", "id"})) # id=123 name='zaygee1'
print(user.copy(update={"name": "update_name"})) # id=123 signup_ts=datetime.datetime(2019, 6, 1, 12, 22) friends=[1, 2, 3] name='update_name'
print("排除字段exclude:", user.copy(exclude={"name", "signup_ts"})) # id=123 friends=[1, 2, 3]
print("deep深复制:", user.copy(deep=True))
parse_obj(): 如果对象不是字典,则用于将任何对象加载到模型中并进行错误处理的实用程序
print(user.parse_obj({"id": 123,"signup_ts":1234567890,"name":"John Doe"}) ) # id=123 signup_ts=datetime.datetime(2009, 2, 13, 23, 31, 30, tzinfo=datetime.timezone.utc) friends=[] name='John Doe'
# print(user.parse_obj('{"id": 123,"signup_ts":1234567890,"name":"John Doe"}') ) # validation error for User
parse_raw(): 用于加载多种格式的字符串的实用程序
print(user.parse_raw('{"id": 123,"signup_ts":1234567890,"name":"John Doe"}'))
parse_file(): 就像parse_raw()但对于文件路径
schema(): 返回将模型表示为 JSON 模式的字典
print("-"*50)
print(user.schema())
print("-"*50)
# {'title': 'User', 'type': 'object', 'properties': {'id': {'title': 'Id', 'type': 'integer'}, 'signup_ts': {'title': 'Signup Ts', 'type': 'string', 'format': 'date-time'}, 'friends': {'title': 'Friends', 'default': [], 'type': 'array', 'items': {'type': 'integer'}}, 'name': {'title': 'Name', 'default': 'zaygee', 'type': 'string'}}, 'required': ['id']}
schema_json(): 返回 JSON 字符串表示schema()
print("-"*50)
print(user.schema_json())
print("-"*50)
# --------------------------------------------------
# {"title": "User", "type": "object", "properties": {"id": {"title": "Id", "type": "integer"}, "signup_ts": {"title": "Signup Ts", "type": "string", "format": "date-time"}, "friends": {"title": "Friends", "default": [], "type": "array", "items": {"type": "integer"}}, "name": {"title": "Name", "default": "zaygee", "type": "string"}}, "required": ["id"]}
# --------------------------------------------------
fields_set: 初始化模型实例时设置的字段名称集
print(user.__fields_set__)
# {'name', 'signup_ts', 'friends', 'id'}
fields: 模型字段的字典
print(user.__fields__)
# {'id': ModelField(name='id', type=int, required=True), 'signup_ts': ModelField(name='signup_ts', type=Optional[datetime], required=False, default=None), 'friends': ModelField(name='friends', type=List[int], required=False, default=[]), 'name': ModelField(name='name', type=str, required=False, default='zaygee')}
错误处理
字段类型校验
必填字段Field(…)&可选字段Optional[]
from pydantic import Field
from typing import Optional
# a、b、c都是必选的,但是c可填None
class Model(BaseModel):
a: int
c: int = Field(...)
d: Optional[int] = Field(...)
print(Model(a=0, c=2, d=None))
parse_obj_as将字段解析为指定类型
from typing import List
from pydantic import parse_obj_as
class Item(BaseModel):
id: int
name: str
item_data = [{'id': 1, 'name': 'My Item'}]
items = parse_obj_as(List[Item], item_data)
print(items, type(items))
for index, key in items:
print(index, key)
print(index[-1], key[-1])
# [Item(id=1, name='My Item')] <class 'list'>
# ('id', 1) ('name', 'My Item')
# 1 My Item
字段类型约束
from decimal import Decimal
from pydantic import (
BaseModel,
NegativeFloat,
NegativeInt,
PositiveFloat,
PositiveInt,
NonNegativeFloat,
NonNegativeInt,
NonPositiveFloat,
NonPositiveInt,
conbytes,
condecimal,
confloat,
conint,
conlist,
conset,
constr,
Field,
ByteSize,
HttpUrl,
SecretStr,
FilePath,
Json
)
class AllModel(BaseModel):
file_path: FilePath # 文件校验
url: Optional[HttpUrl] # 网站校验
password: SecretStr # 秘文
lower_str: constr(to_lower=True, min_length=1, max_length=10) # con*的约束类型
size: ByteSize # 字节大小
class ComplexJsonModel(BaseModel):
json_obj: Json[List[int]]
print(ComplexJsonModel(json_obj='[1, 2, 3]'))
print(AllModel(
file_path="Script/python_test/base/test_pydantic.py",
url="https://www.baidu.com",
password="testpw",
lower_str="SSS",
size=520
))
#file_path=PosixPath('Script/python_test/base/test_pydantic.py') url=HttpUrl('https://www.baidu.com', scheme='https', host='www.baidu.com', tld='com', host_type='domain') password=SecretStr('**********') lower_str='sss' size=520
验证器-todo
pre & per-item验证器
验证器 & each_item
始终验证
重用验证器
数据类验证器
配置文件加载
from typing import Set
from pydantic import (
BaseModel,
BaseSettings,
PyObject,
RedisDsn,
PostgresDsn,
Field,
)
class SubModel(BaseModel):
foo = 'bar'
apple = 1
class Settings(BaseSettings):
auth_key: str
api_key: str = Field(..., env='my_api_key')
redis_dsn: RedisDsn = 'redis://user:pass@localhost:6379/1'
pg_dsn: PostgresDsn = 'postgres://user:pass@localhost:5432/foobar'
special_function: PyObject = 'math.cos'
# to override domains:
# export my_prefix_domains='["foo.com", "bar.com"]'
domains: Set[str] = set()
# to override more_settings:
# export my_prefix_more_settings='{"foo": "x", "apple": 1}'
more_settings: SubModel = SubModel()
class Config:
env_prefix = 'my_prefix_' # defaults to no prefix, i.e. ""
fields = {
'auth_key': {
'env': 'my_auth_key',
},
'redis_dsn': {
'env': ['service_redis_dsn', 'redis_url']
}
}
print(Settings(auth_key="xxx", api_key="xxx").dict())
#{'auth_key': 'xxx', 'api_key': 'xxx', 'redis_dsn': RedisDsn('redis://user:pass@localhost:6379/1', scheme='redis', user='user', password='pass', host='localhost', host_type='int_domain', port='6379', path='/1'), 'pg_dsn': PostgresDsn('postgres://user:pass@localhost:5432/foobar', scheme='postgres', user='user', password='pass', host='localhost', host_type='int_domain', port='5432', path='/foobar'), 'special_function': <built-in function cos>, 'domains': set(), 'more_settings': {'foo': 'bar', 'apple': 1}}
结合dotenv加载配置文件
安装dotenv
# 安装dotenv
pip install python-dotenv
# 新建配置文件
touch .env
---------------------
# .env 文件
# ignore comment
ENVIRONMENT="production"
REDIS_ADDRESS=localhost:6379
APP_NAME='Hello world'
---------------------
# 结合Dotenv文件设置变量
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
environment: str
items_per_user: int = 50
class Config:
env_file = '.env'
env_file_encoding = "utf-8"
settings = Settings(_env_file='/Users/zaygee/work_script/.env', _env_file_encoding='utf-8')
print(settings) # app_name='Hello world' environment='production' redis_address='localhost:6379'