Skip to main content
 首页 » 编程设计

python之使用sqlalchemy加载动态关系类

2025年05月04日11davidwang456

我有一个动态加载类的库。它被暴露为

mylib.registry.<className> 

registry 是一个类的实例,它包含类名(字符串)到模块名的字典,以及一个在需要时动态加载类的 getattr 调用。因此,用户可以引用任何类,而不必处理模块位置(类名有一个全局命名空间,但模块名没有)。

例如,条目:

{'X', 'mylib.sublib.x', 
 'Y', 'mylib.sublib.y'} 

然后可以这样使用:

import mylib 
 
x = mylib.registry.X() 
y = mylib.registry.Y() 

这就是背景。最重要的是,这些对象是彼此有关系的 sqlalchemy ORM 类。假设 X 与 Y 之间存在一对多关系。

因此假设这个定义。

class X(Base): 
    y_id = Column(Integer, ForeignKey('y.id')) 
    y = relationship('Y') 
 
class Y(Base): 
    xs = relationship('X') 

这些在单独的文件中,每个文件都导入顶级注册表。

那么问题来了——我如何在不预先加载每个类的情况下真正解决这个问题?

上面的示例不起作用,因为如果我通过注册表仅导入 X,则 Y 不在 sqlalchemy 类注册表中,因此关系中断。

如果我导入注册表本身,然后直接引用类,那么由于相互依赖性,模块不会加载。

我尝试使用 lambda 来延迟加载,但这也失败了,并出现有关缺少“策略”的错误。

其他人在这里使用了哪些方法?如果我遗漏了一些明显的东西,请告诉我。这是漫长的一天。

谢谢。

请您参考如下方法:

您永远不应该在彼此不了解的两侧使用关系。通常,这可以通过使用 backref 来避免,但在您的情况下,这会产生问题,因为您希望每一方都知道自己的关系。这里的技巧是 back_populates 关键字,由 relationship 提供。 :

y = relationship("Y", back_populates="xs") 

和

xs = relationship("X", back_populates="y") 

应用这些将使他们了解彼此。但是,它不会解决您的导入问题。通常,您现在可以在Y 端from x import X。但反之则行不通,因为它会创建循环导入。

技巧很简单:将导入放在您要导入的类之后。因为 relationship 中的字符串是延迟计算的,所以您可以在定义关系后 导入类。所以对于 X 这样做:

class X(Base): 
    __tablename__ = 'x' 
    id = Column(Integer, primary_key=True) 
    y_id = Column(ForeignKey('y.id')) 
    y = relationship("Y", back_populates="xs") 
 
from y import Y 

对于 Y 也是如此(这不是必需的,但会产生更多的对称性)。我还会在那里发表评论来解释这一点,以避免有人将其置于顶部,从而破坏程序。