BlueHarp
BlueHarp

Reputation: 513

How to avoid hard-coding database path using flask, SQLAlchemy, and declarative_base

Flask-SQLAlchemy suggests the Declarative pattern for setting up a database connection. The example hard-codes a single database path, and it seems like I should avoid that, so that I can configure different databases for unit testing, local development, and then eventually production.

this is their database.py:

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                     autoflush=False,
                                     bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()

def init_db():
    # import all modules here that might define models so that
    # they will be registered properly on the metadata.  Otherwise
    # you will have to import them first before calling init_db()
    import yourapplication.models
    Base.metadata.create_all(bind=engine)

But I haven't been able to figure out how to avoid hard-coding this db path.

Environment variables:

I could just replace the sqlite:///.... path with an environment variable. But that would require some way to have pytest run with one set of environment vars and flask run use a different set. I'd much rather have my unit tests keep track of their own temporary database.[2] I'd also like create_app() that creates the Flask app to take a database path that's a config variable.

Import database.py within an application context

I thought I could replace the database path with current_app.config['DB'], and import the database within with app.app_context(). But the Model classes need to import Base, and I can't subclass Base within the application context. So when the models get loaded, they're loaded outside the application context and that throws runtime errors.

So what's the right way to use declarative_base in a flask app with unit tests?

Thanks for your help!

[2]: If that's what I should do, then I can live with it. But it seems messy to be using env vars there, when I'm configuring a flask app with its own config vars too.

Upvotes: 2

Views: 1461

Answers (1)

BlueHarp
BlueHarp

Reputation: 513

I have resolved my question. Instead of using the declarative_base class factory:

database.py:

engine="someengine"
db_session=[make a session here]
Base = declarative_base()
Base.query = db_session.query

And having my models import Base,

I simply followed the pattern here. In database.py, I create a dumb db with

db = SQLAlchemy()

Then in init.py, in the create_app app factory, I can initialize the app with app config variables and initialize the db with the app.*

db.init_app(app)
with app.app_context():
    import site.models
    db.create_all()

Models inherit from database.py's db.Model class.

from site.database import db
class MyModel(db.Model)

I can access the database in pytest tests by wrapping tests in a fixture:

@pytest.fixture
def app():
    app = create_app({[testing config]}
    with app.app_context():
        g.db = db
        yield app
        g.db.session.remove()
        g.db.drop_all()

This is working for me now. That said, if you think I'm doing something wrong, let me know!

*I think the SQLAlchemy object, once initialized with init_app, stores pointers to values that only exist when there's an active application context? Is that right? That's why just calling db doesn't connect to the apps database, but calling db within the app context does.

Upvotes: 2

Related Questions