Skip to content

Understanding this boilerplate

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

This page explains the execution flow whenever you start the 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. 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__". To understand that, look at this.

But if you want to work in groups and your app is bigger than a couple endpoints...

Now, obviously, we wouldn't want to put everything in one file, since you 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 (what this boilerplate was intended for), 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 a module, which I name api. Because the database model logic and endpoint logic can be separated as well, let's 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, meaning that 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.

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. Let's dive a little deeper into flask blueprints and the database connection.

Database

As mentioned everywhere, 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

db = SQLAlchemy()

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

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.

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 and for the official documentation on code structure for large applications, look here