当前位置: 首页 > 工具软件 > Fast Assert > 使用案例 >

FastAPI学习(1)- Pydantic

湛玄裳
2023-12-01

介绍

pydantic是python进行类型提示和数据验证的一个库,要学习fastapi必须先了解这个库,
额外会用到的工具

pip install pydantic
pip install pydantic[email,dotenv] # 邮箱和环境变量的验证的时候使用

可以提示那些type

除了知道的int,str,list,dict,float等之外,还可以提示
datetime,会将unix timestamp int (e.g. 1496498400) or a string 转为日期和时间.

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] = []

敏感信息保存类型

使用get_secret_value method方法看到密码,也可以只配置到json中,通过json查看

from pydantic import BaseModel, SecretStr, SecretBytes, ValidationError

class SimpleModel(BaseModel):
    password: SecretStr
    password_bytes: SecretBytes


sm = SimpleModel(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes')

# Standard access methods will not display the secret
print(sm)
#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm.password)
#> **********
print(sm.dict())
"""
{
    'password': SecretStr('**********'),
    'password_bytes': SecretBytes(b'**********'),
}
"""
print(sm.json())
#> {"password": "**********", "password_bytes": "**********"}

# Use get_secret_value method to see the secret's content.
print(sm.password.get_secret_value())
#> IAmSensitive
print(sm.password_bytes.get_secret_value())
#> b'IAmSensitiveBytes'

try:
    SimpleModel(password=[1, 2, 3], password_bytes=[1, 2, 3])
except ValidationError as e:
    print(e)
    """
    2 validation errors for SimpleModel
    password
      str type expected (type=type_error.str)
    password_bytes
      byte type expected (type=type_error.bytes)
    """


# If you want the secret to be dumped as plain-text using the json method,
# you can use json_encoders in the Config class.
class SimpleModelDumpable(BaseModel):
    password: SecretStr
    password_bytes: SecretBytes

    class Config:
        json_encoders = {
            SecretStr: lambda v: v.get_secret_value() if v else None,
            SecretBytes: lambda v: v.get_secret_value() if v else None,
        }


sm2 = SimpleModelDumpable(
    password='IAmSensitive', password_bytes=b'IAmSensitiveBytes'
)

# Standard access methods will not display the secret
print(sm2)
#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm2.password)
#> **********
print(sm2.dict())
"""
{
    'password': SecretStr('**********'),
    'password_bytes': SecretBytes(b'**********'),
}
"""

# But the json method will
print(sm2.json())
#> {"password": "IAmSensitive", "password_bytes": "IAmSensitiveBytes"}

pydantic model中如何嵌入python的class

开启arbitrary_types_allowed = True,虽然Pet 中的name是str类型,但是并不会进行验证

from pydantic import BaseModel, ValidationError


# This is not a pydantic model, it's an arbitrary class
class Pet:
    def __init__(self, name: str):
        self.name = name


class Model(BaseModel):
    pet: Pet
    owner: str

    class Config:
        arbitrary_types_allowed = True

Model 有那些模型和属性

  • __fields_set__
    该变量返回用户初始化对象时提供了什么字段
user = User(id='123')
print(user.__fields_set__ == {'id'}) #True
print(user.__fields_set__ == {'name'}) # False
  • dict() 会把套欠的模型全部转为dict,不是只能转外面的一层
在这里插入代码片
  • json()
  • copy()
  • parse_obj()
  • parse_raw()
  • parse_file()
  • from_orm()
  • schema()returns a dictionary representing the model as JSON Schema
  • schema_json()returns a JSON string representation of schema()
  • construct()a class method for creating models without running validation
  • __fields__ a dictionary of the model’s fields
  • __config__ the configuration class for the model

如何动态创建BaseModel

场景:接口接收的参数需要有个param字段,需要根据不通的接口传输不通的格式进行验证(这儿感觉表述不清楚)

方法一:通过继承实现

import pydantic

class User(pydantic.BaseModel):
    id: int
    name: str

class Student(User):
    semester: int

class Student_User(Student):
    building: str

方法二:通过函数实现动态创建

import pydantic

class User(pydantic.BaseModel):
    id: int
    name: str

class Student(User):
    semester: int

class Student_User(Student):
    building: str

print(Student_User.__fields__.keys())

model = pydantic.create_model("Student_User2", building=(str, ...), __base__=Student)

方法三,创建并验证

from pydantic import create_model, ValidationError, validator


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


validators = {
    'username_validator':
    validator('username')(username_alphanumeric)
}

UserModel = create_model(
    'UserModel',
    username=(str, ...),
    __validators__=validators
)

如何实现参数限定

限定jobType字段只能在给定的几个值中选择,使用@validator()

from pydantic import BaseModel, Field, validator
class ReceiveBase(BaseModel):
    jobId: int = Field(..., gt=0, description="任务标识")
    jobType: str = Field(..., description="任务类型"),
    targetClass: str = Field(..., description="表示分析目标类型"),
    targetId: int = Field(..., gt=0, description="目标标识"),

    @validator("jobType")
    def jobType_must_be_in_jobType_list(cls, jobType):
        jobType_list = ["predict", "packing", "label", "anomaly"]
        if jobType not in jobType_list:
            raise ValueError(f'must be in {jobType_list}')
        return jobType

如何让Model不可更改

from pydantic import BaseModel
class FooBarModel(BaseModel):
    a: str
    b: dict
    class Config:
        allow_mutation = False
foobar = FooBarModel(a='hello', b={'apple': 'pear'})

Basemodel 的套欠的解析

现在有这么一个问题,这三个BaseModel是互相套欠的

class Virt(BaseModel):
    timestamp: str 
    value: int 

class Params(BaseModel):
    virt_data: List[Virt]
    preLen: int 
    freq: str 
    
class PredictReceive(ReceiveBase):
    params: Params

接口是这样的

@prerouter.post('/getPredictInfo', response_model=PredictReturn)
def getPredictInfo(data: PredictReceive):
    perf_data = data.params
    jobType = data.jobType
    targetId = data.targetId
    virt = [i.dict() for i in perf_data.virt_data]

我们解析perf_data.virt_data
[Virt(timestamp='2021-01', value=20), Virt(timestamp='2021-02', value=30), Virt(timestamp='2021-03', value=15),]

list中都是Virt对象,这种我们是没办法用的,我用的最粗鲁的办法是
virt = [i.dict() for i in perf_data.virt_data]

但是这种办法不优美,有没有其他更好的办法呢?

对不起,没有想到怎么搞,但是部分场景中可以参考这篇文章

验证

参数多步骤

pre=True 为先做这个验证
each_item=True 对LIST(或者底朝天,set等)中的每个元素处理

from typing import List
from pydantic import BaseModel, ValidationError, validator


class DemoModel(BaseModel):
    square_numbers: List[int] = []
    cube_numbers: List[int] = []

    # '*' is the same as 'cube_numbers', 'square_numbers' here:
    @validator('*', pre=True)
    def split_str(cls, v):
        if isinstance(v, str):
            return v.split('|')
        return v

    @validator('cube_numbers', 'square_numbers')
    def check_sum(cls, v):
        if sum(v) > 42:
            raise ValueError('sum of numbers greater than 42')
        return v

    @validator('square_numbers', each_item=True)
    def check_squares(cls, v):
        assert v ** 0.5 % 1 == 0, f'{v} is not a square number'
        return v

    @validator('cube_numbers', each_item=True)
    def check_cubes(cls, v):
        # 64 ** (1 / 3) == 3.9999999999999996 (!)
        # this is not a good way of checking cubes
        assert v ** (1 / 3) % 1 == 0, f'{v} is not a cubed number'
        return v


print(DemoModel(square_numbers=[1, 4, 9]))
#> square_numbers=[1, 4, 9] cube_numbers=[]
print(DemoModel(square_numbers='1|4|16'))
#> square_numbers=[1, 4, 16] cube_numbers=[]
print(DemoModel(square_numbers=[16], cube_numbers=[8, 27]))
#> square_numbers=[16] cube_numbers=[8, 27]

always=True

对于动态的默认值参数可以开启总是验证

from datetime import datetime

from pydantic import BaseModel, validator


class DemoModel(BaseModel):
    ts: datetime = None

    @validator('ts', pre=True, always=True)
    def set_ts_now(cls, v):
        return v or datetime.now()


print(DemoModel())
#> ts=datetime.datetime(2022, 5, 19, 10, 49, 22, 520641)
print(DemoModel(ts='2017-11-08T14:00'))
#> ts=datetime.datetime(2017, 11, 8, 14, 0)

验证器复用allow_reuse=True

想在多个字段/模型上使用同一个验证器

from pydantic import BaseModel, validator


def normalize(name: str) -> str:
    return ' '.join((word.capitalize()) for word in name.split(' '))


class Producer(BaseModel):
    name: str

    # validators
    _normalize_name = validator('name', allow_reuse=True)(normalize)
 类似资料: