Fastapi 1 Pydantic请求参数校验

Fastapi-1 Pydantic请求参数校验

Pydantic知识

"""
Data validation and settings management using python type annotations.
使用python的类型注解来进行数据校验和settings管理

pydantic enforces type hints at runtime, and provides user friendly errors when data is invalid.
pydantic可以在代码运行时提供类型提示,数据校验失败时提供友好的错误提示

Define how data should be in pure, canonical python; validate it with pydantic.
定义数据应该如何在纯规范的python代码中保存,并用Pydantic验证他
"""

from pydantic import BaseModel, ValidationError, constr
from datetime import datetime, date
from typing import List, Optional
from pathlib import Path
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base

print("\033[31m1. ---Pydantic的基本用法。Pycharm可以安装Pydantic插件 ---\033[0m")


class User(BaseModel):
    id: int  # 必填字段
    name: str = 'lem'  # 有默认值,选填字段
    singup_ts: Optional[datetime] = None  #
    friends: List[int] = []  # 列表中元素是int类型或者可以直接转换成int类型 "1"


external_data = {
    "id": "123",
    "singup_ts": "2022-12-22 12:22",
    "friends": [1, 2, "3"]  # "3" 是可以int("3")的
}

user = User(**external_data)
print(user.id, user.friends)
print(user.singup_ts)
print(repr(user.singup_ts))
print(user.dict())

print("\033[31m2. --- 校验失败处理 ---\033[0m")

try:
    User(id=1, singup_ts=datetime.today(), friends=[1, '2', 'not num'])
except ValidationError as e:
    print(e.json())

print("\033[31m3. --- 模型类的属性和方法 ---\033[0m")
print(user.dict())
print(user.json())
print(user.copy())  # 这里是浅拷贝
print(User.parse_obj(obj=external_data))
print(User.parse_raw('{"id": "123","singup_ts": "2022-12-22 12:22","friends": [1, 2, "3"]}'))

path = Path('pydantic_tutorial.json')
path.write_text('{"id": "123","singup_ts": "2022-12-22 12:22","friends": [1, 2, "3"]}')
print(User.parse_file(path))  # 解析文件

print(user.schema())
print(user.schema_json())

user_data = {"id": "error", "singup_ts": "2022-12-22 12:22", "friends": [1, 2, "3"]}  # id是字符串
print(User.construct(**user_data))  # 不校验数据直接创建模型类,不建议在construct方法中传入未经验证的数据

print(User.__fields__.keys())  # 定义模型类的时候,所有字段都注明类型,字段顺序就不会乱

print("\033[31m4. --- 递归模型 ---\033[0m")


class Sound(BaseModel):
    sound: str


class Dog(BaseModel):
    birthday: date
    weight: float = Optional[None]
    sound: List[Sound]  # 不同的狗有不同的叫声。递归模型(Recursive Models)就是只一个嵌套一个


dogs = Dog(birthday=date.today(), weight=6.66, sound=[{"sound": "wang wang ~"}, {"sound": "ying ying ~"}])

print(dogs.dict())

print("\033[31m5. --- ORM模型:从类实例创建符合ORM对象的模型 ---\033[0m")

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(64), unique=True)
    domains = Column(ARRAY(String(255)))


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

    class Config:
        orm_mode = True


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='lem',
    domains=['test.com, example.com']
)

print(CompanyMode.from_orm(co_orm))

print("\033[31m5. --- Pydantic支持的字段类型 ---\033[0m")  # 官方文档 https://pydantic-docs.helpmanual.io/usage/types/

启动程序

import uvicorn
from fastapi import FastAPI

from tutorial import app03, app04, app05, app06

app = FastAPI()

app.include_router(app03,prefix='/chapter03', tags=['第三章 请求参数和验证'])
app.include_router(app04,prefix='/chapter04', tags=['第四章 响应处理和FastAPI配置'])
app.include_router(app05,prefix='/chapter05', tags=['第五章 FastAPI的依赖注入系统'])


if __name__ == '__main__':
    uvicorn.run('run:app', host='0.0.0.0', port=8000, reload=True, debug=True, workers=1)

请求参数和验证

接口测试:http://127.0.0.1:8000/docs

from enum import Enum
from datetime import date
from typing import Optional, List
from fastapi import APIRouter, Path, Query, Cookie, Header
from pydantic import BaseModel, Field

app03 = APIRouter()

1.路径参数和数字验证

"""Path Parameters and Number Validations 路径参数和数字验证"""


@app03.get('/path/{parameters}')  # 函数的顺序就是路由的顺序 {parameters}是路径参数
def path_params01(parameters: str):
    return {"message": parameters}


