如何在SQLAlchemy中定义(或模拟)包含不同类型的集合?

时间:2014-11-16 16:23:38

标签: python sqlalchemy

使用SQLAlchemy处理项目时,我试图使用我认为的组合模式。我有一类“所有者”对象;我将一些功能封装在组件类中,并通过为组件分配组件为所有者提供不同的功能。所有者和组件都具有需要序列化的状态,因此它们都是SQLAlchemy对象。这是一个简单的例子(linked for readability):

class Employee(DeclarativeBase):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    def __init__(self, name):
        self.name = name


class SalesRole(DeclarativeBase):
    __tablename__ = 'sales_role'
    id = Column(Integer, primary_key=True)
    employee_id = Column(Integer, ForeignKey('employee.id'))
    employee = relationship(
        'Employee',
        backref=backref('sales_role', uselist=False)
    )

    def __init__(self, employee):
        self.employee = employee
        self.total_sales = 0

    def __repr__(self):
        return "<SalesRole(employee='%s')>" % self.employee.name

    # Sales-specific data and behavior
    total_sales = Column(Float)


class CustomerSupportRole(DeclarativeBase):
    __tablename__ = 'support_role'
    id = Column(Integer, primary_key=True)
    employee_id = Column(Integer, ForeignKey('employee.id'))
    employee = relationship(
        'Employee',
        backref=backref('support_role', uselist=False)
    )

    def __init__(self, employee):
        self.employee = employee
        self.tickets_resolved = 0

    def __repr__(self):
        return "<CustomerSupportRole(employee='%s')>" % self.employee.name

    # Support-specific data and behavior
    tickets_resolved = Column(Integer)

我希望能够在所有者类上定义一个属性,该属性返回包含已分配给所有者的所有此类组件的集合(无论何种类型),即

# Define an Employee object bob and assign it some roles
>>> bob.roles
[<SalesRole(employee='Bob')>, <CustomerSupportRole(employee='Bob')>]

我想在没有硬编码对所有者类可以存在的组件类型的任何引用的情况下完成此操作 - 我想在不改变其他任何地方的代码的情况下定义新组件。

我可以使用sqlalchemy-utils中的generic_relationship将所有者实例映射到其组件的中间表或多或少地完成此操作。不幸的是,generic_relationship切断了SQLAlchemy对子对象的自动级联。

我在其他地方尝试过的另一种方法是使用SQLAlchemy的事件框架来监听针对所有者类('mapper_configured'事件)的关系映射。组件将从自身定义backref到所有者类,并使用info参数设置表示此关系的任意标志,作为指向我们希望通过此集合提供的组件之一。注册用于捕获映射事件的函数将测试此标志,并假设构建包含这些关系的集合,但我们永远无法弄清楚如何使其工作。

  • 对我而言,这个集合是一个SQLAlchemy对象并不重要,通过它我可以实际写入数据库(即bob.roles.append(SalesmanRole())。这将非常酷,但只是一个属性作为只读的可迭代视图就够了。
  • 在所有者类中是否存在命名属性backrefs并不重要(例如bob.sales_role。如果他们这样做就没关系,但我认为该集合对我来说实际上更重要。
  • 就像我之前提到的,级联很重要(除非你想让我相信它不是!)。
  • 同样,重要的是我不必在任何地方硬编码组件类型列表。无论什么魔法区别于我希望在其他所有组件的这个组件集中显示的类应该存在于组件本身的定义中。我希望在定义新组件时可以轻松扩展它。

有没有办法让这项工作?或者我应该采取不同的方法 - 随意告诉我这听起来像一个XY问题。

1 个答案:

答案 0 :(得分:0)

答案是我怀疑的,但并不是很难找到:class inheritance。简单,优雅,完成我想要的一切。

class Role(DeclarativeBase):
    __tablename__ = 'role'
    id = Column(Integer, primary_key=True)
    role_type = Column(String)
    employee_id = Column(Integer, ForeignKey('employee.id'))
    employee = relationship('Employee', backref='roles')

    __mapper_args__ = {
        'polymorphic_identity': 'employee',
        'polymorphic_on': role_type
    }


class SalesRole(Role):
    __tablename__ = 'sales_role'
    id = Column(Integer, ForeignKey('role.id'), primary_key=True)

    __mapper_args__ = {
        'polymorphic_identity': 'sales_role'
    }
    # Sales-specific attributes, etc.


class CustomerSupportRole(Role):
    __tablename__ = 'support_role'
    id = Column(Integer, ForeignKey('role.id'), primary_key=True)

    __mapper_args__ = {
        'polymorphic_identity': 'support_role'
    }
    # Support-specific attributes, etc.