A slightly better minimal Flask app

To me, Flask is the undisputed king of Python web frameworks. Sure there is Django, but Django is huge by default and it has solved almost all problems you might encounter during web development so ridiculously well that you barely get to code at all and you’re left with plugging one module into another. (In case you’re curious: On the other end of the spectrum is Bottle, a web framework so small, it is distributed in a single file.)

A really good joke

Right there in the golden middle, between having to reinvent the wheel and being forced into rigid architecture there is Flask, a framework that originated as an April fools joke, does the heavy lifting for you, yet doesn’t force itself onto you. Pythonistas sometimes like to say that while Django forces you to write Django, Flask let’s you write Python.

It is a save bet that any open source developer software that has risen to the popularity levels of Flask has done so in part due to extensive, accurate and well-maintained documentation and in that regard, Flask is a shining example.

Variants of a minimal application

If you are just starting out with web development in Python and decided to give Flask a shot, you will find a ton of well written tutorials that aim to explain to you a version of a Flask minimal application like this one:

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def hello_world():
    return "Hello world!"

app.run(debug=True, port=8000)

A variant of this is also found in the docs alongside explanations of what this code actually does. This tutorial aims to provide you with a more elaborate version of a minimal Flask app, that allows you to better play around, experiment with more core Flask principles and enables a smoother transition from “I’m just fooling around with this one” to “I want to host this on a public server”.

From here on out, I assume that you know what the above code does and have done the most basic things like using the render_template() method and passing variables to templates. If you don’t, please head over to the relevant parts of the docs or read/watch any of the excellent beginners tutorials on Flask development out there. Then, continue here.

What this might fix

For me, there are three issues with the path this minimal app sets beginners on as they add routes, functionality, templates and static files to their app.

  1. As their application grows, they will discover Flasks ingenious Blueprint object and everything it has to offer way too late.
  2. They will come to realize that deploying their application to a production-like environment can be a hassle and they have to rethink part of how their application fits together.
  3. They will subsequently (hopefully) learn about application factories, but at a time, when it can be a struggle to implement them.

So let me propose a different (sort of) minimal Flask app, that introduces beginners to the framework in a slightly more complex, but satisfyingly more scalable way. Even if your first Flask app is unlikely to amass millions of users, it’s nice to know that Flask could potentially handle it. Frankly, instead of “A slightly better minimal Flask app” you might just say it’s a boilerplate project with a tutorial attached, but that’s just not as catchy.

For the remainder of this tutorial I will be using Pipenv, since on the one hand it is a prerequisite for deploying to Heroku anyways and on the other hand I have yet to encounter a downside to using it. If you’re interested in the details of why and how, I recommend this as further reading.

I’m going to omit detailed explanations about setting up a project here, since I assume you know the drill (and/or have your own), but in case you got your wires crossed: pipenv install flask; pipenv shell.

Entry

Here is the first step of what I am proposing: Make your app a module rather than a single file and create an entry file in the root directory.

. 
├── app 
|   └── __init__.py 
├── entry.py 
├── Pipfile           # this is automatically generated by Pipenv 
└── Pipfile.lock      # this too

You can put the code from the minimal application above in the app/__init__.py file, but let’s delete the last line app.run() and instead specify the FLASK_APP environment variable using export FLASK_APP=entry in the terminal. (On Windows, whenever you want to set an environment variable, you use set instead of export, I won’t be mentioning this again explicitly going forward).

In the entry.py file, we are going to import our app in a single line.

from app import app as application

This has multiple benefits. First, we can run the application as we are used to by using the Flask CLI with the convenient flask run command and secondly, we have added the foundation for an additional configuration layer, that will prove handy for separating development from production and for keeping secrets.

It is worth noting that the application namespace plays a special role here, since the WSGI server that we are going to be using (gunicorn) is specifically looking for it (If you’re on Windows, waitress is a viable alternative as far as I know, but I have no experience with it. Hopefully you can do the same things shown here by using waitress whenever I use gunicorn. Fingers crossed).

When starting the development server with flask run, you will see something like this:

Flask warning you to use a WSGI in production

