Sqlalchemy:避免多重继承和使用抽象基类

32

我有许多使用SQLAlchemy的表,以对象的形式建模,这些对象继承自对declarative_base()的调用结果。例如:

Base = declarative_base()
class Table1(Base):
    # __tablename__ & such here

class Table2(Base):
     # __tablename__ & such here

等等。我随后想要让一些通用功能对我的每个数据库表类可用,根据文档所述,最简单的方法就是使用多重继承:

Base = declarative_base()

class CommonRoutines(object):
    @classmethod
    def somecommonaction(cls):
        # body here

class Table1(CommonRoutines, Base):
    # __tablename__ & such here

class Table2(CommonRoutines, Base):
     # __tablename__ & such here

我不喜欢这种方式的原因有三点,A)一般而言,多重继承有些棘手(例如解决像super()调用等问题),B)如果我添加一张新表格,我必须记得同时从BaseCommonRoutines继承,C)实际上,“CommonRoutines”类在某种程度上是一个表格类型。实际上,CommonBase是一个抽象基类,它定义了一组对所有表格都通用的字段和例程。换句话说:“它是一个”抽象表格“。

所以,我想要的是这样的:

Base = declarative_base()

class AbstractTable(Base):
    __metaclass__ = ABCMeta  # make into abstract base class

    # define common attributes for all tables here, like maybe:
    id = Column(Integer, primary_key=True)

    @classmethod
    def somecommonaction(cls):
        # body here

class Table1(AbstractTable):
    # __tablename__ & Table1 specific fields here

class Table2(AbstractTable):
     # __tablename__ & Table2 specific fields here

但这当然行不通。因为我需要 A) 为 AbstractTable 定义一个 __tablename__,B) 这个抽象类的特性会带来各种问题,C) 需要指定 AbstractTable 与每个单独表之间的某种数据库关系。

所以我的问题是:有没有可能以合理的方式实现这一点?理想情况下,我想强制执行以下要求:

  • 不允许多重继承
  • CommonBase/AbstractTable 是抽象的(即不能实例化)
4个回答

55

SQLAlchemy 0.7.3 版本引入了__abstract__指令,用于创建抽象类,即使它们是sqlalchemy.ext.declarative.api.Base的子类也不会被映射到数据库表中。因此,现在你可以像这样创建一个基类:

Base = declarative_base()

class CommonRoutines(Base):
    __abstract__ = True

    id = Column(Integer, primary_key=True)

    def __init__(self):
        # ...

注意 CommonRoutines 没有 __tablename__ 属性。然后创建如下的子类:

class Foo(CommonRoutines):
    __tablename__ = 'foo'

    name = Column(...)

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

这将映射到表格foo,并继承自CommonRoutinesid属性。
来源和更多信息:http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/declarative.html#abstract

在我的情况下它不起作用。当创建抽象类时,SqlAlchemy 报告缺少 __tablename__ 作为错误。 - xsubira
链接已损坏 :( - aquirdturtle
我建议向下滚动到确认的答案,但这里是 Aston 分享的声明性抽象链接的工作链接。https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html#abstract - BejanSadeghian

16

这很简单,您只需使用cls=参数使 declarative_base() 返回一个继承自你的CommonBaseBase类。 在Augmenting The Base文档中也有展示。您的代码可能类似于以下示例:

class CommonBase(object):
    @classmethod
    def somecommonaction(cls):
        # body here

Base = declarative_base(cls=CommonBase)

class Table1(Base):
    # __tablename__ & Table1 specific fields here

class Table2(Base):
     # __tablename__ & Table2 specific fields here

请纠正我,那些"AbstractTable"的继承应该改成"Base",是吗?(即class Table1(Base): - Adam Parkin
是的,这正是我正在寻找的。谢谢!这种方法唯一的缺点是现在CommonBase类的__init__不会被调用(declarative_base生成的类在其__init__中不调用super,因此Python的协作多重继承机制无法工作)。嗯... - Adam Parkin

4

您可以使用AbstractConcreteBase来创建一个抽象基础模型:

from sqlalchemy.ext.declarative import AbstractConcreteBase


class AbstractTable(AbstractConcreteBase, Base):
    id = db.Column(db.Integer, primary_key=True)

    @classmethod
    def somecommonaction(cls):
        # body here

3

如果您想拥有共同列的几个模型,那么您可以使用 __abstract__@declared_attr 来继承共享表属性。例如:

Base = declarative_base()

class CommonRoutines(Base):
    __abstract__ = True

    id = Column(Integer, primary_key=True)
    modified_at = Column(DateTime)
    
    @declared_attr
    def modified_by(self):
        # `user.id` is another table called `user` with an `id` field
        return Column(Integer, ForeignKey('user.id', name='fk_modified_by_user_id')) 


    def __init__(self):
        self.modified_by = None
        super().__init__()


class Foo(CommonRoutines):
    __tablename__ = 'foo'

    name = Column(...)


使用此解决方案,您将拥有一个名为Foo的表格,其中包含Foo类(name)和CommonRoutines中的字段(idmodified_atmodified_by)。

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