动态 SQLAlchemy ORM 关系生成
Dynamic SQLAlchemy ORM relationship generation
Premise:我有一个 lot 表必须单独创建(它们不能动态创建),因此,我发现我自己经常不得不制作允许相关表标准化的mixins:
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_Table
、C_Table
等都有自己的Relator classes时,它变得非常重复,因此,应该很容易解决的问题代码。
我的解决方案:我创建了一个 class 工厂 (?),它创建了一个 mixin class 供一次性使用。
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 对齐方式来完成此操作,并且回旋较少 "hack"。或者总结一下:如何制作生成关系的通用 SQLAlchemy 混合?
我对此一头雾水。不确定这个解决方案是否适合您的需求,但我这样做更多是为了自己的学习练习,如果它对您有帮助,那就太好了。
所以 objective 能够在模型上定义外键和关系,输入尽可能少,这就是我想出的。
以下是我使用的模型:
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']
注意每个模型上的 class 变量 parents
,它是一个字符串序列,应该是从同一 declarative_base
实例继承的其他模型名称。外键和与父 class 的关系将在声明它们为父的 class 上创建。
然后利用以下事实:
Attributes may be added to the class after its construction, and they
will be added to the underlying Table and mapper() definitions as
appropriate
(参见 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)>
这是它创建的架构:
这不适用于复合 primary/foreign 键,但我认为将它放在那里不会太费力。如果 len(inspect(parent).primary_keys) > 1
你需要构建 ForeignKeyConstraints
并将它们添加到 table 定义中,但我根本没有测试过。
我也不认为,如果您能够以这样一种方式命名您的模型,即可以从模型本身的名称中推断出模型的从属关系,那么将其完全自动化并不是一件容易的事.再一次,只是大声思考。
Premise:我有一个 lot 表必须单独创建(它们不能动态创建),因此,我发现我自己经常不得不制作允许相关表标准化的mixins:
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_Table
、C_Table
等都有自己的Relator classes时,它变得非常重复,因此,应该很容易解决的问题代码。
我的解决方案:我创建了一个 class 工厂 (?),它创建了一个 mixin class 供一次性使用。
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 对齐方式来完成此操作,并且回旋较少 "hack"。或者总结一下:如何制作生成关系的通用 SQLAlchemy 混合?
我对此一头雾水。不确定这个解决方案是否适合您的需求,但我这样做更多是为了自己的学习练习,如果它对您有帮助,那就太好了。
所以 objective 能够在模型上定义外键和关系,输入尽可能少,这就是我想出的。
以下是我使用的模型:
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']
注意每个模型上的 class 变量 parents
,它是一个字符串序列,应该是从同一 declarative_base
实例继承的其他模型名称。外键和与父 class 的关系将在声明它们为父的 class 上创建。
然后利用以下事实:
Attributes may be added to the class after its construction, and they will be added to the underlying Table and mapper() definitions as appropriate
(参见 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)>
这是它创建的架构:
这不适用于复合 primary/foreign 键,但我认为将它放在那里不会太费力。如果 len(inspect(parent).primary_keys) > 1
你需要构建 ForeignKeyConstraints
并将它们添加到 table 定义中,但我根本没有测试过。
我也不认为,如果您能够以这样一种方式命名您的模型,即可以从模型本身的名称中推断出模型的从属关系,那么将其完全自动化并不是一件容易的事.再一次,只是大声思考。