如何更改 pydantic 中的日期格式

jon*_*box 29 python python-3.x pydantic

如何更改 pydantic 中的日期格式以进行验证和序列化?为了验证我正在使用@validator. 这两种情况都有解决办法吗?

小智 28

您可以使用pydantic 的自定义 json 编码器来实现自定义 json序列化器。然后,与pydantic 的自定义验证器一起,您就可以拥有这两种功能。


from datetime import datetime, timezone
from pydantic import BaseModel, validator


def convert_datetime_to_iso_8601_with_z_suffix(dt: datetime) -> str:
    return dt.strftime('%Y-%m-%dT%H:%M:%SZ')


def transform_to_utc_datetime(dt: datetime) -> datetime:
    return dt.astimezone(tz=timezone.utc)


class DateTimeSpecial(BaseModel):
    datetime_in_utc_with_z_suffix: datetime

    # custom input conversion for that field
    _normalize_datetimes = validator(
        "datetime_in_utc_with_z_suffix",
        allow_reuse=True)(transform_to_utc_datetime)

    class Config:
        json_encoders = {
            # custom output conversion for datetime
            datetime: convert_datetime_to_iso_8601_with_z_suffix
        }


if __name__ == "__main__":
    special_datetime = DateTimeSpecial(datetime_in_utc_with_z_suffix="2042-3-15T12:45+01:00")  # note the different timezone

    # input conversion
    print(special_datetime.datetime_in_utc_with_z_suffix)  # 2042-03-15 11:45:00+00:00

    # output conversion
    print(special_datetime.json())  # {"datetime_in_utc_with_z_suffix": "2042-03-15T11:45:00Z"}

Run Code Online (Sandbox Code Playgroud)

这个变体也适用于 fastapi 的序列化器,我实际上就是以这种方式使用它的。


Ome*_*ham 19

我认为预验证器可以在这里提供帮助。

from datetime import datetime, date

from pydantic import BaseModel, validator


class OddDate(BaseModel):
    birthdate: date

    @validator("birthdate", pre=True)
    def parse_birthdate(cls, value):
        return datetime.strptime(
            value,
            "%d/%m/%Y"
        ).date()


if __name__ == "__main__":
    odd_date = OddDate(birthdate="12/04/1992")
    print(odd_date.json()) #{"birthdate": "1992-04-12"}
Run Code Online (Sandbox Code Playgroud)


aig*_*fer 16

如果您不一定希望将此行为应用于所有日期时间,您可以创建一个扩展的自定义类型datetime。例如,要创建一个始终确保我们将 tzinfo 设置为 UTC 的日期时间的自定义类型:

from datetime import datetime, timezone

from pydantic.datetime_parse import parse_datetime


class utc_datetime(datetime):
    @classmethod
    def __get_validators__(cls):
        yield parse_datetime  # default pydantic behavior
        yield cls.ensure_tzinfo

    @classmethod
    def ensure_tzinfo(cls, v):
        # if TZ isn't provided, we assume UTC, but you can do w/e you need
        if v.tzinfo is None:
            return v.replace(tzinfo=timezone.utc)
        # else we convert to utc
        return v.astimezone(timezone.utc)
    
    @staticmethod
    def to_str(dt:datetime) -> str:
        return dt.isoformat() # replace with w/e format you want
Run Code Online (Sandbox Code Playgroud)

那么你的 pydantic 模型将如下所示:

from pydantic import BaseModel

class SomeObject(BaseModel):
    some_datetime_in_utc: utc_datetime

    class Config:
        json_encoders = {
            utc_datetime: utc_datetime.to_str
        }
Run Code Online (Sandbox Code Playgroud)

走这条路线有助于可重用性和关注点分离:)

  • @user7111260 这就是 Pydantic 配置模型的方式,请参阅 <https://pydantic-docs.helpmanual.io/usage/model_config/> (2认同)
  • 为什么这是一个不好的做法? (2认同)

Tra*_*umi 11

从 pydantic 2.0 开始,我们可以使用@field_serializer装饰器进行序列化和@field_validator验证。

摘自pydantic 文档

from datetime import datetime, timezone

from pydantic import BaseModel, field_serializer


class WithCustomEncoders(BaseModel):

    dt: datetime

    @field_serializer('dt')
    def serialize_dt(self, dt: datetime, _info):
        return dt.timestamp()


m = WithCustomEncoders(
    dt=datetime(2032, 6, 1, tzinfo=timezone.utc)
)
print(m.model_dump_json())
#> {"dt":1969660800.0}
Run Code Online (Sandbox Code Playgroud)

并进行验证

from pydantic_core.core_schema import FieldValidationInfo

from pydantic import BaseModel, ValidationError, field_validator


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

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

    @field_validator('password2')
    def passwords_match(cls, v, info: FieldValidationInfo):
        if 'password1' in info.data and v != info.data['password1']:
            raise ValueError('passwords do not match')
        return v

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


user = UserModel(
    name='samuel colvin',
    username='scolvin',
    password1='zxcvbn',
    password2='zxcvbn',
)
print(user)
"""
name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
"""
Run Code Online (Sandbox Code Playgroud)