A very basic app factory pattern for flask projects

I am planning to do a series of posts where we will progressively build a demo ecommerce website using Flask, SQLAlchemy and React. The boilerplate repositories prepared for these posts might serve as useful starter kits for others. Plus it will help the reader get a feel of building a production grade website from scratch.

These posts will be based on some of my learnings obtained from building an ecommerce website for the past 6 years. Since I mostly worked on backend apis, devops and analytics — my posts will be focused more on those areas. But I will also try to post a little on frontend tools as well, though my experience there is comparatively limited.

In the first post of the series, we are just going to create a bare minimum flask app in the app factory pattern. The objective is just to build a skeleton architecture which would be able to accomodate the various types of services we will experiment with in the future posts.

Pre-requisites

It is assumed that the reader has a basic working knowledge of Flask and SQLAlchemy. If not, please read through Flask Quickstart and Flask-SQLAlchemy Quickstart once. These examples use the simple pattern where a flask application is instantiated and routes registered — all in a single module. While this is suitable for very small applications (in terms of lines of code), for most applications we will need a more modular and scaleable architecture when deploying to production. Flask provides various tools to accomplish this. We are primarily interested in Blueprints and the app factory pattern. You should read the official docs explaining blueprints and app factories as well once.

Checking out the boilerplate repo

I am planning to write the accompanying examples for all these posts as boilerplates — which can serve as useful starting points for your own apps. The code for the boilerplate app for this particular post is available here. Please clone it before you start reading this post. Or at least keep the github link open to follow along.

Structure of the app

The folder structure looks like this

I will cover testing in a separate post. So the above structure does not include tests directory. Also note that the instance folder mentioned above, won't actually be available when you clone the repo. It is included in .gitignore to keep it out of version control. It is meant to store the non-shareable configuration details of the app (like passwords, secret keys etc)

Here is the .gitignore used

*.pyc
__pycache__/
instance/

Before we start reviewing the code, let’s review the other setup steps required — like installing the packages, setting up the db etc.

Setting up mysql

For this project we are going to use mysql as the database. You can refer to various tutorials available online for the same.

For example, you can use this tutorial to setup a mysql server on an ubuntu machine, and this tutorial for creating a database and granting access to a particular user. Note the information like db username, password and the database name. You will need that later when creating the config file for the app.

Setting up the instance config file

Create a folder named instance inside the cloned repo. As mentioned earlier, this will remain outside version control. Then create a file named application.cfg.py like this

DB_USERNAME = 'admin'
DB_PASSWORD = 'adminpasswd'
DB_SERVER = 'localhost'
DB_NAME = 'flask_boilerplate'
SECRET_KEY = "bf23423e58727fcdfdsgsagg681d9ewrrewr234346"
SECURITY_PASSWORD_SALT = "40113095-16f5-4e85-8140-98234802938320498"

The secret key and the password salt used above are just random strings. I use the python function `uuid.uuid4()` to generate these strings

The instance folder serves multiple purposes

  1. Being out of version control, it can safely contain secret keys
  2. It can also be used to store the variables which need to change between different installations of the app. We will change all the above keys when the app is deployed to production. So a different instance/application.cfg.py will be used when we deploy.

Setting up the virtualenv and installing the requirements

When working with python apps, it is always preferable to install the packages inside a virtual environment.

We can use virtualenvwrapper to set up the python environment for running the app. Create a virtualenv like this

mkvirtualenv -p python3 flask_boilerplate

And with the virtualenv active, install the requirements using pip install -r requirements.txt. Here is the requirements.txt for this project

flask
sqlalchemy
flask_sqlalchemy
flask_script
flask_migrate
flask_security
mysqlclient
bcrypt
ipython

Refer the doc pages for virtualenv and virtualenvwrapper for more details if you are new to this.

With the environment set, we can start reviewing the server code.

The main script — server.py

The application server can be started by calling python server.py. Here is the code

That’s all. The app_factory.create_app function is the only entry point for our application. The entire architecture of the application can be understood by working backwards from here. Note that the application object created here, will also be used by us later when we run the app using uwsgi and serve it behind a nginx proxy. But more on that in the next post.

The app_factory module

This is the heart of the application. It is placed in app/app_factory.py

This function will be invoked not just by server.py, but by any number of other scripts which need an application instance to work with. As we can see in the above example, the create_app function creates a flask app instance, sets some config variables on it, registers some blueprints, initializes some services and returns the ready to use app.

The flag instance_relative_config set to True in the first line allows us to read the application's config information from inside the instance folder. We initialize the config with the variables defined in app/default_config.py. This can include all the values which can be version committed. For secret keys, I prefer to initialize them to empty strings here, so that the app won't break even if the keys are absent in instance/application.cfg.py

The next line app.config.from_pyfile can load the python file with the specified name present in the instance folder.

Now with all the database credentials loaded from the config file, we then initialize the db handler, register the blueprints and initialize the Flask-Security extension which is used to provide support for user registration and login.

The model layer

For the purpose of this example, I have only defined the models required by FlaskSecurity for user authentication. Here is models/user.py

This pattern is straightaway taken from the Flask-Security quickstart example. This library requires a Role model also to be specified for specifiying various roles for users like administrator, editor etc. We are not using roles in this example though. The user_datastore created here is then used in the create_app method when registering the Security module.

