Flask Biography Tutorial Part XIV - Redesign The Application using Blueprints

 
Blueprints Modularized Version of Biography Application

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:

Splitting main.py code into several *.py files

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:

Application's modules

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)

Now this 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:

Structure of an application features by means of Python packages

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 __init__.py:

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:

1
application.register_blueprint(bio)

This complete the overall tasks needed to construct a valid Flask blueprints object.

Exploring Another Blueprint: mod_auth

I will finish the discussion of blueprints object by exploring another blueprint example in this application: mod_auth package. 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 bioapp/mod_auth/__init__.py:
 

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?

After 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:

1
AuthView.register(application)

into this:

1
AuthView.register(mod_auth)

That is, the Flask-Classy view is now registered to a blueprint, instead of to a  Flask application object.

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 AuthView:signin to 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  url_for('auth.AuthView:signin').

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. Smile

Conclusion

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!




Leave comments

authimage

Copyright(c) 2014 - PythonBlogs.com
By using this website, you signify your acceptance of Terms and Conditions and Privacy Policy
All rights reserved