Reputation: 513
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.
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.
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.
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
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