标签归档:pydantic

Pydantic — 强大的数据校验工具,比DRF快12倍

Pydantic 是一个使用Python类型注解进行数据验证和管理的模块。安装方法非常简单,打开终端输入:

pip install pydantic

它类似于 Django DRF 序列化器的数据校验功能,不同的是,Django里的序列化器的Field是有限制的,如果你想要使用自己的Field还需要继承并重写它的基类:

# Django 序列化器
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    author = models.CharField(max_length=32)
    publish = models.CharField(max_length=32)

而 Pydantic 基于Python3.7以上的类型注解特性,实现了可以对任何类做数据校验的功能:

# Pydantic 数据校验功能
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    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)
print(type(user.id))
#> 123
#> <class 'int'>
print(repr(user.signup_ts))
#> datetime.datetime(2019, 6, 1, 12, 22)
print(user.friends)
#> [1, 2, 3]
print(user.dict())
"""
{
    'id': 123,
    'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
    'friends': [1, 2, 3],
    'name': 'John Doe',
}
"""

从上面的基本使用可以看到,它甚至能自动帮你做数据类型的转换,比如代码中的 user.id, 在字典中是字符串,但经过Pydantic校验器后,它自动变成了int型,因为User类里的注解就是int型。

当我们的数据和定义的注解类型不一致时会报这样的Error:

from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel


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


external_data = {
    'id': '123',
    'signup_ts': '2019-06-01 12:222',
    'friends': [1, 2, '3'],
}
user = User(**external_data)
"""
Traceback (most recent call last):
  File "1.py", line 18, in <module>
    user = User(**external_data)
  File "pydantic\main.py", line 331, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for User
signup_ts
  invalid datetime format (type=value_error.datetime)
"""

即 “invalid datetime format”, 因为我传入的 signup_ts 不是标准的时间格式(多了个2)。

1.Pydantic 模型数据导出

通过Pydantic模型中自带的 json 属性方法,能让经过校验后的数据一行命令直接转成 json 字符串,如前文中的user对象:

print(user.dict())  # 转为字典
"""
{
    'id': 123,
    'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
    'friends': [1, 2, 3],
    'name': 'John Doe',
}
"""
print(user.json())  # 转为json
"""
{"id": 123, "signup_ts": "2019-06-01T12:22:00", "friends": [1, 2, 3], "name": "John Doe"}
"""

非常方便。它还支持将整个数据结构导出为 schema json,它能完整地描述整个对象的数据结构类型:

print(user.schema_json(indent=2))
"""
{
  "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": "John Doe",
      "type": "string"
    }
  },
  "required": [
    "id"
  ]
}
"""

2.数据导入

除了直接定义数据校验模型,它还能通过ORM、字符串、文件导入到数据校验模型:

比如字符串(raw):

from datetime import datetime
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime = None
      
m = User.parse_raw('{"id": 123, "name": "James"}')
print(m)
#> id=123 signup_ts=None name='James'

此外,它能直接将ORM的对象输入,转为Pydantic的对象,比如从Sqlalchemy ORM:

from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr

Base = declarative_base()


class CompanyOrm(Base):
    __tablename__ = 'companies'
    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
    id: int
    public_key: constr(max_length=20)
    name: constr(max_length=63)
    domains: List[constr(max_length=255)]

    class Config:
        orm_mode = True


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <models_orm_mode.CompanyOrm object at 0x7f0bdac44850>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']

从Json文件导入:

from datetime import datetime
from pathlib import Path
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime = None
      
path = Path('data.json')
path.write_text('{"id": 123, "name": "James"}')
m = User.parse_file(path)
print(m)

从pickle导入:

import pickle
from datetime import datetime
from pydantic import BaseModel

pickle_data = pickle.dumps({
    'id': 123,
    'name': 'James',
    'signup_ts': datetime(2017, 7, 14)
})
m = User.parse_raw(
    pickle_data, content_type='application/pickle', allow_pickle=True
)
print(m)
#> id=123 signup_ts=datetime.datetime(2017, 7, 14, 0, 0) name='James'

3.自定义数据校验

