I'm trying to create a "modular application" in Flask using Blueprints.
When creating models, however, I'm running into the problem of having to reference the app in order to get the db
-object provided by Flask-SQLAlchemy. I'd like to be able to use some blueprints with more than one app (similar to how Django apps can be used), so this is not a good solution.*
db
instance, which the app then imports together with the rest of the blueprint. But then, any other blueprint wishing to create models need to import from that blueprint instead of the app.My questions are thus:
Edit: I've been thinking about this myself now, and this might be more related to SQLAlchemy than Flask because you have to have the
declarative_base()
when declaring models. And that's got to come from somewhere, anyway!Perhaps the best solution is to have your project's schema defined in one place and spread it around, like Ruby on Rails does. Declarative SQLAlchemy class definitions are really more like schema.rb than Django's models.py. I imagine this would also make it easier to use migrations (from alembic or sqlalchemy-migrate).
I was asked to provide an example, so let's do something simple: Say I have a blueprint describing "flatpages" -- simple, "static" content stored in the database. It uses a table with just shortname (for URLs), a title and a body. This is simple_pages/__init__.py
:
from flask import Blueprint, render_template from .models import Page flat_pages = Blueprint('flat_pages', __name__, template_folder='templates') @flat_pages.route('/<page>') def show(page): page_object = Page.query.filter_by(name=page).first() return render_template('pages/{}.html'.format(page), page=page_object)
Then, it would be nice to let this blueprint define its own model (this in simple_page/models.py
):
# TODO Somehow get ahold of a `db` instance without referencing the app # I might get used in! class Page(db.Model): name = db.Column(db.String(255), primary_key=True) title = db.Column(db.String(255)) content = db.Column(db.String(255)) def __init__(self, name, title, content): self.name = name self.title = title self.content = content
This question is related to:
And various others, but all replies seem to rely on import the app's db
instance, or doing the reverse. The "Large app how to" wiki page also uses the "import your app in your blueprint" pattern.
* Since the official documentation shows how to create routes, views, templates and assets in a Blueprint without caring about what app it's "in", I've assumed that Blueprints should, in general, be reusable across apps. However, this modularity doesn't seem that useful without also having independent models.
Since Blueprints can be hooked into an app more than once, it might simply be the wrong approach to have models in Blueprints?
One of the most sought after helpers being the handling of a database connection across the app. However, ensuring your database connection session is available throughout your app can be accomplished with base SQLAlchemy and does not require Flask-SQLAlchemy.
Step 1 - Install the Flask-SQLAlchemy extension. Step 2 - You need to import the SQLAlchemy class from this module. Step 3 - Now create a Flask application object and set the URI for the database to use. Step 4 - then use the application object as a parameter to create an object of class SQLAlchemy.
To use any Flask Blueprint, you have to import it and then register it in the application using register_blueprint() .
Flask-SQLAlchemy Query Returns the Database Class and the Data.
I believe the truest answer is that modular blueprints shouldn't concern themselves directly with data access, but instead rely on the application providing a compatible implementation.
So given your example blueprint.
from flask import current_app, Blueprint, render_template flat_pages = Blueprint('flat_pages', __name__, template_folder='templates') @flat_pages.record def record(state): db = state.app.config.get("flat_pages.db") if db is None: raise Exception("This blueprint expects you to provide " "database access through flat_pages.db") @flat_pages.route('/<page>') def show(page): db = current_app.config["flat_pages.db"] page_object = db.find_page_by_name(page) return render_template('pages/{}.html'.format(page), page=page_object)
From this, there is nothing preventing you from providing a default implementation.
def setup_default_flat_pages_db(db): class Page(db.Model): name = db.Column(db.String(255), primary_key=True) title = db.Column(db.String(255)) content = db.Column(db.String(255)) def __init__(self, name, title, content): self.name = name self.title = title self.content = content class FlatPagesDBO(object): def find_page_by_name(self, name): return Page.query.filter_by(name=name).first() return FlatPagesDBO()
And in your configuration.
app.config["flat_pages.db"] = setup_default_flat_pages_db(db)
The above could be made cleaner by not relying in direct inheritance from db.Model and instead just use a vanilla declarative_base from sqlalchemy, but this should represent the gist of it.
I have similar needs of making Blueprints completely modular and having no reference to the App. I came up with a possibly clean solution but I'm not sure how correct it is and what its limitations are.
The idea is to create a separate db
object (db = SQLAlchemy()
) inside the blueprint and call the init_app()
and create_all()
methods from where the root app is created.
Here's some sample code to show how the project is structured: The app is called jobs
and the blueprint is called status
and it is stored inside the blueprints folder.
blueprints.status.models.py
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() # <--- The db object belonging to the blueprint class Status(db.Model): __tablename__ = 'status' id = db.Column(db.Integer, primary_key=True) job_id = db.Column(db.Integer) status = db.Column(db.String(120))
models.py
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() # <--- The db object belonging to the root app class Job(db.Model): __tablename__ = 'job' id = db.Column(db.Integer, primary_key=True) state = db.Column(db.String(120)
factory.py
from .blueprints.status.models import db as status_db # blueprint db from .blueprints.status.routes import status_handler # blueprint handler from .models import db as root_db # root db from flask import Flask def create_app(): app = Flask(__name__) # Create database resources. app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////path/to/app.db' root_db.init_app(app) status_db.init_app(app) # <--- Init blueprint db object. with app.app_context(): root_db.create_all() status_db.create_all() # <--- Create blueprint db. # Register blueprint routes. app.register_blueprint(status_handler, url_prefix="/status") return app
I tested it with gunicorn
with gevent
worker and it works. I asked a separate question about the robustness of the solution here: Create one SQLAlchemy instance per blueprint and call create_all multiple times
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With