SQLAlchemy将多个列映射到单个属性

3
我正在使用Flask和SQLAlchemy(通过Flask-SQLAlchemy; 使用MySQL或SQLite)在Python 3中构建Web应用程序,并遇到了一个情况,我想引用我的模型类上的单个属性,该属性封装了我的数据库中的多个列。我对MySQL非常熟悉,但这是我第一次涉足SQLAlchemy的高级用法。阅读文档,搜索SO和谷歌搜索引擎导致我找到了两种可能的解决方案:混合属性(docs)或复合列(docs)。

我的问题是使用每种方法的影响是什么,哪种方法是适合我情况的解决方案?我包含了下面的示例代码,它是我正在做的片段。


背景:我正在开发一个应用程序来跟踪和排序照片,并在其中有一个数据库表,用于存储这些照片的元数据,包括照片拍摄时间。由于照片是在特定地点拍摄的,因此拍摄日期和时间与关联时区有关。由于 SQL 与时区的关系不容易处理,我选择记录照片拍摄时间的两个列:一个 datetime 存储日期和时间,一个字符串存储时区名称。(请不要讨论如何在 SQL 中存储时区感知日期和时间的问题。)我想要的是模型类上的单个参数,可以用于获取正确的 Python datetime 对象,并且我也可以像设置任何其他列一样设置它。
这是我的表格:
class Photo(db.Model):

    __tablename__ = 'photos'

    id = db.Column(db.Integer, primary_key=True)
    ...
    taken_dt = db.Column(db.datetime, nullable=False)
    taken_tz = db.Column(db.String(64), nullable=False)
    ...

这是我使用混合参数的代码(在上面的datetime/pytz类中添加,datetime/pytz代码是伪代码):

    @hybrid_parameter
    def taken(self):
        return datetime.datetime(self.taken_dt, self.taken_tz)

    @taken.setter(self, dt):
        self.taken_dt = dt
        self.taken_tz = dt.tzinfo

从那里开始,我不确定我需要什么样的@taken.expression@taken.comparator,或者为什么我会选择一个而不是另一个。

这是我使用复合列(再次添加到上面的类中,datetime/pytz代码是伪代码)所拥有的:

    taken = composite(DateTimeTimeZone._make, taken_dt, taken,tz)

class DateTimeTimeZone(object):

    def __init__(self, dt, tz):
        self.dt = dt
        self.tz = tz

    @classmethod
    def from_db(cls, dt, tz):
        return DateTimeTimeZone(dt, tz)

    @classmethod
    def from_dt(cls, dt):
        return DateTimeTimeZone(dt, dt.tzinfo)

    def __composite_values__(self):
        return (self.dt, self.tz)

    def value(self): 
        #This is here so I can get the actual datetime.datetime object
        return datetime.datetime(self.dt, self.tz)

看起来这种方法有相当多的额外开销,我无法想出一种直接从datetime.datetime对象设置它的方式,而不必先实例化值对象使用.from_dt

如果我走错了方向,希望能得到任何指导。谢谢!

1个回答

4
TL;DR: 通过将AttributeEvent与列连接并检查具有设置tz属性的datetime实例,然后返回DateTimeTimeZone对象来解决问题。如果您查看SQLAlchemy文档中的Attribute Events,则可以了解到可以告诉SQLAlchemy侦听属性设置事件并在此调用您的代码。在其中,您可以按照自己的意愿对正在设置的值进行任何修改。但是,您不能同时访问类的其他属性。我尚未尝试与复合材料结合使用,因此不知道这是否会在复合材料的类型转换之前或之后被调用。您需要尝试。

编辑:这完全取决于您想要实现什么。 AttributeEvent可以帮助您确保数据一致性,而hybrid_property和其它方法则可以使查询更加容易。您应该根据预期用例使用每个方法。

更详细的讨论:

hybrid_attributecomposite是两种完全不同的方法。要理解hybrid_attribute,首先必须了解column_property的作用和功能。

1)column_property

这个方法放置在映射器上,可以包含任何可选择的内容。因此,如果将具体子选择放入column_property中,则可以像访问具体列一样只读地访问它。计算是即时完成的。您甚至可以使用它来搜索条目。 SQLAlchemy会为您构建包含子选择的正确选择。

示例:

class User(Base):
   id = Column(Integer, primary_key=True)
   first_name = Column(Unicode)
   last_name = Column(Unicode)

   name = column_property(first_name + ' ' + last_name)
   category = column_property(select([CategoryName.name])
                              .select_from(Category.__table__
                                           .join(CategoryName.__table__))
                              .where(Category.user_id == id))

db.query(User).filter(User.name == 'John Doe').all()

db.query(User).filter(User.category == 'Paid').all()

正如您所看到的,这可以简化很多代码,但必须小心考虑性能影响。

2) hybrid_method和hybrid_attribute

hybrid_attribute就像column_property一样,但可以在实例上下文中调用不同的代码路径。因此,您可以在类级别上拥有selectable,但在实例级别上使用不同的实现。使用hybrid_method,您甚至可以对两侧进行参数化。

3) composite_attribute

这是使您能够将多个具体列组合为逻辑单个列的方法。您必须编写一个类来表示这个逻辑列,以便SQLAlchemy可以从中提取正确的值并在查询中使用它。这很好地集成在查询框架中,不应该带来任何额外的问题。根据我的经验,复合列的用例相当罕见。您的用例似乎很好。要修改值,您始终可以使用AttributeEvents。如果您想要整个实例可用,您必须在提交之前调用MapperEvent。这肯定有效,因为我使用它来实现了一个完全透明的审计跟踪跟踪系统,该系统将每个表中更改的每个值存储在单独的一组表中。


感谢简明的比较,对我很有帮助!我肯定在寻找尽可能透明地设置和获取以及最好还能过滤的解决方案。属性事件替代方案是我之前没有遇到过的,看起来很有趣,我需要进一步调查。但是我觉得混合属性方法最接近我想要的,因为它将允许我使用现有的日期时间对象来获取和设置取件日期,而不需要构建一个新的值类来包装它们。 - bjg222

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