Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge multiple declarative bases in SQLAlchemy

Is it possible to merge a _BoundDeclarativeMeta instance with another?

For example:

from sqlalchemy.ext.declarative import declarative_base

Base1 = declarative_base()
Base2 = declarative_base()

# Something like this?
CombinedBase = Base1.merge(Base2)

In reality I would be subclassing orm models from each of them before merging. The problem is that I have an independent package that needs certain tables to work. Yet it is going to be used by another independent package.

like image 459
Tim Martin Avatar asked Apr 29 '15 21:04

Tim Martin


People also ask

What is declarative base Sqlalchemy?

A base class stores a catlog of classes and mapped tables in the Declarative system. This is called as the declarative base class. There will be usually just one instance of this base in a commonly imported module. The declarative_base() function is used to create base class. This function is defined in sqlalchemy.

What is Sqlalchemy ext declarative?

function sqlalchemy.ext.declarative. has_inherited_table(cls) Given a class, return True if any of the classes it inherits from has a mapped table, otherwise return False. This is used in declarative mixins to build attributes that behave differently for the base class vs. a subclass in an inheritance hierarchy.

What is DeclarativeMeta?

DeclarativeMeta is a class within the sqlalchemy. ext. declarative module of the SQLAlchemy project. declarative_base is another callable from the sqlalchemy.


2 Answers

For my use case this worked:

from sqlalchemy import MetaData

combined_meta_data = MetaData()

for declarative_base in [Base1, Base2]:
    for (table_name, table) in declarative_base.metadata.tables.items():
        combined_meta_data._add_table(table_name, table.schema, table)

Or, even better, automatically merge all declarative bases:

import gc
from sqlalchemy import MetaData

combined_meta_data = MetaData()

for declarative_base in ([obj for obj in gc.get_objects() if isinstance(obj, DeclarativeMeta)]):
    for (table_name, table) in declarative_base.metadata.tables.items():
        combined_meta_data._add_table(table_name, table.schema, table)

That meta data can then be used for example to create a diff to the current database:

from sqlalchemy import create_engine
from alembic.migration import MigrationContext
from alembic.autogenerate import compare_metadata
import pprint

engine = create_engine(...)
migration_context = MigrationContext.configure(engine.connect())

diff = compare_metadata(migration_context, combined_meta_data)
pprint.pprint(diff)
like image 186
Martin Loetzsch Avatar answered Oct 16 '22 07:10

Martin Loetzsch


sqlalchemy.Table.to_metadata appears to be a non-internal API that can copy a Table from one metadata object to another.

from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base
    
def merge_metadata(*original_metadata) -> MetaData:
    merged = MetaData()

    for original_metadatum in original_metadata:
        for table in original_metadatum.tables.values():
            table.to_metadata(merged)
    
    return merged

Base1 = declarative_base()
Base2 = declarative_base()
print(merge_metadata(Base1.metadata, Base2.metadata).tables)
like image 38
Ben Avatar answered Oct 16 '22 05:10

Ben