【问题标题】:Mapping object IDs in sqlalchemy在 sqlalchemy 中映射对象 ID
【发布时间】:2021-09-27 20:52:37
【问题描述】:

我有一个现有的数据库架构,它使用整数作为主键,需要向每个表添加第二个 UUID 列。这个 UUID 是服务的消费者在引用记录时将使用的,现有的整数 ID 将保持私有并且仅在内部使用。下面是一个简化的例子。事务表具有 TransactionID 和 TransactionUUID 列,还具有指向 Category 表的外键 CategoryID。同样,Category 表有 CategoryID 和 CategoryUUID 列。

+------------+--------------------------------------+------+
| CategoryID | CategoryUUID                         | Name |
+------------+--------------------------------------+------+
|          1 | f9aafcf6-8cc7-4f1e-a847-bfbb0297bc5f | Foo  |
|          2 | 1cf892e3-c100-4542-bfd1-131ba8a18c17 | Bar  |
+------------+--------------------------------------+------+

+---------------+--------------------------------------+---------+---------------------+------------+
| TransactionID | TransactionUUID                      | Amount  | Time                | CategoryID |
+---------------+--------------------------------------+---------+---------------------+------------+
|             1 | 48182040-beb5-4df8-98fe-c3f6f5557cc0 | 55.0000 | 2021-07-15 09:56:11 |          2 |
+---------------+--------------------------------------+---------+---------------------+------------+
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import fields, post_load


db = SQLAlchemy()
ma = Marshmallow()


class Category(db.Model):
    __tablename__ = 'Category'
    id = db.Column('CategoryID', db.Integer, primary_key=True)
    uuid = db.Column('CategoryUUID', db.CHAR(36), unique=True, nullable=False)
    name = db.Column('Name', db.String(64), nullable=False)
    transactions = db.relationship('Transaction', back_populates='category')


class CategorySchema(ma.Schema):
    uuid = fields.UUID()
    name = fields.Str()

    @post_load
    def make_category(self, data, **kwargs):
        return Category(**data)


class Transaction(db.Model):
    __tablename__ = 'Transaction'
    id = db.Column('TransactionID', db.Integer, primary_key=True)
    uuid = db.Column('TransactionUUID', db.CHAR(36), unique=True, nullable=False)
    amount = db.Column('Amount', db.DECIMAL, nullable=False)
    time = db.Column('Time', db.DateTime, nullable=False)
    category_id = db.Column('CategoryID', db.Integer, db.ForeignKey('Category.CategoryID'), nullable=False)
    category = db.relationship('Category', back_populates='transactions')


class TransactionSchema(ma.Schema):
    uuid = fields.UUID()
    amount = fields.Decimal()
    time = fields.DateTime()
    category_uuid = fields.UUID()

    @post_load
    def make_transaction(self, data, **kwargs):
        return Transaction(**data)

我想要实现的是允许对事务资源的发布请求以允许创建新事务,并以某种方式将 TransactionSchema 类中的 category_uuid 字段链接到相应的 category_id。我面临的挑战是在序列化和反序列化对象时如何将 UUID 与其对应的整数 ID 关联起来。例如,当一个新事务被创建并且消费者通过这样的有效负载发布时:

{
    "uuid": "7f68e33f-c5d2-41c8-9826-5df904722a1a",
    "amount": 10.00,
    "time": "2021-07-17T04:50:25+00:00",
    "category_uuid": "f9aafcf6-8cc7-4f1e-a847-bfbb0297bc5f"
}

JSON 对象反序列化如下:

transaction = TransactionSchema().load({...})

Transaction 类没有 category_uuid 属性,需要以某种方式将此值转换为相应的整数 ID。同样,当像这样序列化事务时:

json = TransactionSchema().dump(transaction)

category_id属性需要翻译成对应的category_uuid值。

在 sqlalchemy 中是否有一种优雅的方式来实现这种 ID 映射?

【问题讨论】:

    标签: python sqlalchemy flask-sqlalchemy flask-marshmallow


    【解决方案1】:

    这样做的一种方法是利用marshmallow 中定义的post_loadpre_dump 装饰器。代码如下:

    from marshmallow import fields, post_load, pre_dump
    
    class TransactionSchema(ma.Schema):
        uuid = fields.UUID()
        amount = fields.Decimal()
        time = fields.DateTime()
        category_uuid = fields.UUID()
    
        @post_load
        def make_transaction(self, data, **kwargs):
            data["category_id"] = Category.query.filter_by(
                uuid=str(data["category_uuid"])
            ).all()[0].id
            del data["category_uuid"]
            return Transaction(**data)
        
        @pre_dump
        def serialize_transaction(self, obj, **kwargs):
            obj.category_uuid = obj.category.uuid
            return obj
    

    通过这种方式,您可以在(反)序列化交易时获得自动转换。

    【讨论】:

    • 感谢您的建议。我考虑过做这样的事情,虽然它确实有效,但在 post_load 和 pre_dump 修饰方法中添加数据访问逻辑对我来说感觉很混乱。例如,如果需要额外的验证逻辑,例如检查提供的 category_uuid 是否存在于数据库中,那么这些方法还需要在其中包含异常处理逻辑,以防出现这种情况。似乎这种逻辑更好地放置在资源类中(我正在使用烧瓶-restful)。我希望有一种方法可以在仅使用 sqlalchemy 之后实现我的目标。
    • 不确定是否只能使用sqlalchemy 来完成。我个人认为最好将此逻辑放在TransactionSchema 中,因为这对我来说更像是一个模式问题。解决您提到的问题的一种方法是在TransactionSchema中编写一个验证方法和一个转换方法,然后在make_transaction中执行它们,将这两个逻辑分开。
    猜你喜欢
    • 2011-08-03
    • 2015-03-25
    • 2010-11-05
    • 2014-02-20
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 2019-09-19
    • 2011-01-27
    相关资源
    最近更新 更多