简介

python类型注释的数据校验和设置管理 pydantic在运行时强制执行类型提示,并在数据无效时提供友好的错误 换句话说,pydantic保证输出模型的类型和约束,而不是输入数据 在pydantic中定义对象的主要方法是通过继承自BaseModel类的模型 官方文档:https://pydantic-docs.helpmanual.io/

QuickStart

  1. 安装

    1. pip install pydantic
  2. 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”

}

]

  1. <a name="LydxY"></a>
  2. ### 模型&模型属性
  3. 基本模型继承自类 BaseModel
  4. <a name="GsCif"></a>
  5. #### dict(): 返回模型字段和值的字典
  6. > - include: 包含在返回字典中的字段
  7. > - exclude:要从返回的字典中排除的字段
  8. > - by_alias: 是否应使用字段别名作为返回字典中的键
  9. > - exclude_unset: 创建模型时未明确设置的字段是否应从返回的字典中排除, 默认 False
  10. > - exclude_defaults:是否应从返回的字典中排除等于其默认值(无论是否设置)的字段,默认 False
  11. > - exclude_none: 是否None应该从返回的字典中排除等于的字段;默认 False
  12. ```python
  13. print("返回模型字段和值的字典", user.dict(include={"name"}, exclude_unset=True)) # {'name': 'zaygee'}
  14. print(user.dict(exclude_defaults=True))
  15. 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
  1. print("返回json字符串表示dict():", user.json(indent=4), type(user.json()))

copy(): 浅复制模型副本

  • 典中的字段
  • exclude:要从返回的字典中排除的字段
  • update:创建复制模型时要更改的值字典
  • deep: 是否对新模型进行深拷贝;默认False
  1. print("浅复制模型副本:", user.copy(include={"name", "id"})) # id=123 name='zaygee1'
  2. 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'
  3. print("排除字段exclude:", user.copy(exclude={"name", "signup_ts"})) # id=123 friends=[1, 2, 3]
  4. print("deep深复制:", user.copy(deep=True))

parse_obj(): 如果对象不是字典,则用于将任何对象加载到模型中并进行错误处理的实用程序

  1. 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'
  2. # print(user.parse_obj('{"id": 123,"signup_ts":1234567890,"name":"John Doe"}') ) # validation error for User

parse_raw(): 用于加载多种格式的字符串的实用程序

  1. print(user.parse_raw('{"id": 123,"signup_ts":1234567890,"name":"John Doe"}'))

parse_file(): 就像parse_raw()但对于文件路径

schema(): 返回将模型表示为 JSON 模式的字典

  1. print("-"*50)
  2. print(user.schema())
  3. print("-"*50)
  4. # {'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()

  1. print("-"*50)
  2. print(user.schema_json())
  3. print("-"*50)
  4. # --------------------------------------------------
  5. # {"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"]}
  6. # --------------------------------------------------

fields_set: 初始化模型实例时设置的字段名称集

  1. print(user.__fields_set__)
  2. # {'name', 'signup_ts', 'friends', 'id'}

fields: 模型字段的字典

  1. print(user.__fields__)
  2. # {'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[]

  1. from pydantic import Field
  2. from typing import Optional
  3. # a、b、c都是必选的,但是c可填None
  4. class Model(BaseModel):
  5. a: int
  6. c: int = Field(...)
  7. d: Optional[int] = Field(...)
  8. print(Model(a=0, c=2, d=None))

parse_obj_as将字段解析为指定类型

  1. from typing import List
  2. from pydantic import parse_obj_as
  3. class Item(BaseModel):
  4. id: int
  5. name: str
  6. item_data = [{'id': 1, 'name': 'My Item'}]
  7. items = parse_obj_as(List[Item], item_data)
  8. print(items, type(items))
  9. for index, key in items:
  10. print(index, key)
  11. print(index[-1], key[-1])
  12. # [Item(id=1, name='My Item')] <class 'list'>
  13. # ('id', 1) ('name', 'My Item')
  14. # 1 My Item

字段类型约束

  1. from decimal import Decimal
  2. from pydantic import (
  3. BaseModel,
  4. NegativeFloat,
  5. NegativeInt,
  6. PositiveFloat,
  7. PositiveInt,
  8. NonNegativeFloat,
  9. NonNegativeInt,
  10. NonPositiveFloat,
  11. NonPositiveInt,
  12. conbytes,
  13. condecimal,
  14. confloat,
  15. conint,
  16. conlist,
  17. conset,
  18. constr,
  19. Field,
  20. ByteSize,
  21. HttpUrl,
  22. SecretStr,
  23. FilePath,
  24. Json
  25. )
  26. class AllModel(BaseModel):
  27. file_path: FilePath # 文件校验
  28. url: Optional[HttpUrl] # 网站校验
  29. password: SecretStr # 秘文
  30. lower_str: constr(to_lower=True, min_length=1, max_length=10) # con*的约束类型
  31. size: ByteSize # 字节大小
  32. class ComplexJsonModel(BaseModel):
  33. json_obj: Json[List[int]]
  34. print(ComplexJsonModel(json_obj='[1, 2, 3]'))
  35. print(AllModel(
  36. file_path="Script/python_test/base/test_pydantic.py",
  37. url="https://www.baidu.com",
  38. password="testpw",
  39. lower_str="SSS",
  40. size=520
  41. ))
  42. #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

始终验证

重用验证器

数据类验证器

配置文件加载

  1. from typing import Set
  2. from pydantic import (
  3. BaseModel,
  4. BaseSettings,
  5. PyObject,
  6. RedisDsn,
  7. PostgresDsn,
  8. Field,
  9. )
  10. class SubModel(BaseModel):
  11. foo = 'bar'
  12. apple = 1
  13. class Settings(BaseSettings):
  14. auth_key: str
  15. api_key: str = Field(..., env='my_api_key')
  16. redis_dsn: RedisDsn = 'redis://user:pass@localhost:6379/1'
  17. pg_dsn: PostgresDsn = 'postgres://user:pass@localhost:5432/foobar'
  18. special_function: PyObject = 'math.cos'
  19. # to override domains:
  20. # export my_prefix_domains='["foo.com", "bar.com"]'
  21. domains: Set[str] = set()
  22. # to override more_settings:
  23. # export my_prefix_more_settings='{"foo": "x", "apple": 1}'
  24. more_settings: SubModel = SubModel()
  25. class Config:
  26. env_prefix = 'my_prefix_' # defaults to no prefix, i.e. ""
  27. fields = {
  28. 'auth_key': {
  29. 'env': 'my_auth_key',
  30. },
  31. 'redis_dsn': {
  32. 'env': ['service_redis_dsn', 'redis_url']
  33. }
  34. }
  35. print(Settings(auth_key="xxx", api_key="xxx").dict())
  36. #{'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

  1. # 安装dotenv
  2. pip install python-dotenv
  3. # 新建配置文件
  4. touch .env
  5. ---------------------
  6. # .env 文件
  7. # ignore comment
  8. ENVIRONMENT="production"
  9. REDIS_ADDRESS=localhost:6379
  10. APP_NAME='Hello world'
  11. ---------------------
  12. # 结合Dotenv文件设置变量
  13. from pydantic import BaseSettings
  14. class Settings(BaseSettings):
  15. app_name: str = "Awesome API"
  16. environment: str
  17. items_per_user: int = 50
  18. class Config:
  19. env_file = '.env'
  20. env_file_encoding = "utf-8"
  21. settings = Settings(_env_file='/Users/zaygee/work_script/.env', _env_file_encoding='utf-8')
  22. print(settings) # app_name='Hello world' environment='production' redis_address='localhost:6379'