如何更改pydantic格式的日期



如何更改pydantic日期格式以进行验证和序列化?为了验证,我使用@validator。这两种情况都有解决方案吗?

您可以使用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"}

这个变体也可以在fastapi的序列化器中工作,我实际上就是这样使用它的。

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

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"}

如果您不需要将此行为应用于所有日期时间,您可以创建自定义类型扩展datetime。例如,要创建一个自定义类型,以确保我们有一个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

那么您的pydantic模型将看起来像:

from pydantic import BaseModel
class SomeObject(BaseModel):
some_datetime_in_utc: utc_datetime
class Config:
json_encoders = {
utc_datetime: utc_datetime.to_str
}

这样做有助于可重用性和关注点分离:)

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

摘自pydantic docs:

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}

验证:

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'
"""

为了确保datetime字段是Timezone-Aware并且设置为UTC,我们可以在Pydantic v2中使用Annotated验证器。

从文档中引用:

当你想将验证绑定到类型而不是模型或字段时,你应该使用Annotated验证器。

from datetime import timezone, datetime
from typing import Annotated
from pydantic import BaseModel, AwareDatetime, AfterValidator, ValidationError

def validate_utc(dt: AwareDatetime) -> AwareDatetime:
"""Validate that the pydantic.AwareDatetime is in UTC."""
if dt.tzinfo.utcoffset(dt) != timezone.utc.utcoffset(dt):
raise ValueError("Timezone must be UTC")
return dt

DatetimeUTC = Annotated[AwareDatetime, AfterValidator(validate_utc)]

class Datapoint(BaseModel):
timestamp: DatetimeUTC

# valid
d0 = Datapoint(timestamp=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
print(f"d0: {d0.timestamp}, timezone: {d0.timestamp.tzinfo}")
# valid
d1 = Datapoint(timestamp='2021-01-01T00:00:00+00:00')
print(f"d1: {d1.timestamp}, timezone: {d1.timestamp.tzinfo}")
# valid
d2 = Datapoint(timestamp='2021-01-01T00:00:00Z')
print(f"d2: {d2.timestamp}, timezone: {d2.timestamp.tzinfo}")
# invalid, missing timezone
try:
d3 = Datapoint(timestamp='2021-01-01T00:00:00')
except ValidationError as e:
print(f"d3: {e}")
# invalid, non-UTC timezone
try:
d4 = Datapoint(timestamp='2021-01-01T00:00:00+02:00')
except ValidationError as e:
print(f"d4: {e}")

如果我们运行这个,我们看到d0, d1, d2是有效的,而d3和d4不是:

d0: 2021-01-01 00:00:00+00:00, timezone: UTC
d1: 2021-01-01 00:00:00+00:00, timezone: UTC
d2: 2021-01-01 00:00:00+00:00, timezone: UTC
d3: 1 validation error for Datapoint
timestamp
Input should have timezone info [type=timezone_aware, input_value='2021-01-01T00:00:00', input_type=str]
For further information visit https://errors.pydantic.dev/2.3/v/timezone_aware
d4: 1 validation error for Datapoint
timestamp
Value error, Timezone must be UTC [type=value_error, input_value='2021-01-01T00:00:00+02:00', input_type=str]
For further information visit https://errors.pydantic.dev/2.3/v/value_error

最新更新