Pydantic 模型:调用 .dict() 时将 UUID 转换为字符串

问题描述 投票:0回答:5

感谢您的宝贵时间。

在调用

.dict()
使用 pymongo 保存到 monogdb 时,我尝试将 UUID 字段转换为字符串。我尝试过
.json()
但似乎 mongodb 不喜欢它
TypeError: document must be an instance of dict, bson.son.SON, bson.raw_bson.RawBSONDocument, or a type that inherits from collections.MutableMapping

这是我到目前为止所做的:

from uuid import uuid4
from datetime import datetime
from pydantic import BaseModel, Field, UUID4

class TestModel(BaseModel):
    id: UUID4 = Field(default_factory=uuid4)
    title: str = Field(default="")
    ts: datetime = Field(default_factory=datetime.utcnow)

record = TestModel()
record.title = "Hello!"
print(record.json())
# {"id": "4d52517a-88a0-43f8-9d9a-df9d7b6ddf01", "title": "Hello!", "ts": "2021-08-18T03:00:54.913345"}
print(record.dict())
# {'id': UUID('4d52517a-88a0-43f8-9d9a-df9d7b6ddf01'), 'title': 'Hello!', 'ts': datetime.datetime(2021, 8, 18, 3, 0, 54, 913345)}

有什么建议吗?谢谢。


我能做的最好的事情就是在该模型中创建一个名为

to_dict()
的新方法并调用它

class TestModel(BaseModel):
    id: UUID4 = Field(default_factory=uuid4)
    title: str = Field(default="")

    def to_dict(self):
        data = self.dict()
        data["id"] = self.id.hex
        return data


record = TestModel()
print(record.to_dict())
# {'id': '03c088da40e84ee7aa380fac82a839d6', 'title': ''}
python python-3.x pydantic
5个回答
4
投票

Pydantic 可以在验证后或同时转换或验证字段。在这种情况下,您需要使用

validator

第一种方式(这种方式同时验证/转换到其他字段):

from uuid import UUID, uuid4
from pydantic import BaseModel, validator, Field

class ExampleSerializer(BaseModel):
    uuid: UUID = Field(default_factory=uuid4)
    other_uuid: UUID = Field(default_factory=uuid4)
    other_field: str
    
    _transform_uuids = validator("uuid", "other_uuid", allow_reuse=True)(
        lambda x: str(x) if x else x
    )

req = ExampleSerializer(
    uuid="a1fd6286-196c-4922-adeb-d48074f06d80",
    other_uuid="a1fd6286-196c-4922-adeb-d48074f06d80",
    other_field="123"
).dict()

print(req)

第二种方式(这种方式在其他方式之后验证/转换):

from uuid import UUID, uuid4
from pydantic import BaseModel, validator, Field

class ExampleSerializer(BaseModel):
    uuid: UUID = Field(default_factory=uuid4)
    other_uuid: UUID = Field(default_factory=uuid4)
    other_field: str
    
    @validator("uuid", "other_uuid")
    def validate_uuids(cls, value):
        if value:
            return str(value)
        return value

req = ExampleSerializer(
    uuid="a1fd6286-196c-4922-adeb-d48074f06d80",
    other_uuid="a1fd6286-196c-4922-adeb-d48074f06d80",
    other_field="123"
).dict()

print(req)

结果:

{'uuid': 'a1fd6286-196c-4922-adeb-d48074f06d80', 'other_uuid': 'a1fd6286-196c-4922-adeb-d48074f06d80', 'other_field': '123'}

3
投票

遵循 Pydantic 的类文档 -with-get_validators

我创建了以下自定义类型 NewUuid。

它接受与 UUID 格式匹配的字符串,并通过使用 uuid.UUID() 使用该值来验证它。如果该值无效,则 uuid.UUID() 引发异常(请参阅示例输出),如果该值有效,则 NewUuid 返回一个字符串(请参阅示例输出)。异常是任何 uuid.UUID() 的异常,但它也包含在 Pydantic 的异常中。

下面的脚本可以按原样运行。


import uuid

from pydantic import BaseModel


