Application Structure and Blueprint in Flask

Up until now, our entire Flask application mainly resides in a single file main2.py. That’s fine for small applications, but as the project grows it becomes difficult to manage. When we break a monolithic file into multiple files, the code in it becomes more maintainable and predictable.

Flask doesn’t impose any restrictions on how you should structure the application. However, it does provides some guidelines to make the application modular.

In this course, we will use the following application structure.

Here is the rundown of what each file and folder does:

File Description
app_dir The app_dir is the root directory of your Flask project.
app The app directory is a Python package which holds views, templates, and static files.
__init__.py The __init__.py tells the Python that app directory is a Python package.
static The static directory contains the static files of the project.
templates The templates directory contains templates.
views.py The views.py contains routes and view functions.
config.py The config.py contains settings and configuration for your Flask application.
runner.py The entry point for your Flask application.

In the rest of the lesson, we will convert our project to conform to this directory structure. We will start by creating config.py.

Class-Based Configurations

A software project usually runs in three different environments:

  1. Development.
  2. Testing.
  3. Production.

As the project evolves you will encounter a need to specify different configuration options for different environment. You will also notice that no matter which environment you are in some configurations always remains the same. We can implement such a configuration system using classes.

Start off by defining default configurations in the base class and then create environment-specific classes which inherit from the base class. The environment-specific classes can override or add environment-specific configurations.

Create a new file named config.py inside flask_app directory and add the following code in it:

flask_app/config.py

Note that for the first time we are reading values of some configurations from the environment variables. We are also providing default values in case environment variables are not set. This method is particularly useful when you have some sensitive data that you don’t want to hardcode in the application itself.

To read configurations from a class use from_object() method as follows:

Creating Application Package

Create a new directory app directory inside the flask_app directory and move all files and directories to this directory ( except env and migrations directories and our newly created config.py file ). Inside the app directory create __init__.py file with the following code:

flask_app/app/__init__.py

The __init__.py creates the application instance and initializes extensions. If FLASK_ENV environment variable is not set the Flask application will run in the debug mode ( i.e app.debug = True ). To put the application in production mode set the FLASK_ENV environment variable to config.ProductionConfig.

After initializing extensions, the import statement in line 21, imports all the views. This is necessary to connect the application instance to the view functions, otherwise, Flask will not be aware of your view functions.

Rename main2.py file to views.py and update it so that it only contains routes and view functions. Here is the complete code of updated views.py file.

flask_app/app/views.py

The views.py file now only contains view functions. We have moved the code for models, form classes and utility functions to their respective files, as follows:

flask_app/app/models.py

flask_app/app/forms.py

flask_app/app/utils.py

Finally, to launch the application add the following code to runner.py file:

flask_app/runner.py

The runner.py is the entry point of our project. The file starts by creating an instance of Manager() object. It then defines make_shell_context() function. The objects returned by make_shell_context() function will be available inside the shell without explicit import statements. At last, the run() method on the Manager instance is called to start the server.

Import Flow

We have created quite a few files in this lesson and it is very easy to lose track of which file does what and the order in which files are executed. To make things clear this section explains shows how everything works together.

Things start with the execution of runner.py file. The second line of runner.py file imports app and db from the app package. When Python interpreter encounters this line, program control transfers to __init__.py to start its execution. In line 7, __init__.py imports the config module which transfers the program control to config.py. When the execution of config.py completes the program control again comes back to __init__.py. In line 21, __init__.py file imports views module which transfers the program control to views.py. The first line of views.py again imports application instance app from the app package. The application instance app is already in the memory so it will not be imported again. In line 4, 5 and 6, views.py imports models, forms and send_mail function respectively which transfers the program control temporarily to their respective files in turn. When the execution of views.py finishes the program control comes back to __init__.py. This completes the execution of __init__.py. The program control comes back to runner.py and starts the execution of the statement in line 3.

The third line of runner.py imports the classes defined in the models.py module. Since models are already available from views.py, the models.py file will not be executed again.

Since we are running runner.py as the main module, the condition in line 17 evaluates to True and manager.run() starts the application.

Running Project

We are now ready to run our project. In the terminal enter the following command to start the server.

If FLASK_ENV environment variable is not set, the preceding command will start the application in the debug mode. Navigate to http://127.0.0.1:5000/ and you should see the home page which currently looks like this:

Browse the remaining pages of the application to make sure everything is working as expected.

Our application is now very flexible. It can pick up a completely different set of configurations just by reading an environment variable. For example, let’s say we want to put our site in production mode. To do so, simply create an environment variable FLASK_ENV with the value config.ProductionConfig.

In the terminal, enter the following command to create FLASK_ENV environment variable:

This command creates an environment variable in Linux and Mac OS. Window user can use the following command:

Run the application again.

Now our application is running in production mode. At this point, if Python code raises an exception you will see a 500 Internal Server Error instead of a stack trace.

