Circular import of db reference using Flask-SQLAlchemy and Blueprints

Flask SqlalchemyFlask

Flask Sqlalchemy Problem Overview


I am using Flask-SQLAlchemy and Blueprints and I cannot help myself from using circular imports. I know I can write imports inside functions and make it work but it sounds nasty, I'd like to confirm with the community if there is a better way to do this.

The problem is I have a module (blueprints.py) where I declare the database and import the blueprints but those blueprints need to import the database declaration at the same time.

This is the code (excerpt of the important parts):

##application.apps.people.views.py from application.blueprints import db

people = Blueprint('people', __name__,
                 template_folder='templates',
                 static_folder='static')

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)

@people.route('/all')
def all():
    users = User.query.all()

##application.blueprints.py from application.apps.people.views import people

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
app.register_blueprint(people, url_prefix='/people')

I have read the documentation and the questions I found on this topic, but I still cannot find the answer I am looking for. I have found this chapter (https://pythonhosted.org/Flask-SQLAlchemy/contexts.html) where it suggest to put the initialization code inside a method but the circular import still persist.

Edit I fixed the problem using the pattern Application Factory

Flask Sqlalchemy Solutions


Solution 1 - Flask Sqlalchemy

I fixed the problem with the help of the Application Factory pattern. I declare the database in a third module and configure it later in the same module in which I start the application.

This results in the following imports:

  • database.py → app.py
  • views.py → app.py
  • database.py → views.py

There is no circular import. It is important to make sure that the application was started and configured before calling database operations.

Here is an example application:

app.py

from database import db
from flask import Flask
import os.path
from views import User
from views import people


def create_app():
    app = Flask(__name__)
    app.config['DEBUG'] = True
    app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:////tmp/test.db"
    db.init_app(app)    
    app.register_blueprint(people, url_prefix='')
    return app 

    
def setup_database(app):
    with app.app_context():
        db.create_all()
	user = User()
	user.username = "Tom"
	db.session.add(user)
	db.session.commit()    


if __name__ == '__main__':
    app = create_app()
    # Because this is just a demonstration we set up the database like this.
    if not os.path.isfile('/tmp/test.db'):
      setup_database(app)
    app.run()

database.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

views.py

from database import db
from flask.blueprints import Blueprint


people = Blueprint('people', __name__,
                 template_folder='templates',
                 static_folder='static')

                 
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)


@people.route('/')
def test():
  user = User.query.filter_by(username="Tom").first()
  return "Test: Username %s " % user.username

Solution 2 - Flask Sqlalchemy

Circular imports in Flask are driving me nuts. From the docs: http://flask.pocoo.org/docs/0.10/patterns/packages/

> ... Be advised that this is a bad idea in general but here it is actually fine.

It is not fine. It is deeply wrong. I also consider putting any code in __init__.py as a bad practice. It makes the application harder to scale. Blueprints is a way to alleviate the problem with circular imports. I think Flask needs more of this.

Solution 3 - Flask Sqlalchemy

I know this has been solved already, but I solved this in a slightly different way and wanted to answer in case it helps others.

Originally, my application code (e.g. my_app.py) had this line:

db = SQLAlchemy(app)

And so in my models.py, I had:

from my_app import db

class MyModel(db.Model):
    # etc

hence the circular references when using MyModel in my_app.

I updated this so that models.py had this:

# models.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()  # note no "app" here, and no import from my_app above

class MyModel(db.Model):
    # etc as before

and then in my_app.py:

# my_app.py

from models import db, MyModel  # importing db is new

# ...

db.init_app(app)  # call init_app here rather than initialising db here

Solution 4 - Flask Sqlalchemy

Serge, bring out definition of models in a separate file called models.py. Register blueprint in __init__.py file of the package.

You've got circular import because blueprint file trying to import people reference from views.py, but in views.py you're trying to import db from blueprints.py. And all of this is done at the top level of the modules.

You can make your project structure like this:

app
  __init__.py  # registering of blueprints and db initialization
  mods
    __init__.py
    people
      __init__.py  # definition of module (blueprint)
      views.py  # from .models import User
      models.py # from app import db

UPD:

For those who are in the tank:

people/__init__.py --> mod = Module('app.mods.people', 'people')

people/views.py --> @mod.route('/page')

app/__init__.py --> from app.mods import people; from app.mods.people import views; app.register_blueprint(people.mod, **options);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionS182View Question on Stackoverflow
Solution 1 - Flask SqlalchemyS182View Answer on Stackoverflow
Solution 2 - Flask SqlalchemydexityView Answer on Stackoverflow
Solution 3 - Flask SqlalchemySamView Answer on Stackoverflow
Solution 4 - Flask SqlalchemyMikeView Answer on Stackoverflow