【问题标题】:Pydantic Model: Convert UUID to string when calling .dict()Pydantic 模型:调用 .dict() 时将 UUID 转换为字符串
【发布时间】:2021-08-18 03:08:11
【问题描述】:

感谢您的宝贵时间。

在调用.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


【解决方案1】:

关注Pydantic's docs for classes-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)

【讨论】:

    【解决方案2】:

    您不需要将 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"
        }
    

    【讨论】:

      猜你喜欢
      • 2021-12-28
      • 2022-12-30
      • 1970-01-01
      • 2019-03-27
      • 1970-01-01
      • 1970-01-01
      • 2017-06-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多