动态SQLAlchemy ORM关系生成

时间:2018-07-21 21:02:36

标签: python-3.x orm sqlalchemy factory

前提:我有一个很多表格,这些表格必须单独创建(它们不能动态创建),因此,我发现自己经常需要制作允许使用的mixin相关表的标准化:

class A_Table(Base):
    id = Column(Integer, primary_key=True)

class A_Relator(My_Mixin_Base):
    @declared_attr
    def a_table_id(cls):
        return Column(ForeignKey(A_Table.id))

    @declared_attr
    def a_table(cls):
        return relationship(A_Table)

class B_Table(A_Relator, Base):
    id = Column(Integer, primary_key=True)

class C_Table(A_Relator, Base):
    id = Column(Integer, primary_key=True)

class D_Table(A_Relator, Base):
    id = Column(Integer, primary_key=True)

# ad nauseam

简单,但是当B_TableC_Table等都有自己的Relator类时,它会变得非常重复,因此应该在代码中轻松解决。

我的解决方案:我创建了一个类工厂(?),该类创建了一次使用的mixin类。

def related(clss, defined=False, altName=None):
    class X((Definer if defined else Relator),):
        linkedClass = clss

        @classmethod
        def linkedClassFieldName(cls):
            return "{}Id".format(clss.getBackrefName())

        def linkId(cls):
            return Column(ForeignKey(clss.id))

        def linkRe(cls):
            return relationship(clss,
                                foreign_keys=getattr(cls, "{}Id".format(clss.getBackrefName() if not altName else altName)),
                                backref=cls.getBackrefName())

    setattr(X, "{}Id".format(clss.getBackrefName() if not altName else altName), declared_attr(X.linkId))
    setattr(X,   "{}".format(clss.getBackrefName() if not altName else altName), declared_attr(X.linkRe))
    del X.linkId
    del X.linkRe

    return X

您可以执行以下操作并完成它:

class B_Table(related(A_Table), Base):
    id = Column(Integer, primary_key=True)

...但是这是混乱且令人困惑的,我想有一种更好的方法来做到这一点,从而大大减少了不确定性。

问题:我正在寻找一种方法,以一种更直接的SQLAlchemy对齐方式,减少回旋现象。或总而言之:我如何制作一个通用的SQLAlchemy mixin来生成关系?

1 个答案:

答案 0 :(得分:1)

我对此一团糟。不确定此解决方案是否能满足您的需求,但我将其作为自己的一项学习练习来进行,如果对您有所帮助,那就太好了。

所以我的目标是能够在模型上定义外键和关系,并且输入尽可能少,这就是我想出的。

这是我使用的模型:

class Base:
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    @declared_attr
    def id(cls):
        return Column(Integer, primary_key=True)

    def __repr__(self):
        return f'<{type(self).__name__}(id={self.id})>'

Base = declarative_base(cls=Base)

class A_Table(Base):
    parents = []

class B_Table(Base):
    parents = ['A_Table']

class C_Table(Base):
    parents = ['A_Table', 'B_Table']

请注意每个模型上的类变量parents,这是一个字符串序列,应该是从同一declarative_base实例继承的其他模型名称。外键和与父类的关系将在将它们声明为父类的类上创建。

然后利用以下事实:

  

属性可以在构造后添加到类中,并且它们   将被添加到基础的Table和mapper()定义中   合适

(请参阅docs

我遍历Base上定义的所有模型,并根据给定的父级构建所需的对象并将其插入。

这是完成所有操作的函数:

from sqlalchemy import inspect  # this would be the only new import you'd need

def relationship_builder(Base):
    """ Finds all models defined on Base, and constructs foreign key
    columns and relationships on each as per their defined parent classes.

    """

    def make_fk_col(parent):
        """ Constructs a Column of the same type as the primary
        key of the parent and establishes it as a foreign key.
        Constructs a name for the foreign key column and attribute.

        """
        parent_pk = inspect(parent).primary_key[0]
        fk_name = f'{parent.__name__}_{parent_pk.name}'
        col = Column(
            fk_name, parent_pk.type,
            ForeignKey(f'{parent.__tablename__}.{parent_pk.name}')
        )
        return fk_name, col

    # this bit gets all the models that are defined on Base and maps them to
    # their class name.
    models = {
        cls.__name__: cls  for cls in Base._decl_class_registry.values() if
        hasattr(cls, '__tablename__')
    }

    for model in models.values():
        for parentname in model.parents:
            parent = models.get(parentname)
            if parent is not None:
                setattr(model, *make_fk_col(parent))
                rel = relationship(parent, backref=model.__name__)
                setattr(model, parentname, rel)

要测试,这只是在我定义了其他所有内容的模块的底部:

if __name__ == '__main__':
    relationship_builder(Base)
    a = A_Table(id=1)
    b = B_Table(id=1)
    c = C_Table(id=1)
    a.B_Table.append(b)
    a.C_Table.append(c)
    b.C_Table.append(c)
    print(b.A_Table)
    print(c.A_Table)
    print(c.B_Table)
# <A_Table(id=1)>
# <A_Table(id=1)>
# <B_Table(id=1)>

这是它创建的架构:

Schema produced

这不适用于复合主键/外键,但是我认为将其放到那里并不是一件容易的事。如果是len(inspect(parent).primary_keys) > 1,则需要构建ForeignKeyConstraints并将其添加到表定义中,但是我还没有进行任何测试。

我也认为,如果您可以以可以从模型本身的名称推断出模型的从属地位的方式命名模型,那么使其完全自动化不会太费力。再次,只是大声思考。