完全使用动态表生成刷新SQLAlchemy

7 投票
1 回答
6621 浏览
提问于 2025-04-18 15:55

我需要在一个循环里创建很多相似的数据库,位置各不相同。在循环开始时,我会为新的 path_sql_db 在磁盘上创建一个引擎。

    engine = sa.create_engine("sqlite:///{}".format(path_sql_db), echo=0, listeners=[util_sa.ForeignKeysListener()])
    Session = sa.orm.sessionmaker(bind=engine)
    session = Session()

然后,我的表在几个模块中都继承自一个叫 DB_Base 的东西,这个东西是在外部模块里定义的;

from sqlalchemy.ext.declarative import declarative_base
DB_Base = declarative_base()

问题是,在下一个循环迭代时,我无法创建我的表,因为它们似乎还存在于某个地方?

InvalidRequestError: Table 'vector_var01' is already defined for this MetaData instance.  
Specify 'extend_existing=True' to redefine options and columns on an existing Table object.

我试过从引擎上调用 MetaData.drop_all();

meta = sa.MetaData(bind = engine)
meta.reflect()
meta.drop_all()
session.close()

也试过从 Base 上调用;

DB_Base.metadata.bind = engine
DB_Base.metadata.reflect()
DB_Base.metadata.drop_all()

但都没有成功,我还是在黑暗中摸索。

这个错误提到的 MetaData 实例到底指的是哪个?我该如何完全重置我的数据库代码的状态?

编辑

好的,我找到了问题所在。我正在尝试动态生成 ORM 表。我在研究优化程序,并把设计空间变量存储在自己的表中,每个可能的变量值一行。

导致错误的最小示例;

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))

def generate_variable_table_class(name):
    """This is a helper function which dynamically creates a new ORM enabled class
    The table will hold the individual values of each variable
    Individual values are stored as a string
    """

    class NewTable( Base ):
        __tablename__ = "vector_{}".format(name)
        id = Column(Integer, primary_key=True)
        value = Column(String(16), nullable=False, unique=True)
        def __init__(self,value):
            self.value = str(value)

        def __str__(self):
            return self.value

        def __repr__(self):
            return self.value    


    NewTable.__name__ = "vector_ORM_{}".format(name)

    return NewTable


if __name__ == "__main__":

    for name in 'asfd', 'jkl', 'xyz':
        print("For loop: ",name)
        engine = create_engine(r'sqlite:///c:\testdelete\{}.sql'.format(name))
        Base.metadata.create_all(engine)
        Session = sessionmaker(bind=engine)
        session = Session()

        bunch_o_foos = [Foo(name = i) for i in range(10)]
        session.add_all(bunch_o_foos)
        session.commit()
        for foo in bunch_o_foos:
            print(foo.id)


        variables = [generate_variable_table_class(i) for i in range(10)]

这实际上和这个问题是一样的; SQLAlchemy 中的动态 Python 类定义。这真的不可能吗?

1 个回答

4

这是一个简单的例子:

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))

for name in 'asfd', 'jkl', 'xyz':
    engine = create_engine('sqlite:///{}'.format(name))
    Base.metadata.create_all(engine)

运行得很好。drop_all 的意思是删除所有表,这可能不是你想要的。而且一旦你使用了 bind,你就把元数据对象绑定到了那个特定的引擎上。不过:

Base.metadata.bind = engine
Base.metadata.create_all()

在这个简单的例子中也可以正常工作。

编辑

根据这个示例,你遇到错误是因为你试图用相同的 Base 子类定义一个同名的类(比如说 vector_0),而这个 Base 子类已经有一个 MetaData 对象,这个对象每个表名只能有一个。

  • 在你新的简单案例中,每个数据库的表没有区别,所以你应该把 generate_variable_table_class 的调用移出主循环,只调用一次。

  • 如果你有每个数据库特有的行为,你可以每次使用一个新的 Base(也就是说,把 Foo 也放到一个函数里!)(另外,generate_variable_table_class 的调用应该放在 create_all 之前,而不是最后)

  • 即便如此,sqlalchemy 也不喜欢它们都叫 NewTable。声明式 ORM 会评估你的类定义,所以它在你设置 __name__ 之前就看到了 NewTable。一个解决方案是不要使用声明式系统(可以查看文档中的“经典映射”)。

  • 但另一种方法是扩展声明式元类来处理名称变化。declarative_base 函数接受一个明确的 metaclass 参数,所以这似乎在框架的规范之内。要使用下面的那个,你需要在 NewTable 定义中设置 __name__ = "vector_ORM_{}".format(name)。如果你想更干净一点,也可以更新 __qualname__,不过 sqlalchemy 在任何地方都没有使用它。

from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta

class NamingDeclarativeMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        if '__name__' in cls.__dict__:
            cls.__name__ = classname = cls.__dict__['__name__']
        DeclarativeMeta.__init__(cls, classname, bases, dict_)

Base = declarative_base(metaclass=NamingDeclarativeMeta)

撰写回答