The views

As mentioned earlier, we are using the blueprints pattern to modularize the urls. In our app, we are classifying the urls into 2 categories — pages and api — defined in views/main_pages_bp.py and views/main_api_bp.py respectively. The former is supposed to render html responses while the latter will render json responses.

Here is views/main_pages_bp.py

from flask import Blueprint, render_templatemain_pages_bp = Blueprint('main_pages_bp', __name__)@main_pages_bp.route("/")
def index():
return render_template("index.html")
@main_pages_bp.route("/about")
def about():
return render_template("about.html")

And here is views/main_api_bp.py

from flask import Blueprint, jsonifymain_api_bp = Blueprint('main_api_bp', __name__)@main_api_bp.route("/")
def index():
return jsonify({
"status": "success"
})

Templates

The template files specified in the render_template method used in the views need to be present in the templates folder. We can create a standard layout which can be extended by each page

templates/layout.html

The various concepts used in the above template — like import, block, macros, include etc are explained in detail in the Jinja2 documentation The include statements are basically partial templates which can be defined in separate files and loaded in other templates. The blocks are sections which can be overridden by any child template. Macros are similar to functions. The various partial templates defined in app/templates/layout_sections can be explored directly in the repository code. In macros.html, some utility functions have been defined like this

Now the template for home page — index.html is defined as follows

Thus we have an example of how to inherit from a layout and override specific blocks.

Static folder

In flask, the folder named as static is assumed to be the static folder meant to store and serve assets by default. The name of the folder can be customized if required, but there is no reason to do so usually. So here we have app/static folder with js and css files in it. These files were included in the templates using the macros.static_js_tag and macros.static_css_tag as shown above.

I have just placed some alert messages in the static code — just to illustrate that those files have been loaded. In further tutorials we will further explore how to bundle these static assets together while deploying to production.

The migration script — dbmanager.py

Now that we have reviewed the contents of the app folder, we are ready to run the application. But we need to create the database tables prior to that. While smaller flask-sqlalchemy projects would have just used the db.create_all method defined on the FlaskSQLAlchemy library for this, for a larger project we need a more scaleable method.

Typically, for large website projects, we need a database migration tool to manage the schema changes. The database schema will keep changing through the lifetime of the project — with constant addition, modification and deletion of various tables and constraints. Web frameworks handle this by using db migration tools — which have a chronological versioning mechanism. These tools will generate migration scripts, with each migration script having a reference to its immediate predecessor. Thus at any point in future, anyone who checks out the project, can just run the migrations scripts in order and get the database to the current schema. This is a very crucial requirement when multiple developers are working on the project.

In our case, we are going to use a library called flask_migrate to generate and manage the migrations. This is based on the alembic library which is the standardly used migration framework for SQLAlchemy.

The migration script looks like this

You can consider this as boilerplate code. We are creating a Manager instance using flask script - which is basically an interface for generating command line utilities for flask. We are then registering our app in the manager. You can see how the app_factory pattern was useful here. It let us generate an instance of the app for the migration tool to introspect the registered models. We then add a command called db which uses flask_migrate. The above code automatically ensures that the following commands are readily available

python dbmanager.py db init - To generate the migrations folder for the first time

python dbmanager.py db migrate - To generate the migration scripts whenever we make any changes in our models

python dbmanager.py db upgrade - To apply the generated migrations in the chronological order so that the database reaches the correct schema state

python dbmanager.py db downgrade - To downgrade the database schema to a previous version - thus rolling back the changes.

The migrations folder is auto-generated by running python dbmanager.py db init. Whether to version commit this folder or not is a choice for the developer to make. I prefer version committing it, so that others can get started by running just python dbmanager.py db upgrade

So let’s just run python dbmanager.py db upgrade. You will be able to see a message showing that the migration was applied successfully.

Running the server

Now that everything is ready, the development server can be initialized by running python server.py

You can register an user account by clicking on the “Register” link and signing up. Then you can login using those credentials. The login and register pages don’t have any styling as they use the default html templates defined by the flask_security library. In the next posts, we can see how to customize these templates to use our layout.

Using cli.py

The last remaining script left to review is cli.py

This is equivalent to the management shell offered by frameworks like Django. I have kept it very simple for the sake of this tutorial. But we can add a lot more features here as we will see in subsequent posts

To understand the reasoning behind second and third lines, you need to understand the concept of application contexts and request contexts in flask and how and why a request context needs to be pushed in the shell as explained here

But these are pretty advanced topics. So for the sake of this tutorial, just assume it as boilerplate code.

We can use this tool by calling the script like this and fiddling around with various objects like this

(myvenv) surya@devbox:~/personal/flask_pattern_minimal_with_auth$ python cli.pyIn [1]: models.User.query.count()Out[1]: 2In [2]: models.User.query.all()Out[2]: [user1@gmail.com, user2@example.com]

Conclusion

In the first post of this series, we have built a very basic flask app using app_factory pattern. It does nothing except to let you register an user account and login. And it lays out the the skeleton of various concepts like templates, static folders etc which will be further explored in the subsequent posts.

Originally published at https://techonometrics.com on April 15, 2020.

Entrepreneur, Full stack web developer, Product Manager. Dabbling with data science now. Interested in Economics, Politics and History.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store