class NewUuid(str):
    """
    Partial UK postcode validation. Note: this is just an example, and is not
    intended for use in production; in particular this does NOT guarantee
    a postcode exists, just that it has a valid format.
    """

    @classmethod
    def __get_validators__(cls):
        # one or more validators may be yielded which will be called in the
        # order to validate the input, each validator will receive as an input
        # the value returned from the previous validator
        yield cls.validate

    @classmethod
    def __modify_schema__(cls, field_schema):
        # __modify_schema__ should mutate the dict it receives in place,
        # the returned value will be ignored
        field_schema.update(
            # simplified regex here for brevity, see the wikipedia link above
            pattern='^[A-F0-9a-f]{8}(-[A-F0-9a-f]{4}){3}-[A-F0-9a-f]{12}$',
            # some example postcodes
            examples=['4a33135d-8aa3-47ba-bcfd-faa297b7fb5b'],
        )

    @classmethod
    def validate(cls, v):
        if not isinstance(v, str):
            raise TypeError('string required')
        u = uuid.UUID(v)
        # you could also return a string here which would mean model.post_code
        # would be a string, pydantic won't care but you could end up with some
        # confusion since the value's type won't match the type annotation
        # exactly
        return cls(f'{v}')

    def __repr__(self):
        return f'NewUuid({super().__repr__()})'


class Resource(BaseModel):
    id: NewUuid
    name: str


print('-' * 20)
resource_correct_id: Resource = Resource(id='e8991fd8-b655-45ff-996f-8bc1f60f31e0', name='Server2')
print(resource_correct_id)
print(resource_correct_id.id)
print(resource_correct_id.dict())
print('-' * 20)

resource_malformed_id: Resource = Resource(id='X8991fd8-b655-45ff-996f-8bc1f60f31e0', name='Server3')
print(resource_malformed_id)
print(resource_malformed_id.id)

输出示例

--------------------

id=NewUuid('e8991fd8-b655-45ff-996f-8bc1f60f31e0') name='Server2'
e8991fd8-b655-45ff-996f-8bc1f60f31e0
{'id': NewUuid('e8991fd8-b655-45ff-996f-8bc1f60f31e0'), 'name': 'Server2'}

--------------------

Traceback (most recent call last):
  File "/Users/smoshkovits/ws/fallback/playground/test_pydantic8_uuid.py", line 58, in <module>
    resource_malformed_id: Resource = Resource(id='X8991fd8-b655-45ff-996f-8bc1f60f31e0', name='Server3')
  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Resource
id
  invalid literal for int() with base 16: 'X8991fd8b65545ff996f8bc1f60f31e0' (type=value_error)

0
投票

我找到了一种简单的方法,使用 .dict() 将 UUID 转换为字符串:

from uuid import UUID
from pydantic import BaseModel


class Person(BaseModel):
    id: UUID
    name: str
    married: bool


person = Person(id='a746f0ec-3d4c-4e23-b6f6-f159a00ed792', name='John', married=True)

print(json.loads(person.json()))

结果:

{'id': 'a746f0ec-3d4c-4e23-b6f6-f159a00ed792', 'name': 'John', 'married': True}

0
投票

有一种简单的方法可以帮助转换您的数据

from datetime import datetime

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

fake_db = {}


class Item(BaseModel)
    uid: UUID = Field(default_factory=uuid4)
    updated: datetime = Field(default_factory=datetime_now)

m_data = Item()
jsonable_encoder(m_data)
{
 'updated': '2024-05-14T03:06:25.427068+00:00',
 'uid': '62301346-cf2f-48f4-a8be-4c019a6f4f7c'
}

jsonable_encoder可以为你工作。


-3
投票

您不需要将 UUID 转换为字符串

mongodb
。您只需将记录作为 UUID 添加到数据库中,它会将其保存为
Binary

这是创建快速 UUID 并将其直接保存到数据库的示例:

    from pydantic import BaseModel
    from uuid import UUID, uuid4


    class Example(BaseModel):
        id: UUID
        note: str


    def add_uuid_to_db():
        #database = <get your mongo db from the client>
        collection = database.example_db
        new_id: UUID = uuid4()
        new_record = {
            'id': new_id,
            'note': "Hello World"
        }
        new_object = Example(**new_record)
        collection.update_one(
            filter={},
            update={"$set": new_object.dict()},
            upsert=True
        )


    if __name__ == '__main__':
        add_uuid_to_db()

这是结果记录:

    {
      "_id": {
        "$oid": "611d1d0d6e00f4849c14a792"
      },
      "id": {
        "$binary": "jyxxsFKaToupb55VUKm0kw==",
        "$type": "3"
      },
      "note": "Hello World"
    }
© www.soinside.com 2019 - 2024. All rights reserved.