Because we are still in development, we have to delete FLASK_ENV environment variable. The FLASK_ENV will be deleted automatically as soon as you close the terminal. To manually delete it enter the following command:

Window users can use this command:

Our project is in much better shape. Now things are organized in a much more predictable way than it was possible before. The technique devised here is useful for small to medium-sized projects. However, Flask has some more tricks under its sleeve which can help you to become more productive.

Blueprints

Blueprints are yet another way to organize the application. Blueprints provide separation of concerns at the level of views. Just like a Flask application, a Blueprint can have its own views, static files, and templates. We can also map blueprints with their own URIs. For example, let’s say we are working on a blog and its admin panel. A blueprint for a blog would contain view function, templates and static assets specific to the blog only. Whereas the blueprint for admin panel would contain views, static files, and templates specific to the admin panel. Blueprints can be implemented either by using a module or a package.

It’s time to add a blueprint to our project.

Creating Blueprint

Create an directory named main inside the flask_app/app directory and move views.py and forms.py to this directory. Inside the main directory create __init__.py file with the following code:

flask_app/app/main/__init__.py

We are creating blueprint object using the Blueprint class. The Blueprint() constructor takes two arguments, the blueprint name and the name of the package where blueprint is located; for most applications passing __name__ to it will suffice.

By default, views functions in the blueprint will look for templates and static assets in the application’s templates and static directories respectively.

We can change that by specifying the location of templates and static assets while creating Blueprint object as follows:

In this case, Flask will look for templates and static assets in the templates_dir and static_dir directories inside the blueprint package.

The template path added by a blueprint has lower priority than the application’s templates directory. That means if you have two templates of the same name in templates_dir and templates directories, Flask will use the template from the templates directory.

The following are some noteworthy points to remember about the blueprints:

1. When using blueprints the routes are defined using the route decorator of the blueprint object rather than the application instance ( app ).

2. To create URLs when using blueprints you have to prefix the endpoint with the name of the blueprint and a dot (.). This is true whether you are creating URLs in the Python code or in templates. For example:

This will return the URL of the index route of the main blueprint.

The name of the blueprint can be omitted, in case you are in the same blueprint that you want to create URL for. For example:

This will return the URL of the index route of the main blueprint assuming you are in the view function or template of the main blueprint.

To accommodate the changes we have to we have to update import statements, url_for() calls and routes in views.py file. Here is the updated version of views.py file.

flask_app/app/main/views.py

Notice that throughout the views.py file, we are creating URLs without specifying the blueprint name because we are inside the same blueprint for which we are creating URL for.

Also update the url_for() call in admin.html as follows:

flask_app/app/templates/admin.html

The view functions in views.py are now associated with the main blueprint. Next, we have to register the blueprint on the Flask application. Open app/__init__.py and modify it as follows: (changes are highlighted):

flask_app/app/__init__.py

The register_blueprint() method of the application instance is used to register blueprint. We can register multiple blueprints by calling register_blueprint() for each blueprint. Notice that in line 11, we are assigning main.login to the login_manager.login_view. In this case, it is necessary to specify the blueprint name otherwise Flask wouldn’t be able to tell which blueprint you are referring to.

At this point, the application structure should look like this:

The Application Factory

We are already using packages and blueprints in our application. We can further improve our app by delegating the task of instantiating application instance to the Application Factory. Application Factory is simply a function which creates an object.

So what do we get by doing this:

  1. It makes testing easier because we can create application instance with different settings.
  2. We can run multiple instances of the same application in the same process. This is handy when you have load balancers distributing traffic across different servers.

Let’s update app/__init__.py to implement an application factory as follows (changes are highlighted):

flask_app/app/__init__.py

We have delegated the task of creating application instance to the create_app() function. The create_app() function takes a single argument named config and returns an application instance.

Application factory separates the instantiation of the extensions from their configurations. The instantiation occurs before create_app() is called and configuration occurs inside the create_app() function using the init_app() method.

Next, update runner.py to use applicaton factory as follows:

flask_app/runner.py

It is important to note that when using application factories, we no longer have access to the application instance in the blueprint at the import time. To access application inside a blueprint use current_app proxy from the flask package. Let’s update our project to use current_app variable as follows:

flask_app/app/main/views.py

flask_app/app/utils.py

Now you should have a have a solid understanding of Flask, its different components and how everything fits together. Believe it or not, we have explored quite a lot. In the next part of this course, we will use what we have learned to create a Delicious clone named Flask-Marks. Delicious is a social bookmarking site launched in 2003. In 2017, it was acquired by Pinboard and since then it is running in read-only mode. Flip the page and let’s get started.

2 thoughts on “Application Structure and Blueprint in Flask

  1. Hi,

    Thanks for sharing this. can you please explain line 2 in file flask_app/app/main/views.py. How the import works here? And how can I test it tat this line works? Please let me know

Leave a Comment