Actually, since we were jsut talking about this, let’s try it right now. In your terminal: pipenv install gunicorn then gunicorn entry. BOOM. Your Flask app is now running on a WSGI server, just like Flask recommends for production. Easy enough, but we might have jumped the gun on this a little, since we have no practical use for this as of right now. Still, it’s solid progress to have this part of our app working already.

Blueprinting

Adding routes to Flask is fairly straightforward, however, as an app grows in size, having them all in a single file might render your code hard to read, understand and maintain. This is why it is convenient to move groups of routes into their own modules. Let’s create a child module to our app called main, since it will hold our main routes. (At this stage, I’m also sneaking in the static folder, which is part of any reasonable Flask app, but won’t be discussed in this text.)

.
├── app
|   ├── __init__.py
|   ├── static
|   ├── main
|   |   └── __init__.py
|   └── templates
|       ├── about.jinja 
|       └── index.jinja
├── entry.py
├── Pipfile         # this is automatically generated by Pipenv 
└── Pipfile.lock    # this too

So what’s going on inside this newly created main module? Well, we are creating a Blueprint for our main routes. Imagine a Blueprint like a way of grouping routes by logical proximity. For example, if you app holds user accounts and each of these accounts have a profile and a settings page, you probably want to group those two routes (along with more that might be necessary at a later point) in a Blueprint.

From a coding perspective, Blueprints mimic app instances a fair bit: First, we import the Blueprint class and intantiate it by passing it a name and the location of the Blueprint. Then we register our routes on the newly created Blueprint object (instead of app):

from flask import Blueprint, render_template

main = Blueprint('main', __name__)

@main.route('/')
def index():
    return render_template('index.jinja')

@main.route('/about')
def about():
    return render_template('about.jinja')

Two notes on the templates: 1) The contents do not matter as long as they are valid HTML, just make them distinguishable, so you know if the app is returning the right response. 2) I’m using the .jinja file extension purely because my code editor won’t otherwise know to do the Jinja syntax-highlighting. The Jinja2 templating engine actually couldn’t care less about the file extensions.

In the app/__init__.py file, let’s delete the routes and register the blueprint instead. The file then concisely reads:

from flask import Flask
from .main import main as main_blueprint

app = Flask(__name__)

app.register_blueprint(main_blueprint)

Let’s try it out. Fire up a development server with flask run and see if your index.jinja file is correctly rendered. Check if the same is true for the /about route. All good? Awesome!

Note that the register_blueprint() method accepts an optional url_prefix argument, so in case of our hypthetical user profile and settings pages from earlier, we could do something like  app.register_blueprint(user_blueprint, url_prefix="/user") which will result in neat URI’s like /user/profile/konstantin for example.

This makes sense and keeps everything nice and tidy. Let’s take look at extensions:

Two ways of initializing an extension

You may or may not have registered extensions when working with Flask. In most beginner tutorials, you will learn to initialize the extension by passing the app object to the imported class, so assuming you have imported Flask-Bootstrap, you would do something like bootstrap = Bootstrap(app), roughly speaking.

This means though, that at the time of initializing the extension, the app instance must have already been created. This – as we will shortly see – can stymie more complex ways to initialize your app.

Therefore, most Flask extensions offer a second way of initializing, namely with their init_app() method. Let’s try this in our app, using Flask-Misaka.

Flask-Misaka is an extension that allows you to use markdown in your templates. Granted, chances you will want to use markdown in your Flask app are probably relatively slim, but it gives us a nice test case.

Don’t forget to install via Pipenv. Then, in our app/__init__.py file we can do:

from flask import Flask
from .main import main as main_blueprint
from flask_misaka import Misaka

md = Misaka()              # creating the empty object

app = Flask(__name__)      # creating the app instance

md.init_app(app)           # initializing the extension with the app

app.register_blueprint(main_blueprint)

At this point, this may seem like an unnecessary complication to implement, but the benefits will soon become clear to you.

For now, you can try out if the extension actually works by rendering markdown (Flask-Misaka exposes the “markdown”-filter for use in templates). For this, I added a third route /markdown to the main module which renders a markdown.jinja template. This template contains:

{% filter markdown %}
  # I'm using *markdown* with Flask!
  Pretty **rad** if you ask me. I can even do `Code formatting`.
{% endfilter %}

and renders

Rendered result of markdown template.