你还能给它增加 validator 装饰器,增加你需要的校验逻辑:

from pydantic import BaseModel, ValidationError, validator


class UserModel(BaseModel):
    name: str
    username: str
    password1: str
    password2: str

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

    @validator('password2')
    def passwords_match(cls, v, values, **kwargs):
        if 'password1' in values and v != values['password1']:
            raise ValueError('passwords do not match')
        return v

    @validator('username')
    def username_alphanumeric(cls, v):
        assert v.isalnum(), 'must be alphanumeric'
        return v

上面,我们增加了三种自定义校验逻辑:

1.name 必须带有空格

2.password2 必须和 password1 相同

3.username 必须为字母

让我们试试这三个校验是否成功实现:

user = UserModel(
    name='samuel colvin',
    username='scolvin',
    password1='zxcvbn',
    password2='zxcvbn',
)
print(user)
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'

try:
    UserModel(
        name='samuel',
        username='scolvin',
        password1='zxcvbn',
        password2='zxcvbn2',
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for UserModel
    name
      must contain a space (type=value_error)
    password2
      passwords do not match (type=value_error)
    """

可以看到,第一个UserModel里的数据完全没有问题,通过校验。

第二个UserModel里的数据,由于name存在空格,password2和password1不一致,无法通过校验。

4.性能表现

这是最令我惊讶的部分,Pydantic 比 Django-rest-framework 还快了12.3倍:

PackageVersionRelative PerformanceMean validation time
pydantic1.7.393.7μs
attrs + cattrs20.3.01.5x slower143.6μs
valideer0.4.21.9x slower175.9μs
marshmallow3.10.02.4x slower227.6μs
voluptuous0.12.12.7x slower257.5μs
trafaret2.1.03.2x slower296.7μs
schematics2.1.010.2x slower955.5μs
django-rest-framework3.12.212.3x slower1148.4μs
cerberus1.3.225.9x slower2427.6μs

而且他们的所有基准测试代码都是开源的,你可以在下面这个Github链接找到:

https://github.com/samuelcolvin/pydantic/tree/master/benchmarks

如果你的网络无法访问GitHub,请关注Python实用宝典公众号后台回复Pydantic获取。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Pydantic-使用Python类型提示进行数据解析和验证

使用Python类型提示进行数据验证和设置管理

快速且可扩展,虚伪的很好地玩你的短裤/IDE/大脑。定义数据在纯规范Python3.6+中的格式;使用以下命令进行验证虚伪的

帮助

看见documentation有关更多详细信息,请参阅

安装

使用以下方式安装pip install -U pydanticconda install pydantic -c conda-forge有关要进行的更多安装选项,请参阅虚伪的更快,请参阅Install部分,请参阅文档中的

一个简单的例子

from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel

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

external_data = {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']}
user = User(**external_data)
print(user)
#> User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
#> 123

贡献

有关设置开发环境以及如何为虚伪的,请参见Contributing to Pydantic

报告安全漏洞

请参阅我们的security policy

Fastapi-FastAPI框架,高性能,易学,编码速度快,可投入生产

FastAPI框架,高性能,易学,编码速度快,可投入生产


文档https://fastapi.tiangolo.com

源代码https://github.com/tiangolo/fastapi


FastAPI是一种现代、快速(高性能)的Web框架,用于使用Python 3.6+基于标准Python类型提示构建API

主要功能包括:

  • 快地:非常高的性能,可与节点JS(多亏了斯塔莱特和皮丹蒂克)One of the fastest Python frameworks available
  • 快速编码:提高功能开发速度约200%至300%。*
  • 更少的错误:减少约40%的人为(开发人员)引起的错误。*
  • 直观:强大的编辑支持。无处不在的完成度。调试时间更短
  • 简单易懂:设计成易于使用和学习。减少阅读文档的时间
  • 短的:最大限度地减少代码重复。来自每个参数声明的多个功能。更少的错误
  • 健壮:获取可投入生产的代码。使用自动交互文档
  • 基于标准的:基于(并完全兼容)API开放标准:OpenAPI(以前称为Swagger)和JSON Schema

*基于对内部开发团队、构建生产应用程序的测试进行估计

意见

[.]我在用FastAPI这几天有一吨多。[.]实际上我正计划把它用在我所有团队的微软的ML服务他们中的一些人正在融入核心窗口产品和一些办公室产品

卡比尔汗-微软(ref)

我们采用了FastAPI库以派生睡觉可以查询获取的服务器预测[路德维希]

皮耶罗·莫利诺,雅罗斯拉夫·杜丁和赛苏曼斯·米利亚拉-优步(Uber)(ref)

Netflix我很高兴地宣布我们的危机管理编排框架:派单好了![使用以下组件构建FastAPI]

凯文·格利森,马克·维拉诺瓦,福里斯特·蒙森-Netflix(ref)

我欣喜若狂FastAPI太好玩了!

布莱恩·奥肯-Python Bytes播客主持人(ref)

老实说,你建造的东西看起来非常坚固和精美。在很多方面,这是我想要的拥抱一下是-看到有人建造这样的建筑真的很鼓舞人心

蒂莫西·克罗斯利-Hug创建者(ref)

如果你想学一门现代框架要构建睡觉API,请查看FastAPI[.]它快速、易用、易学。

我们已经切换到FastAPI为了我们的API接口[.]我想你会喜欢的。

Ines Montani-Matthew Honnibal-Explosion AI创始人-spaCy创作者(ref)(ref)

要求

Python 3.6+

FastAPI站在巨人的肩膀上:

安装

$ pip install fastapi

---> 100%

您还需要一台ASGI服务器用于生产,例如UvicornHypercorn

$ pip install uvicorn[standard]

---> 100%

示例

创建它

  • 创建文件main.py使用:
from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}
或使用async def

如果您的代码使用async/await,使用async def

from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

注意事项

如果您不知道,请查看“赶时间?”部分关于async and await in the docs

运行它

使用以下命令运行服务器:

$ uvicorn main:app --reload

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28720]
INFO:     Started server process [28722]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
关于命令uvicorn main:app --reload

该命令uvicorn main:app指的是:

  • main:文件main.py(Python“模块”)
  • app:在中创建的对象main.py用这条线app = FastAPI()
  • --reload:使服务器在代码更改后重新启动。这样做只是为了发展。

检查一下

在以下位置打开您的浏览器http://127.0.0.1:8000/items/5?q=somequery

您将看到JSON响应为:

{"item_id": 5, "q": "somequery"}

您已经创建了一个API,该API:

  • 中接收HTTP请求。路径//items/{item_id}
  • 两者都有路径拿走GET运营(也称为HTTP方法:)
  • 这个路径/items/{item_id}有一个路径参数item_id这应该是一个int
  • 这个路径/items/{item_id}有一个可选的str查询参数q

交互式API文档

现在转到http://127.0.0.1:8000/docs

您将看到自动交互API文档(由提供Swagger UI):

替代API文档

现在,请转到http://127.0.0.1:8000/redoc

您将看到替代自动文档(由提供ReDoc):

示例升级

现在修改该文件main.py接收来自PUT请求

使用标准Python类型声明Body,这要归功于Pydatics

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float
    is_offer: Optional[bool] = None


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}


@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
    return {"item_name": item.name, "item_id": item_id}

服务器应自动重新加载(因为您添加了--reload发送到uvicorn上述命令)

Interactive API文档升级

现在转到http://127.0.0.1:8000/docs

  • 交互API文档将自动更新,包括新的Body:

  • 点击[试用]按钮,即可填写参数,直接与接口交互:

  • 然后点击“执行”按钮,用户界面将与您的API进行通信,发送参数,得到结果并显示在屏幕上:

备用API文档升级

现在,请转到http://127.0.0.1:8000/redoc

  • 替代文档还将反映新的查询参数和正文:

概述

总而言之,您声明一次作为函数参数的参数类型、正文等

您可以使用标准的现代Python类型来实现这一点

您不必学习新语法、特定库的方法或类等

只是标准的Python 3.6+

例如,对于int

item_id: int

或者对于更复杂的Item型号:

item: Item

有了这一份声明,你就会得到:

  • 编辑器支持,包括:
    • 完成
    • 类型检查
  • 数据验证:
    • 数据无效时自动清除错误
    • 即使是针对深度嵌套的JSON对象的验证也是如此
  • 输入数据的转换:从网络到Python数据和类型的转换。阅读自:
    • JSON
    • 路径参数
    • 查询参数
    • 曲奇饼
    • 标题
    • 表格
    • 文件
  • 输出数据转换:从Python数据和类型转换为网络数据(如JSON):
    • 转换Python类型(strintfloatboollist等)
    • datetime对象
    • UUID对象
    • 数据库模型
    • 还有更多
  • 自动交互式API文档,包括2个替代用户界面:
    • 大摇大摆的UI
    • 复单

回到前面的代码示例,FastAPI将:

  • 验证是否存在item_id在用于的路径中GETPUT请求
  • 验证item_id类型为intGETPUT请求
    • 如果不是,客户端将看到一个有用的、明确的错误
  • 检查是否存在名为的可选查询参数q(如图所示http://127.0.0.1:8000/items/foo?q=somequery)用于GET请求
    • 作为q参数是用= None,它是可选的
    • 如果没有None这将是必需的(就像在具有以下情况的情况下的身体一样PUT)
  • PUT请求/items/{item_id},将正文读作JSON:
    • 检查它是否具有必需的属性name这应该是一个str
    • 检查它是否具有必需的属性price那一定是一个float
    • 检查它是否具有可选属性is_offer,那应该是一个bool,如果存在
    • 所有这些也适用于深度嵌套的JSON对象
  • 自动从JSON转换为JSON或自动转换为JSON
  • 使用OpenAPI记录可由以下人员使用的所有内容:
    • 交互式文档系统
    • 自动客户端代码生成系统,适用于多种语言
  • 直接提供2个交互式文档web界面

我们只是触及了皮毛,但您已经对它的工作原理有了大致的了解

尝试使用以下命令更改行:

    return {"item_name": item.name, "item_id": item_id}

出发地:

        ... "item_name": item.name ...

收件人:

        ... "item_price": item.price ...

并查看您的编辑器将如何自动完成属性并了解其类型:

有关包含更多功能的更完整示例,请参阅Tutorial – User Guide

剧透警报:教程-用户指南包括:

  • 的声明参数从其他不同的地方,如:标题曲奇饼表单域文件
  • 如何设置验证约束作为maximum_lengthregex
  • 一款功能非常强大且易于使用的依赖项注入系统
  • 安全性和身份验证,包括支持OAuth2使用JWT代币HTTP Basic身份验证
  • 更高级(但同样简单)的声明技术深度嵌套的JSON模型(多亏了皮丹蒂克)
  • 许多额外功能(感谢Starlette),如:
    • WebSockets
    • 图形QL
    • 极其简单的测试,基于requestspytest
    • CORS
    • Cookie会话
    • 还有更多

性能

独立TechEmpower基准显示FastAPI在Uvicorn AS下运行的应用程序one of the fastest Python frameworks available,仅低于Starlette和Uvicorn本身(由FastAPI内部使用)。(*)

要了解更多信息,请参阅小节Benchmarks

可选依赖项

由Pydtic使用:

由Starlette使用:

  • requests-如果要使用TestClient
  • aiofiles-如果要使用,则为必填项FileResponseStaticFiles
  • jinja2-如果要使用默认模板配置,则为必填项
  • python-multipart-如果您想支持表单“解析”,则为必填项,带有request.form()
  • itsdangerous-需要用于SessionMiddleware支持
  • pyyaml-Starlette的必填项SchemaGenerator支持(FastAPI可能不需要)
  • graphene-需要用于GraphQLApp支持
  • ujson-如果要使用,则为必填项UJSONResponse

由FastAPI/Starlette使用:

  • uvicorn-对于加载和服务您的应用程序的服务器
  • orjson-如果要使用,则为必填项ORJSONResponse

您可以使用以下命令安装所有这些组件pip install fastapi[all]

许可证

这个项目是根据麻省理工学院的许可条款授权的。