Reputation: 71
I have an issue where I need to remap a class, after the corresponding table in the database is altered (columns may be added or removed).
Minimal example:
from sqlalchemy import MetaData, Column, Integer, Table, ForeignKey
from sqlalchemy.orm import mapper
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
Base.metadata = MetaData()
class TableOne(Base):
__tablename__ = "tableone"
id = Column(Integer, primary_key=True)
class DynamicTable(object):
pass
def get_dynamic_table(columns):
return Table(
"dynamictable",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("tableone_id", ForeignKey("tableone.id")),
*[Column(name, type) for name, type in columns],
)
config = {}
config["dynamic_columns"] = [("foo", Integer)]
initial_table = get_dynamic_table(config["dynamic_columns"])
mapper(DynamicTable, initial_table)
# Run a lot of queries involving e.g. session.query(getattr(DynamicTable, config[dynamic_columns][0]))
# Change config
config["dynamic_columns"] = [("bar", Integer)]
# .. rebuild dynamictable in backend with new config ...
# Now, DynamicTable doesn't reflect the database table
Base.metadata.remove(initial_table) # Removes the table from metadata, but not the mapper..
updated_table = get_dynamic_table(config["dynamic_columns"])
mapper(DynamicTable, updated_table) # Raises sqlalchemy.exc.ArgumentError
The error message in full reads
Traceback (most recent call last):
File "test.py", line 45, in <module>
mapper(DynamicTable, updated_table)
File "<string>", line 2, in mapper
File "/dist/ella-python/lib/python3.7/site-packages/sqlalchemy/orm/mapper.py", line 694, in __init__
self._configure_class_instrumentation()
File "/dist/ella-python/lib/python3.7/site-packages/sqlalchemy/orm/mapper.py", line 1220, in _configure_class_instrumentation
self.class_)
sqlalchemy.exc.ArgumentError: Class '<class '__main__.DynamicTable'>' already has a primary mapper defined. Use non_primary=True to create a non primary Mapper. clear_mappers() will remove *all* current mappers from all classes.
Is there any way to re-map this (or potentially all) tables?
Upvotes: 3
Views: 2055
Reputation: 71
There is an undocumented method on the Mapper
-class, dispose()
. This is used in clear_mappers
, which has some strict warnings about usage, but the following seems to work:
from sqlalchemy.orm.mapper import class_mapper
existing_mapper = class_mapper(DynamicTable)
existing_mapper.dispose()
Base.metadata.remove(initial_table) # Removes the table from metadata, but not the mapper..
updated_table = get_dynamic_table(config["dynamic_columns"])
mapper(DynamicTable, updated_table) # Is now re-mapped
Upvotes: 3