The extension works. Mission accomplished. Let’s see why taking the extra step via init_app() is a good thing to do:

Dynamically configure the app

There is a special dictionary. It’s app.config. You can print it to the console to see what it contains. I guess it’s obvious why it’s special: It contains Flask’s configuration variables, but not only that, many extensions also rely on it.

Configuring your app can be a daunting task, there are many concepts that you can apply (here are just some of them), so we will concentrate on the relatively simple idea that is behind all of them: Having a development configuration running on your local machine and a production configuration on a public server, safely and neatly organized. (We will omit a testing/staging config.)

Owning a factory

You see, instead of just creating the app in our app/__init__.py file, we can instead write a function that creates the app. A function like this is known as an application factory and has special powers in Flask. The idea is to use conditional logic inside the application factory to create our app instance. Moving on from what you see in this tutorial, you will probably want to use your own configuration files, abstract if-statements to dictionaries and so on, but just to display the idea, here is a look at the new app/__init__.pyfile, where the app is created dynamically, depending on a setting argument passed to it.

import os

from flask import Flask
from .main import main as main_blueprint
from flask_misaka import Misaka

md = Misaka()


def create_app(setting="default"):
    app = Flask(__name__)
    if setting == "default" or setting == "development":
        app.config["DEBUG"] = True
        app.config["NUCLEAR_CODES"] = "insecure string"
    elif setting == "production":
        app.config["DEBUG"] = False
        app.config["NUCLEAR_CODES"] = os.environ.get("NUCLEAR_CODES")
    else:
        print("No valid configuration.")
        return False

    md.init_app(app)
    app.register_blueprint(main_blueprint)

    return app

This file binds a couple of talking points of this text together:

  1. Notice how the app is only created inside the function, so at the time we are calling initialize Misaka(), the app does not yet exist. Therefore, we have to initialize it emptily, before we are able to pass it the app instance inside the function.
  2. The app is dynamically initialized. I have added a made up config variable named NUCLEAR_CODES to display use of any secret keys you might need for your app. Sometimes it’s okay to use dummy values in development like I am doing here, but actual secrets should never be in your files, always in environment variables, which is why the production configuration gets it from there.
  3. The way we have organized the app is particularly neat, because it leverages the power of how Flask was built, let me show you how:

Prevent yourself from making mistakes

We should only run the flask run command in development, since we want to use a WSGI server in production. So can configure the the flask run command to call the create_app() application factory with the development configuration using export FLASK_APP="entry:create_app('development')". So when we update our entry.py file to

from app import create_app

application = create_app("production")

then Line 3 will run with little effect, but at the end of the script, Flask will start the development server with our create_app() function, using our the configuration we are passing to it (development in this case, as we have specified above). For more on how you can configure the Flask CLI to your advantage, look here.

Gunicorn on the other hand does not care about a function called create_app(), but is rather looking for – as you may remember from way up there – an app instance called application. It will find one line 3 and then start a server with our production configuration.

This means that running flask run for development will automatically give us the development environment and running gunicorn entry will automatically give us our production environment. Neat!

Subsequently it should be really hard to accidentally run your development configuration on a live server, since most reasonable ways of deploying a Flask app allow you to use gunicorn, while your development environment can still be conveniently spawned with flask run.

Summary

If you followed along with the tutorial, what you have right now is a great starting point from which you can start exploring more concepts of Flask, extensions and ideas of your own.

We have

  1. Started to organize routes modularly using Blueprints
  2. Integrated a more configurable way to initialize a Flask app using application factories
  3. Set our app up to be easily deployed using gunicorn while still maintaining a development environment thanks to dynamically configuring one app for different environments.

If you want to check out the finished project, I have created this repository.

Now you

Please do not stop here, go ahead and build awesome web apps, play around, figure out new things. Just as an idea, a big topic you could tackle next is integrating a database in your app. For this let me point you to Flask-SQLAlchemy or, if you are a contrarian and don’t like to follow the crowd, you can give Peewee a shot.

You can also try and deploy the app to Heroku, the repository contains the suited Procfile for this purpose.

If you find out something new, please write a tutorial yourself and let me know, so I can learn something new too.

I want to explicitly mention Miguel Grinberg and recommend you buy his book on Flask web development, which I have read and reread if you really want to dig deep.