Figure 1 - Blueprints Modularized version of Biography Application
In a retrospect, the intention that drive me in finishing this Flask Biography Application is because I would like to start a series that extensively talk about Python Cloud Computing. As I deploy the application in PythonAnywhere, I realize that the application structure is far from complete (I dare not to say perfect..). Back at that time, I throw all application's classes, object, method, configuration, etc, into a single Python file (or let's call it Python module. It's much more Pythonic that way). For the sake of simplicity, it's cool actually. But things will become seriously ill, if the application begins to grows.
In the previous article, we have done an initial work of restructuring the single application module into several modules and using Flask-Classy to better manage our routes/views method. But the restructuring work is not yet finished: you can't clearly see any separation of concerns (a.k.a application features) toward the existing application code. And that's where Flask Blueprints nicely fits into our discussion.Taken from Armin own writing, "Blueprints can greatly simplify how large applications work and provide a central means for Flask extensions to register operations on applications. A Blueprint object works similarly to a Flask application object, but it is not actually an application. Rather it is a blueprint of how to construct or extend an application". At the moment we won't talk about Flask Extension, instead we will obey Armin's word, that Flask Blueprints object, is blueprint (sorry for the blueprintception) to construct or extend an application. Meaning, to construct a large Flask application, one must use Flask Blueprint object in doing so. Coupled with the proper use of Python package and module, you will be able to construct a large Flask application in a much effective way.
Great, what are we waiting for? Let's go deeper!
Why do we need Blueprints?
I preface this article with screenshot of the result of applying Blueprints modularization technique into our Biography Application. It was taken from PyCharm's project view. My quick question regarding that screenshot is, "What are the application features?". Can you named all of them? I bet you can! Just with a quick glimpse you can easily spot all of the application modules: auth, portfolio, biography and settings. Try to answer the same question with our previous application structure seen below:
Figure 2 - Try to guess what are the application's features here?
Not a clue. Nothing. Nada.
Yes, we can see that the application at least consisted of config, forms, models and views/route/controller, .. but that's not what we meant by application's features. You simply can't answer it using the above application structure.
And that's why we need Blueprints.
Revisiting Python Modules and Packages
Before we start digging into the wonderful world of Blueprints, let's step back for a moment for an important concept in Python software development: Modules and Packages. In the previous article we have formally introduced to Python modules: any Python
*.py file is a valid Python module. But what are package? What constitute a valid Python package?
A valid Python package is a directory containing a
__init__.py file with may also completed with related Python modules. If it's only a directory with buncha Python modules, without
__init__.py, then it's not a Python package. PyCharm denoted Python package by folder icon marked with small blue dot inside it (). This
__init__.py file is truely awesome. You can think this file as a constructor for a package. When this package is imported, Python will executed the content of this file. A perfect place to instantiate any object and prepare any plumbing code needed. And you may have already guessed: this is where we instantiate a Blueprints object for a given application's feature.
Redesign The Application with Blueprints in Mind
I would love to guide you directly on how to instantiate a Blueprints object. But, as I experience it myself, it won't give you a good grasp on the good intention that Blueprints provided. For a start, let see what features existed in our Biography Application:
Figure 3 - Application's features
At the current state, it only consisted of four features.
The next question is : "How do you think you can bring this application features into Python world?" Package or modules? If it's not clear to you, try to decide it for a moment.
The above four application's features will be brought into Python as Python packages, which then will consisted of several Python modules.
Implementing Application Features as Blueprint
Let's start with
wsgi/main.py that will run our application, with code seen below:
1 2 3 4
from bioapp import application if __name__ == '__main__': application.run(host="0.0.0.0", port=8888)
wsgi/main.py is having a very simple role: import Flask
application object from
bioapp package and when
wsgi/main.py were loaded from command line, use it to run WSGI server. Let's have a look at our
bioap package, along with its Authentication package (named as
mod_auth) unfolded below:
Figure 4 - Structure of an application features by means of Python packages
Try to compare Figure 4 above with Figure 2, and you may begin to realize that we simply take all objects related to Authentication features from all Python modules in Figure 2, and put it into its own Python modules inside a new package
bioapp.mod_auth (yes, Python package can be nested). If you wondering why we need a new package named
bioapp to host all of our packages, this is because we need to share the same Flask
Application object, SQLAlchemy
db object and
Config. Storing all of them in a root package will solve this dependency.
When the statement
from bioapp import application were encountered, Python will know that
bioapp is a valid Python package, due to the existence of the file
__init__.py in directory
bioapp. Python will then execute this file, making all the objects instantiated there readily available to use. Below are the content of
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
import os from flask import Blueprint, Flask from flask_sqlalchemy import SQLAlchemy from config import Config application = Flask(__name__, static_folder= os.path.join(os.path.dirname(__file__), "..", "static")) application.config.from_object(Config) db = SQLAlchemy(application) bio = Blueprint('bio', __name__) from views import * from mod_auth import mod_auth from mod_portfolio import mod_portfolio from mod_biography import mod_biography from mod_settings import mod_settings application.register_blueprint(bio) application.register_blueprint(mod_auth) application.register_blueprint(mod_portfolio) application.register_blueprint(mod_biography) application.register_blueprint(mod_settings)
In the above code we register five blueprints: one Blueprint (
bio) was instantiated here, while the fours are coming from another Python package. I use this
bio blueprint object to store objects (views/forms/model etc.) that is not related to any specific application features. Of course you can store this blueprint object into its own package, say
mod_main, but I choose not to do so. Hey, it's Flask! It's your decision! :)
In the above code, we instantiate a blueprint object this way:
bio = Blueprint('bio', __name__). You only have to give two arguments here: the name of the blueprint object and the module where this blueprint was instantiated. Actually, the usual case of blueprints is to associate it with a certain URL prefix, such as
/auth for Blueprints that implement authentication features. But, as we already use Flask-Classy to do just that, we don't need to associate it with certain url prefix.
Later, we import all objects from module
views with code seen below:
1 2 3 4
@bio.route('/') @bio.route('/<username>') def index(username=None): pass
Notice something cool? Instead of registering routes into our Flask
application object as we done previously, we are now registering it into our
bio blueprint object. All application features, are now separated into its own Blueprint object, not directly into Flask
application object. This make the application structure more modular.
Final task to create our blueprints functional, is to register it into Flask
application object with code seen below:
This complete the overall tasks needed to construct a valid Flask blueprints object.
Exploring Another Blueprint: mod_auth
mod_authpackage. This package is the implementation of Authentication features in our application. You can see that it's imported and registered from within
bioapp/__init__.py. Let's have a look at its
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from flask import Blueprint from flask.ext.login import LoginManager login_manager = LoginManager() mod_auth = Blueprint('auth', __name__, template_folder="templates" ) from views import * @mod_auth.record_once def on_load(state): login_manager.init_app(state.app) login_manager.login_view = '/auth/signin' @login_manager.user_loader def load_user(id): return Users.query.get(id)
Inspect that in the instantiation of this blueprint object, we gave the Blueprint constructor another argument:
template_folder. This is because this blueprint have its own templates (have a look at Figure-4 previously). As shown in
bio blueprint object, it's not always a blueprint have its own templates. Blueprint can even have its own static files. But in this application I choose to let blueprints share the same static folders.
Another interesting point to note is, the
record_once decorator. Because in the previous code we know that we have to call
login_manager.init_app() with a valid Flask
application object, we must use this decorator that will be called the first time the blueprint was registered. It make sure that the
login_manager get associated only once with the Flask
application object. Yes, you can multiple register the same blueprints to the Flask
application object,with different URL prefix. Nice, isn't it?
mod_auth blueprint was instantiated, the next step is to import the views for this blueprint. And as we already use Flask-Classy to manage all of our routes/views method, we only have to change its registration statement from this:
That is, the Flask-Classy view is now registered to a blueprint, instead of to a Flask
The Change in URL Map
Do you know that you can inspect all routes defined in our Flask application using this statement in Python REPL:
main.application.url_map. Below are the routes prior introduction of Blueprints:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
>>> import main >>> main.application.url_map Map([<Rule '/biography/edit_biography' (POST, OPTIONS) -> BiographyView:edit_bio graphy>, <Rule '/biography/edit_fullname' (POST, OPTIONS) -> BiographyView:edit_fullname >, <Rule '/biography/upload_avatar' (POST, OPTIONS) -> BiographyView:upload_avatar >, <Rule '/biography/edit_tagline' (POST, OPTIONS) -> BiographyView:edit_tagline>, <Rule '/portfolio/add_update' (POST, OPTIONS) -> PortfolioView:add_update>, <Rule '/auth/signout/' (HEAD, OPTIONS, GET) -> AuthView:signout>, <Rule '/auth/signin' (HEAD, POST, OPTIONS, GET) -> AuthView:signin>, <Rule '/auth/signup' (HEAD, POST, OPTIONS, GET) -> AuthView:signup>, <Rule '/settings/' (HEAD, OPTIONS, GET) -> SettingsView:index>, <Rule '/' (HEAD, OPTIONS, GET) -> index>, <Rule '/portfolio/delete/<id>' (HEAD, OPTIONS, GET) -> PortfolioView:delete>, <Rule '/portfolio/get/<id>' (HEAD, OPTIONS, GET) -> PortfolioView:get>, <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>, <Rule '/<username>' (HEAD, OPTIONS, GET) -> index>]) >>>
And this is the routes after we introduce Blueprints:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
>>> import main >>> main.application.url_map Map([<Rule '/biography/edit_biography' (POST, OPTIONS) -> biography.BiographyVie w:edit_biography>, <Rule '/biography/edit_fullname' (POST, OPTIONS) -> biography.BiographyView:edi t_fullname>, <Rule '/biography/upload_avatar' (POST, OPTIONS) -> biography.BiographyView:upl oad_avatar>, <Rule '/biography/edit_tagline' (POST, OPTIONS) -> biography.BiographyView:edit _tagline>, <Rule '/portfolio/add_update' (POST, OPTIONS) -> portfolio.PortfolioView:add_up date>, <Rule '/auth/signout/' (HEAD, OPTIONS, GET) -> auth.AuthView:signout>, <Rule '/auth/signin' (HEAD, POST, OPTIONS, GET) -> auth.AuthView:signin>, <Rule '/auth/signup' (HEAD, POST, OPTIONS, GET) -> auth.AuthView:signup>, <Rule '/settings/' (HEAD, OPTIONS, GET) -> settings.SettingsView:index>, <Rule '/' (HEAD, OPTIONS, GET) -> bio.index>, <Rule '/portfolio/delete/<id>' (HEAD, OPTIONS, GET) -> portfolio.PortfolioView: delete>, <Rule '/portfolio/get/<id>' (HEAD, OPTIONS, GET) -> portfolio.PortfolioView:get >, <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>, <Rule '/<username>' (HEAD, OPTIONS, GET) -> bio.index>]) >>>
Notice the pattern? All routes name are now prefixed with its blueprint name. For example the route name for
/auth/signin is now changed from
auth.AuthView:signin. This clearly shown that we push down Flask-Classy routes one level t go down under its Blueprints object. Because of this change, in all of our
*.html templates file, for all call to
url_for('AuthView:signin') we must change it to
The Advantage of Using Blueprints
The fact that we can associate certain URL prefix into a blueprint, is not that amazed us here. We have already done this using Flask-Classy. But the truly things that new to use, is the fact that now the application is highly modular. For example, try to create another flask application and follow the way this application structure. If this new Flask application would also like to have an Authentication features, you can simply copy the
mod_auth directory into
<whatever_your_application_root_is>/, and .. voila! You will have the same authentication features as this application has.
But having said that, you can't view blueprint as a pluggable application as it is in Django. It's only a blueprint on how we structured large Flask application. But still, the blueprint can easily use into new application without much effort.
And that's folks, how we properly use Blueprint.
I do hope I have presented you with a clear to understand discussion on Flask Blueprints. Got to say, it's not easy to find a good explanation of how to properly use Blueprints. This part of the series was intended to do just that.
As I have already satisfied with how the application structured, our next article will continue the discussion of another Python Cloud Computing solution, and will try to host this application there.
As always, you can download the application code use in this chapter here: bio-part-14.zip
Or follow its Github repository here : pythonthusiast/bio.
Stay tuned for my next articles!