@app03.get('/path/parameters')
def path_params01():
    return {"message": "This is a message"}


class CityName(str, Enum):
    Beijing = 'Beijing China'
    Shanghai = 'Shanghai China'


@app03.get('/enum/{city}')  # 枚举类型参数
async def latest(city: CityName):
    if city == CityName.Shanghai:
        return {"city_name": city, "confirmed": 1492, "death": 7}
    if city == CityName.Beijing:
        return {"city_name": city, "confirmed": 999, "death": 9}
    return {"city_name": city, "latest": "unknown"}


@app03.get('/files/{file_path:path}')  # 通过path parameters传递文件路径
def filepath(file_path: str):
    return f"The file path is {file_path}"


# 长度和正则表达式的验证
@app03.get('/path_/{num}')
def path_params_validate(
        num: int = Path(..., ge=1, le=100, title='Your number', description='不可描述')
):
    return num

2.查询参数和字符串验证

"""Path Parameters and String Validations 查询参数和字符串验证"""


@app03.get('/query')
def page_limit(page: int = 1, limit: Optional[int] = None):  # 给了默认值就是选填参数,没给默认值就是必填参数
    if limit:
        return {"page": page, "limit": limit}
    return {"page": page}


@app03.get('/query/bool/conversion')
def type_conversion(param: bool = False):
    return param


@app03.get('/query/validations')
def query_params_validate(
        value: str = Query(..., min_length=8, max_length=16, regex="^a"),  # Query查询参数
        values: List[str] = Query(default=['v1', 'v2'], alias='alias_name')
):  # 多个查询参数的列表。参数别名
    return value, values

3.请求体和字段

"""Request Body and Fields 请求体和字段"""


class CityInfo(BaseModel):
    name: str = Field(..., example="Beijing")  # Field ...代表必填字段,example是注解的作用,值不会被验证
    country: str
    country_code: str = None  # 给一个默认值
    country_population: int = Field(default=800, title="人口数量", description="国家的人口数量", ge=800)

    class Config:
        schema_extra = {
            "example": {
                "name": "Shanghai",
                "country": "China",
                "country_code": "CN",
                "country_population": 1400000000,
            }
        }


@app03.post('/request_body/city')
def city_info(city: CityInfo):
    print(city.name, city.country)
    return city.dict()

4.多参数混合

"""Request Body + Path parameters + Query parameters 多参数混合"""


@app03.put('/request_body/city/{name}')
def mix_city_info(
        name: str,
        city01: CityInfo,
        city02: CityInfo,  # Body可以定义时多个
        confirmed: int = Query(ge=0, description="确诊数", default=0),
        death: int = Query(ge=0, description="死亡数", default=0),
):
    if name == 'Shanghai':
        return {'Shanghai': {"confirmed": confirmed, "death": death}}
    return city01.dict(), city02.dict()

5.数据格式嵌套的请求体

"""Request Body - Nested Models 数据格式嵌套的请求体"""


class Data(BaseModel):
    city: List[CityInfo] = None  # 这里就是定义数据格式嵌套的请求体
    date: date  # 额外的数据类型,还有uuid,datetime,bytes,frozenset等,具体参考官方文档
    confirmed: int = Field(ge=0, description="确诊数", default=0)
    deaths: int = Field(ge=0, description="死亡数", default=0)
    recovered: int = Field(ge=0, description="痊愈数", default=0)


@app03.put('/request_body/nested')
def nested_models(data: Data):
    return data

6.Cookie 和 Header

"""Cookie 和 Header"""@app03.get('/cookie')  # 效果只能用Postman测试def cookie(cookie_id: Optional[str] = Cookie(None)):    return {'cookie_id': cookie_id}@app03.get('/header')def header(        user_agent: Optional[str] = Header(None, convert_underscores=True),        x_token: List[str] = Header(None),):    """    有些HTTP代理和服务器是不允许在请求头中有下划线的,所有Header提供convert_underscores=True的属性让_变为-    :param user_agent: convert_underscores=True 会把 user_agent 变成 user-agent    :param x_token: x_token是包含多个值的列表    :return:    """    return {"User-Agent": user_agent, "x_token": x_token}

7.总结

  • 在使用Pydantic定义请求体数据时,对数据进行校验使用Field类
  • 在使用路径参数时,对数据进行校验使用Path类
  • 在使用查询参数时,对数据进行校验使用Query类
  • 定义Cookie参数需要使用Cookie类
posted @ 2021-04-27 08:44  橘丶阳菜  阅读(2478)  评论(0编辑  收藏  举报