Skip to content

Understanding this boilerplate

Timothy Ko edited this page Oct 31, 2018 · 15 revisions

The goal of this page is to explain the execution flow whenever you start the app.

Basic app

Let's first look at the most basic flask app

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<h1>Hello World!</h1>"

if __name__ == "__main__":
     app.run()

Pretty simple code... When you run python <file name>, you start the flask server because it sets up app and executes app.run(). Remember that whenever you run a python file, it runs top to bottom, and it will execute whatever is under if __name__ == "__main__".

Now, obviously, we wouldn't want to put everything in one file...

You always want your code to flow well and be readable based on the logic and dependencies of your application. For many applications, especially building out REST APIs with Postgres, we want to split out the models( which describe our interaction with the database tables), as well as splitting out the endpoints (based on what the endpoints are written for (authentication, different resources?, etc.)).

Thus, like many other projects, flask-boilerplate puts the entire flask application in one folder, which I name api. And, for the reasons mentioned above, we should also split up the database model abstractions and the endpoints into models and views folders.

Python's import system

So, how do we move the same executions as shown by the initial code and move it over to this new structure?

First, we must understand python's import system. In python, whenever a folder has __init__.py, it is a package. Whenever you import the package, the code in __init__.py will be executed. Whenever you import a file, you will also execute whatever is in that file. Remember that python is an interpreted language, and if you provide any code instructions in the file, it will execute. Obviously, if you define functions or classes, they won't execute... but if you have a print statement outside of any defined function/class, it will execute once -- when you import it the first time in the scope of your application's execution.

So, say you had a module mypackage:

package/
    __init__.py
    app.py

When you do from mymodule import app the first time, it will execute whatever is in package/__init__.py and put the variables, functions, etc into its namespace. If you import other modules (files) in __init__.py, they will also be imported and executed. Thus, whatever instructions are in package/app.py will be executed.

In the case of flask-boilerplate

In this boilerplate, we have

api/
    __init__.py
    models/
        __init__.py
    views/
        __init__.py

Inside api/__init__.py, we see the function create_app. This function creates the app, the same way the simple code above did it, and sets up logging, flask blueprints, database connections, as well as the application configuration (app.config). We have different application configurations, which is chosen through the environment variable FLASK_ENV and taken from the file api/config.py, which describes different types of configurations. Most of the setup is pretty straightforward, but I'd love to discuss flask blueprints and the database connection a little more.

Database

As mentioned everywhere in this documentation, we use SQLAlchemy to interact with postgres. It wraps SQL tables as Python objects so we easily and safely work with the database, instead of injecting SQL commands. Flask-SQLALchemy simplifies the connection process by allowing us to connect to postgres with the command

db = SQLAlchemy()

However, you must be inside the flask application context, which keeps track of application-level data (including the app config). This is because Flask-SQLAlchemy looks into the app config and finds SQLALCHEMY_DATABASE_URI, which should hold the url to the database and tries to connect to it. This is why we don't import api.models outside of create_app, since the line db = SQLALchemy() is called whenever you import api.models. We import it api.models the flask app is instantiated.

For tests, we setup a temporary postgres database, get it's URL and then set SQLALCHEMY_DATABASE_URI in the test app config to be that url... and the process happens over again, but with the flask test client

Blueprints extends the flask app object itself, which we use in the modules inside views/, such as api/views/main.py. We must tell the flask app instance that we have this blueprint and it's logic, which is through the app.register_blueprint function.

Conclusion

To wrap it up, whenever you run python manage.py runserver you would create the app (look at manage.py), which would instantiate the flask app, setup logging, connect to the database, etc, and then run it (look at the function runserver in manage.py. It's very similar to the last line of the simple code we have above).

For more on python code structure, look here. For the official documentation on code structure for large applications, look here