I need to create many similar databases in different locations in a for-loop. In the beginning of the loop, I create the engine to a new path_sql_db
on disk.
engine = sa.create_engine("sqlite:///{}".format(path_sql_db), echo=0, listeners=[util_sa.ForeignKeysListener()])
Session = sa.orm.sessionmaker(bind=engine)
session = Session()
Then I have my Tables in several modules inherit from DB_Base which was defined in an external module;
from sqlalchemy.ext.declarative import declarative_base
DB_Base = declarative_base()
The problem is that during my next iteration of the for-loop, I can't create my tables since they still exist somewhere?
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.
I've tried to MetaData.drop_all() from the engine;
meta = sa.MetaData(bind = engine)
meta.reflect()
meta.drop_all()
session.close()
And also from the Base;
DB_Base.metadata.bind = engine
DB_Base.metadata.reflect()
DB_Base.metadata.drop_all()
With no success, I'm still just flailing around in the dark here.
Which MetaData instance is the error referring to? How can I completely reset the state of my database code?
EDIT
Ok I tracked down the problem. I'm trying to dynamically generate ORM tables. I am studying optimization routines and storing design space variables in their own tables, one row per possible value of the variable.
Minimal example causing error;
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)]
Which is actually the same as this question; Dynamic Python Class Definition in SQLAlchemy. Is this not possible?
As a minimal example:
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)
Runs fine. drop_all
means drop tables, probably not what you want. And once you use bind
you're tying the metadata object to that specific engine. Although:
Base.metadata.bind = engine
Base.metadata.create_all()
Also works in the minimal example.
EDIT
Based on the sample case, you're getting the error because you're attempting to define a class with the same table name (e.g. vector_0) using the same Base
subclass, which has a MetaData
object attached to it, which can only have one of each table name.
In your new simple case, there is no difference between the tables per database, so you should move the calls to generate_variable_table_class
out of your main loop and only make them once.
If you have per-database behavior, you could use a new Base
each time (i.e. move Foo
into a function too!) (Also, the calls to generate_variable_table_class
should be above create_all
not at the end)
Even then, sqlalchemy doesn't like that they are all named NewTable
. The declarative ORM evaluates your class definitions, so it sees NewTable
before you set __name__
afterward. A solution would be to not use the declarative system (see "Classical Mappings" in the docs).
But another way would be to extend the declarative metaclass to handle name-changing. The declarative_base
function takes an explicit metaclass
argument so this seems within the spec of the framework. To use the one below, you would set __name__ = "vector_ORM_{}".format(name)
inside the NewTable
definition. If you want to be really clean update __qualname__
too, though sqlalchemy doesn't use it anywhere.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With