pythonsqlalchemydynamic-class-creation

How to dynamically create classes inside a module-level initialize() method in Python


I'm writing a library to talk to a database using SQLAlchemy. I really like SQLAlchemy's autoload_with=engine feature, which can be passed to the Table constructor to grab all of the table's columns without the programmer having to define them explicitly.

Here's the basic approach for a table named "something":

Base = declarative_base()
engine = create_engine('mysql://user:pass@host/db_name')
table = Table('something', Base.metadata, autoload_with=engine)

class Something(Base):
    __table__ = table

However, we have multiple versions of our database (on different hosts) so I need my engine to be passed in as a parameter at runtime. I sort of hate the idea of writing something like this in my module, but I'm blanking on a better approach:

Base = declarative_base()
Something = None   # gets defined after initialize() is called

def initialize(engine):
    table = Table('something', Base.metadata, autoload_with=engine)
    class _Something(Base):
        __table__ = table

    global Something
    Something = _Something

And then client code has to do something nasty like this before using any of the SQLAlchemy models:

import custom_db_api

engine = create_engine(...)
custom_db_api.initialize(engine)

Is there a better approach to handling this kind of module-initialization by an outside caller?


Solution

  • Well, you'll have to find some way to pass the engine variable to your custom_db_api module. This might be marginally cleaner...

    Base = declarative_base()
    
    class Something(Base):
        pass
    
    def initialize(engine):
        Something.__table__ = Table('something', Base.metadata, autoload_with=engine)
    

    ...or if you can infer the correct engine initialization parameter from some 'global', like sys.argv, you could use something like this...

    import sys
    
    Base = declarative_base()
    if len(sys.argv) > 1 and sys.argv[1] == '--use-alt-db':
        engine = create_engine('mysql://user:pass@alt_host/db_name')
    else:
        engine = create_engine('mysql://user:pass@main_host/db_name')
    
    table = Table('something', Base.metadata, autoload_with=engine)
    
    class Something(Base):
        __table__ = table
    

    It kinda depends on how you intend to tell the program which DB to use.