15

I'm having a hard time to make my application run. Flask-SQLAlchemy extension creates an empty database whenever I try to separate module in packages. To better explain what I'm doing, let me show how my project is structured:

Project
|
|-- Model
|   |-- __init__.py
|   |-- User.py
|
|-- Server
|   |-- __init__.py
|
|-- API
|   |-- __init__.py

The idea is simple: I want to create a package for my model, as I don't like spreading code in a single package, and separate "sub" projects (like API), as in the future I will be using blueprints to better isolate sub apps.

The code is very simple:

First, the Model.__init__.py:

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

Note that I created this only to use a single SQLAlchemy() object accross the package. No we go to Model.User

from Model import db

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

Once again note the from Model import db that I used to allow the same db object.

Finally, the Server.__init__.py goes like this:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import Model, API
db = Model.db


def main():
    app = Flask("__main__")
    db = SQLAlchemy(app)
    db.create_all()
    API.SetAPIHookers(app)
    app.run(host="0.0.0.0", port=5000, debug=True)

if __name__ == "__main__":
    main()

From my point of view, the db = SQLAlchemy(app) allows me to pass my app object without creating a circular reference.

The problem is that whenever I run this code, the sqlite database file is created empty. That made me think that maybe Python don't import things like I thought it would. So I tested my theory by removing the import Model and creating the user directly inside Server... and voilá, it worked!

Now comes my question: Is there a 'pythonic' way to correctly separate modules like I want or should I leave everything in the same package?

2
  • 2
    Many years later the sentence "Python doesn't import things like I thought it would" is still a problem. It seems that it still exists a HUGE gap between the way Python is taught on blogs, videos, and even courses, and the Python in an actual work environment. My advice for the beginners: don't stick to the simple examples that show only how the basic functionalities work; always go a step further and check how they are used in the real world. But always check the balance between rewards and costs. Commented Jan 20, 2021 at 8:34
  • @AlmirCampos Can you provide a good example or a good guide explaining how to manage python imports and structure a project properly? Commented Jun 6, 2022 at 19:49

2 Answers 2

30

Right now, you have set up your application using what is a rough equivalent to the "Application Factory" pattern (so called by the Flask documentation). This is a Flask idea, not a Python one. It has some advantages, but it also means that you need to do things such as initialize your SQLAlchemy object using the init_app method rather than the SQLAlchemy constructor. There is nothing "wrong" with doing it this way, but it means that you need to run methods like create_all() within an application context, which currently you would not be if you tried to run it in the main() method.

There are a few ways you can resolve this, but it's up to you to determine which one you want (there is no right answer):

Don't use the Application Factory pattern

In this way, you don't create the app in a function. Instead, you put it somewhere (like in project/__init__.py). Your project/__init__.py file can import the models package, while the models package can import app from project. This is a circular reference, but that's okay as long as the app object is created in the project package first before model tries to import app from package. See the Flask docs on Larger Application Patterns for an example where you can split your package into multiple packages, yet still have these other packages be able to use the app object by using circular references. The docs even say:

Every Python programmer hates them, and yet we just added some: circular imports. [...] Be advised that this is a bad idea in general but here it is actually fine.

If you do this, then you can change your Models/__init__.py file to build the SQLAlchemy object with a reference to the app in the constructor. In that way, you can use create_all() and drop_all() methods of the SQLAlchemy object, as described in the documentation for Flask-SQLAlchemy.

Keep how you have it now, but build in a request_context()

If you continue with what you have now (creating your app in a function), then you will need to build the SQLAlchemy object in the Models package without using the app object as part of the constructor (as you've done). In your main method, change the...

db = SQLAlchemy(app)

...to a...

db.init_app(app)

Then, you would need to move the create_all() method into a function inside of the application context. A common way to do this for something this early in the project would be to utilize the before_first_request() decorator....

app = Flask(...)

@app.before_first_request
def initialize_database():
    db.create_all()

The "initialize_database" method is run before the first request is handled by Flask. You could also do this at any point by using the app_context() method:

app = Flask(...)
with app.app_context():
    # This should work because we are in an app context.
    db.create_all()

Realize that if you are going to continue using the Application Factory pattern, you should really understand how the application context works; it can be confusing at first but necessary to realize what errors like "application not registered on db instance and no application bound to current context" mean.

Sign up to request clarification or add additional context in comments.

7 Comments

Actually it won't. The db = SQLAlchemy(app) is my way to reset the db variable with the correct app context. If I remove this line I get the following error: RuntimeError: application not registered on db instance and no application bound to current context.
About the uppercase, you are right! I will fix this. My C++/C# background is too strong in me.
No luck. Still getting the "application not registered on db instance and no application bound to current context" error. Seems to me that once the db variable is instanciated, the new app context is not bounded.
Amazing answer! After reading everything you wrote and linked, I was able to figure out that I only needed a simple with app.test_request_context(): before calling my Model.db.create_all(). Also I rewrote it to better suit a multi app scenario. Thanks!!
After googling for an hour, finally THE answer!
|
5

Your problem is this line:

db = SQLAlchemy(app)

it should be this:

db.init_app(app)

By running SQLAlchemy app again, you're reassigning db to the newly created db obj.

Please try NOT to move away from the application factory setup. It removes import time side effects and is a GOOD thing. In fact, you may want to import db inside your factory because importing a model that subclasses the Base (db.model in this case) has side effects of its own (tho less of an issue).

Initializing your app in a __init__.py means that when you importing anything from your package for use, you're going to end up bootstrapping your app, even if you don't need it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.