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

9

感谢您的时间。

我试图在使用 pymongo 将 UUID 字段转换为字符串并调用 .dict() 方法保存到 monogdb 时进行转换。我尝试使用 .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': ''}

相关 https://dev59.com/6FEG5IYBdhLWcg3wduYf - alex_noname
4个回答

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文档中关于带有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}

1
它有一个很大的缺点,就是对整个模型进行了冗余的序列化和反序列化。 - Tzoiker

-2

对于 mongodb,您无需将 UUID 转换为字符串。您可以将记录直接作为 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"
    }

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