Python [pydantic] - 日期验证

4

我希望您能将JSON输入有效性验证为Pydantic类,然后将文件简单地注入到Mongo中。

具有日期类型的简单类


class CustomerBase(BaseModel):
    birthdate: date = None


使用马达与Mongo一起工作
数据库配置:
from motor.motor_asyncio import AsyncIOMotorClient

DB = DB_CLIENT[CONF.get("databases", dict())["mongo"]["NAME"]]

2021年8月3日 - 更新:

我进行了以下调试测试,首先打印类以查看它是如何保存的,接下来尝试将其注入到Mongo中。

因此输入为:

{ "birthdate": "2021-03-05"}

路由:

@customers_router.post("/", response_model=dict)
async def add_customer(customer: CustomerBase):
    print(customer.dict())

>> {'birthdate': datetime.date(2021, 3, 5)}

    await DB.customer.insert_one(customer.dict())
    return {"test":1}

>> 
 File "./customers/routes.py", line 74, in add_customer
    await DB.customer.insert_one(customer.dict())
  File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.8/site-packages/pymongo/collection.py", line 698, in insert_one
    self._insert(document,
  File "/usr/local/lib/python3.8/site-packages/pymongo/collection.py", line 613, in _insert
    return self._insert_one(
  File "/usr/local/lib/python3.8/site-packages/pymongo/collection.py", line 602, in _insert_one
    self.__database.client._retryable_write(
  File "/usr/local/lib/python3.8/site-packages/pymongo/mongo_client.py", line 1498, in _retryable_write
    return self._retry_with_session(retryable, func, s, None)
  File "/usr/local/lib/python3.8/site-packages/pymongo/mongo_client.py", line 1384, in _retry_with_session
    return self._retry_internal(retryable, func, session, bulk)
  File "/usr/local/lib/python3.8/site-packages/pymongo/mongo_client.py", line 1416, in _retry_internal
    return func(session, sock_info, retryable)
  File "/usr/local/lib/python3.8/site-packages/pymongo/collection.py", line 590, in _insert_command
    result = sock_info.command(
  File "/usr/local/lib/python3.8/site-packages/pymongo/pool.py", line 699, in command
    self._raise_connection_failure(error)
  File "/usr/local/lib/python3.8/site-packages/pymongo/pool.py", line 683, in command
    return command(self, dbname, spec, slave_ok,
  File "/usr/local/lib/python3.8/site-packages/pymongo/network.py", line 120, in command
    request_id, msg, size, max_doc_size = message._op_msg(
  File "/usr/local/lib/python3.8/site-packages/pymongo/message.py", line 714, in _op_msg
    return _op_msg_uncompressed(
bson.errors.InvalidDocument: cannot encode object: datetime.date(2021, 3, 5), of type: <class 'datetime.date'>

问题: 1. 类中保存的日期格式为生日:datetime.date(2021,3,5),这是否符合预期? 2. 明显的问题来自: ''' DB.customer.insert_one(customer.dict()) ''' 当我将Class中的日期类型更改为字符串时,它可以工作。

更新 09/03/2022:

根据Tom的建议: 添加了修饰器和parse_birthday函数。 现在我能够将文档记录到Mongo中,但无法读取它。


class CustomerBase(BaseModel):
    birthdate: datetime.date

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

    def dict(self, *args, **kwargs) -> 'DictStrAny':
        for_mongo = kwargs.pop('for_mongo', False)
        d = super().dict(*args, **kwargs)
        if for_mongo:
            for k, v in d.items():
                if isinstance(v, datetime.date):
                    d[k] = datetime.datetime(
                        year=v.year,
                        month=v.month,
                        day=v.day,
                    )
        return d

class CustomerOnDB(CustomerBase):
    id_: str

分配数据(工作中): 输入:{"birthdate": "01/11/1978"}

@customers_router.post("/", response_model=dict )
async def add_customer(customer: CustomerBase):

    customer_op = await DB.customer.insert_one(customer.dict(for_mongo=True))
    if customer_op.inserted_id:
        #customer_op.inserted_id -> is the str _id
        await _get_customer_or_404(customer_op.inserted_id)
        return { "id_": str(customer_op.inserted_id) }

当尝试读取:

def validate_object_id(id_: str):
    try:
        _id = ObjectId(id_)
    except Exception:
        raise HTTPException(status_code=400)
    return _id


@customers_router.get(
    "/{id_}",
    response_model=CustomerOnDB
)
async def get_customer_by_id(id_: ObjectId = Depends(validate_object_id)):
    customer = await DB.customer.find_one({"_id": id_})
    if customer:
        customer["id_"] = str(customer["_id"])
        return customer
    else:
        raise HTTPException(status_code=404, detail="Customer not found")

获取:


  File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 126, in serialize_response
    raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 1 validation error for CustomerOnDB
response -> 0 -> birthdate
  strptime() argument 1 must be str, not datetime.datetime (type=type_error)


1
你说你想验证 dd/mm/yyyy 并给出了一个 yyyy-mm-dd 的例子。那么到底是哪个呢? - Tom Wojcik
1
你的错误信息提到了bson,但在你的问题中没有其他提及。你能否提供一个[mcve]来展示你的问题? - jwodder
@jwodder -u没错,错误来自bson,主帖已更新更多细节。 - AviC
@TomWojcik - 其实两种格式都不行,建议使用dd/mm/yyyy格式。 - AviC
是的,它确实有用。展示一下哪里不起作用。 - Tom Wojcik
1个回答

2

我不确定你的问题是什么,因为你的CustomerBase在处理这个输入时工作正常。

如果你想解析%d/%m/%Y日期,请使用验证器和pre参数进行解析。

class CustomerBase(BaseModel):
    birthdate: date

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

编辑: 您添加了一条评论,提到了另一件不能按照您的预期工作的事情。据我所知,Mongo不接受datetime.date。只需在转储为字典时将其更改为datetime.datetime或更改类型为datetime即可。

示例

import datetime

from pydantic.main import BaseModel


class CustomerBase(BaseModel):
    birthdate: datetime.date

    def dict(self, *args, **kwargs) -> 'DictStrAny':
        d = super().dict(*args, **kwargs)
        for k, v in d.items():
            if isinstance(v, datetime.date):
                d[k] = datetime.datetime(
                    year=v.year,
                    month=v.month,
                    day=v.day,
                )
        return d

如果您需要这两种功能。
import datetime

from pydantic import validator
from pydantic.main import BaseModel


class CustomerBase(BaseModel):
    birthdate: datetime.date

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

    def dict(self, *args, **kwargs) -> 'DictStrAny':
        for_mongo = kwargs.pop('for_mongo', False)
        d = super().dict(*args, **kwargs)
        if for_mongo:
            for k, v in d.items():
                if isinstance(v, datetime.date):
                    d[k] = datetime.datetime(
                        year=v.year,
                        month=v.month,
                        day=v.day,
                    )
        return d


>>> c = CustomerBase(**{"birthdate": "03/05/2021"})
>>> c.dict()
>>> {'birthdate': datetime.date(2021, 5, 3)}
>>> c.dict(for_mongo=True)
>>> {'birthdate': datetime.datetime(2021, 5, 3, 0, 0)}


谢谢Tom,所以当涉及到Mongo时,看起来我需要将datetime.date函数转换为datetime.datetime,并且还需要处理格式的装饰器?有没有一种方法可以在def dict()内包含日期格式处理? - AviC
当您想将字典加载到 pydantic 对象中时,可以使用 validator 方法;而在创建 Mongo 输入时,则需要使用 dict() 方法(从 pydantic 对象转换为 dict)。因此,无法将它们合并为单个方法。但是,同时使用这两种方法也没有问题。 - Tom Wojcik
汤姆,将生日类型更改为datetime.datetime将需要用户以复杂的格式插入生日,这是您的意思吗?还是需要验证器? - AviC
parse_birthday 中不要调用 date(),这样它会返回带有时间的日期,并将 birthdate 的类型更改为 datetime.datetime - Tom Wojcik
Tom,不确定您在parse_birthday中对date()的意思,因为没有调用date()。 我已经更新了主贴并提出了从Mongo检索数据时遇到的问题, 似乎为了非常微不足道的事情而感到头痛:( - AviC
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接