Creating Flask-SQLAlchemy instance with metadata from Blueprint

Nick K9

TL;DR: How do I use a metadata object from a Blueprint to create the Flask-SQLAlchemy instance? The only place I can see to provide the declarative base metadata object is in the initial SQLAlchemy() call. But when I import it from the Blueprint in my extensions.py file, the Blueprint's code needs the db object and the loading fails due to a circular import.


I have several model classes which I would like to use both in and outside of Flask. I am using the declarative method to do this, and my app is set up to use the App Factory model and Blueprints. The way the models get registered with SQLAlchemy is through the use of the metadata argument when the db object is created. In the context of my application, it makes sense to declare the metadata object in a Blueprint, instead of in the main app Blueprint. (This is where most of the code which references it will be, including the non-Flask utility script used for initially populating the database.) However, importing the model class from the second Blueprint ends up in a circular import.

$ flask db migrate
Error: While importing "my_app", an ImportError was raised:

Traceback (most recent call last):
  File "my_app/venv/lib/python3.7/site-packages/flask/cli.py", line 235, in locate_app
    __import__(module_name)
  File "my_app/my_app.py", line 1, in <module>
    from app import create_app
  File "my_app/app/__init__.py", line 7, in <module>
    from app.extensions import *
  File "my_app/app/extensions.py", line 10, in <module>
    from turf.models import metadata
  File "my_app/turf/__init__.py", line 1, in <module>
    from .routes import bp
  File "my_app/turf/routes.py", line 14, in <module>
    from app.extensions import db
ImportError: cannot import name 'db' from 'app.extensions' (my_app/app/extensions.py)

As mentioned in this general question on circular imports for Blueprints, a solution which works is to import the db object from inside each function in the second Blueprint, thus side-stepping the import during initialization of the extensions.py file. But in addition to being annoying, this just feels extremely hacky.

Ideally, I would be able to pass the metadata object I've created to SQLAlchemy's init_app() method. That would resolve this issue at a stroke. Unfortunately, init_app() doesn't take a metadata argument. Is there some other way to register metadata with an SQLAlchemy instance after initialization? Or have I missed some other key element of the declarative model approach?

I should say that the non-Flask portion of this is working just fine. My utility script is able to import the models and use them to add objects to the database. It's only the Flask imports which are giving me trouble.

Here is the hierarchy:

.
├── app
│   ├── __init__.py
│   └── extensions.py
└── turf
    ├── __init__.py
    ├── models.py
    └── routes.py

And the relevant code which fails due to circular import:

app/__init__.py:

from app.extensions import *

def create_app():
    app = Flask(__name__)

    with app.app_context():
        import turf
        app.register_blueprint(turf.bp)

        db.init_app(app)

app/extensions.py:

from turf.models import metadata

db = SQLAlchemy(metadata=metadata)

turf/__init__.py:

from .routes import bp

turf/models.py:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import MetaData

metadata = MetaData()
Base = declarative_base(metadata=metadata)

# All the turf models are declared in this file
class Boundary(Base):
    # ...etc...

turf/routes.py:

from .models import *
from app.extensions import db

bp = Blueprint('Turf', __name__, url_prefix='/turf')

@bp.route('/')
def index():
    return render_template('turf/index.html')
Nick K9

It turns out you can declare the MetaData object in the extensions.py file and then import that in the Blueprint. I was sure this would fail, because the metadata object is now being populated after the db object is created, but I've verified that the models are indeed available and work as expected. And no more circular dependency. I've actually broken this part out into its own file to allow Blueprint code to import as little as possible.

app/base.py:

from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base

metadata = MetaData()
Base = declarative_base(metadata=metadata)

app/extensions.py:

from flask_sqlalchemy import SQLAlchemy
from .base import metadata

db = SQLAlchemy(metadata=metadata)

turf/models.py:

from app.base import Base

# All the turf models are declared in this file
class Boundary(Base):
    # ...etc...

This also answers another question I had with my original approach: how would it work if I had a second Blueprint which also had model objects which needed to be available from non-Flask code? Now, I have created a single Base object and can use that to implement new classes in different Blueprints as needed.

One minor annoyance with this approach, though. In the non-Flask DB population script, I was originally able to use from models import * to refer to the sibling module (file) containing the models. This let me call the script directly, à la cd turf; python populate_db.py --arg. That no longer works because the models.py file now references a different package, app.extensions. So instead I have to use this workaround:

turf/populate_db.py:

try:
    from .models import *
except:
    print("You have to run this from outside the 'turf' directory like so: $ python -m turf.populate_db [...]")
    sys.exit(1)

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Java

flask - blueprint - sqlalchemy - cannot import name 'db' into moles file

From Dev

Calling method from Flask Blueprint

From Java

flask blueprint template folder

From Java

Using Flask-SQLAlchemy in Blueprint models without reference to the app

From Java

Using Flask-SQLAlchemy in Blueprint models without reference to the app

From Dev

Flask and SQLAlchemy, application not registered on instance

From Dev

Creating database with SQLAlchemy in Flask

From Dev

Flask: Template in Blueprint Inherit from Template in App?

From Dev

AWS Retrieving Security Credentials from Instance Metadata

From Dev

Flask Session not persistant in blueprint

From Dev

Creating a database in flask sqlalchemy

From Dev

Overwrite route in flask blueprint

From Dev

Flask Blueprint 404

From Dev

Understanding MetaData() from SQLAlchemy in Python

From Dev

Flask-SQLAlchemy creating schema before creating tables

From Dev

Creating a list field from metadata in Hakyll

From Dev

Flask SQLAlchemy Association Table: error creating backref

From Dev

Creating tables in Flask using SQLAlchemy does not work

From Dev

Importing from main app in a flask blueprint

From Dev

Creating a basic Table with Flask-SQLAlchemy

From Dev

alembic autogenerate get the metadata for a Flask-SQLAlchemy bind

From Dev

Getting flask-restful routes from within a blueprint

From Dev

Flask SQLAlchemy query from heirarchy

From Dev

Flask Blueprint structure

From Dev

Creating a primary key in sqlalchemy when creating a table instance

From Dev

Flask - Forms - Blueprint

From Dev

Running a Flask blueprint from the program

From Dev

Flask blueprint and registering path

From Dev

Inject dependencies in Flask blueprint

Related Related

HotTag

Archive