Flask Biography Tutorial Part XIII - Structuring Project to Support Larger Flask Application

 

Splitting main.py module/file into several *.py files/modules

 

If you have followed this Flask series until this article, you will know that up until now our Flask article still lived inside a single file: main.py. Is it a bad practice? Perhaps. But it really is fun. It is a proof that a web application can be contained within one single Python module, which is a realization of the initial April Fools Joke that drive the creation of our beloved Flask web framework: it's possible to create such, but certainly not advisable.

Having said that, Flask still don't impose any strict directory organization or modules separation. Compare this with Django, where it come up with a monolithinc ready to use project directory that you must adhere, in Flask world, you can freely to choose to follow a simple organization of your Flask application into package as this official guide shows you, or turning your Flask application into a highly modular application using Blueprints. In this article we are going to implement a much more simplified version of Flask project directory organization : splitting main.py module into several *.py files/modules that wrap logically related class/code into its own module. The idea is simple: e.g., if you want to add a new form, you don't have to scroll through the code that talks about models. In the next subsequent article, we will then modularize our application using Blueprints.

Let's get started!

 

Revisiting WSGI

Although this Flask Tutorial series talk specifically of using Openshift as the deployment platform, but as I have intention to discuss another PaaS solution (such as Heroku, Google Cloud, etc), I would like to give a wider view of the process of restructuring our Flask application.

In a framework that implement the standard WSGI specification, your application will going to have a single Python file that act as your application startup file (a good practical hands-on with WSGI can be found in Flask's author page here). If you are using another PaaS solution beside Openshift, try to find this file. Whereas in Openshift's Python application, this startup file is reside in wsgi/application. From the standard wsgi/application provided by Openshift, I have modified this file into this:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python
import os
 
virtenv = os.environ['OPENSHIFT_PYTHON_DIR'] + '/virtenv/'
os.environ['PYTHON_EGG_CACHE'] = os.path.join(virtenv, 'lib/python2.7/site-packages')
virtualenv = os.path.join(virtenv, 'bin/activate_this.py')
try:
    execfile(virtualenv, dict(__file__=virtualenv))
except IOError:
    pass
 
from main import *
dbinit()

From the PEP 0333 that define what WSGI is, we have this definition of what constitute a WSGI application : "The application object is simply a callable object that accepts two arguments. The term "object" should not be misconstrued as requiring an actual object instance: a function, method, class, or instance with a __call__ method are all acceptable for use as an application object". Therefore, in our current single file main.py, we instantiate our Flask object with the name application, making our application as a fully qualified WSGI application: runnable by uWSGI server being used by Openshift to serve our Python application.

PS: Another example of WSGI server is gunicorn, wsgiref, waitress, etc. There is also Openshift's custom gear of using them beside the default one uWSGi server provided by Openshift.

Have a look at the above wsgi/application. After activating Virtual Environment used by our application, we then import main.py file, making this application available with the following line in main.py:

1
application = Flask(__name__)

Therefore, the core work of re-organization of our Flask Biography application should be done in this main.py file.

Revamping main.py

Have a look at our current main.py file. At the current state, we throw all kinds of classes, objects and functions there. But however it intertwined, we still able to recognize these several kind of objects:

Those are perfect example of objects that need to be separated into its own *.py file and later must be imported into a new main.py to compose a valid Flask application object as in the previous main.py file. Using the above objects separation's list complete with its line of code part, we will be able to easily refactor it into a new *.py file/module. Below are the result of the above objects separation into several Python modules:

  • main.py: this module will now have a simple role of:
    Remember, that the code after  if __name__ == '__main__': that will run the builtin WSGI server, will only get executed if this module was called from command line using the command: python main.py. As our Flask application already being handled by Openshift uWSGI server, this main.py module will only get imported by wsgi/application.
  • config.py: as you can see from the main.py file, Flask class is having a config object that can load its properties from a class with from_object() method. This will make us easier in storing configurations into separate python modules. If you want to store sensitive information there, you can later put this config.py file into .gitignore file making this code will never get published into central git repository. Or, in my current approach, I utilize system environment variable to store sensitive information (e.g. connection string to database, complete with its username and password).
  • models.py: this is where all of our Flask models were stored. Please inspect that this module is not imported from within main.py, instead it is imported from within views.py, that is because we use our application models intensively when we respond to user requests.
  • forms.py: contains all of our WTForms classes, thas was also imported from within views.py.
  • views.py: the core part of this application, which consist of all route/views method that will respond to users request.

As long as you know how to import Python modules, the above task is rather trivial.

But our job is not done yet. Have a look at our views.py. At the current state, that file is still a mess. It still is a storage of all routes/views method without any organization or modularization whatsoever. The following section will talk about how to modularize this views module using Flask-Classy. Instead of using regular functions as your routes/views method, finally you can use an OOP approach to manage your routes/views method. This new level of modularization is the important thing from Flask-Classy that gives Flask web development a better way of handling application complexity.

Let see how we restructure our views module using Flask-Classy.

Restructuring Views Modules Using Flask-Classy

A quote from Flask-Classy homepage: "I ❤ Flask. Like a lot. But sometimes projects get a little big and I need some way of managing and organizing all the different pieces. I know what you’re saying: “But what about Blueprints?” You’re right. Blueprints are pretty awesome. But I found that they aren’t always enough to encapsulate a specific context the way I need. What I wanted, no what I needed was to be able to group my views into relevant classes each with their own context and behavior. It’s also made testing really nifty too".

The underlined part of the quote is a neat definition of what Flask Classy is.

First of all, lets inspect what our views module looks like. As I am using PyCharm, I can easily inspect the current structure of views module by pressing CTRL+F12 to popup the file structure window, as seen below:

A flat structure of views module

A flat structure of views module

The structure given by the picture above is a flat structure: all items were on the same level. No grouping or hierarchical structure imposed. Although all the function were using pretty names that make us realize its logical grouping, there is no actual grouping there. We just got lucky that Jetbrans PyCharm display our method in alphabetical order. 

For the sake of our example, lets take all the function that serve as routes/views method for editing users biography, and restructure them using Flask-Classy. I am sure you have already know it, but for clarification, I will just pasted all of those functions --in stripped form to conserve space-- below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@application.route('/user_edit_biography', methods=['POST'])
def user_edit_biography():
    pass
 
@application.route('/user_edit_fullname', methods=['POST'])
def user_edit_fullname():
    pass
 
@application.route('/user_edit_tagline', methods=[ 'POST'])
def user_edit_tagline():
    pass
 
@application.route('/user_upload_avatar', methods=['POST'])
def user_upload_avatar():
    pass

Observe that for each method we specifically set its route name, e.g we set route for URL /user_edit_fullname to be handled by  function user_edit_fullname().  Although it's intentionally for the route name and the function name to use the same name, you are free to use different name though.

In order to introduce a new class using Flask-Classy for the above methods, you will need to first install Flask-Classy package into your Python environment using the command pip install Flask-Classy==0.6.3. And if you do use Openshift, don't forget to add this requirement into setup.py. Having all prepared, lets start creating this class below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BiographyView(FlaskView):
    @route('edit_biography', methods=['POST'])
    def edit_biography(self):
        pass
 
    @route('edit_fullname', methods=['POST'])
    def edit_fullname(self):
        pass
 
    @route('edit_tagline', methods=['POST'])
    def edit_tagline(self):
        pass
 
    @route('upload_avatar', methods=['POST'])
    def upload_avatar(self):
        pass
 
 
BiographyView.register(application)

We create BiographyView class which is a new class derived from Flask-Classy FlaskView as our class parent, that will serve as class for all methods that dealt with editing users Biography. For each member method, we decorate it with Flask-Classy route decorator to set its route name and method type. Actually, if your method type is GET, you can omitted the route decorator. But, as we do need to set our method to use POST method type, we have to specifically use route decorator, that needs the first argument to be the route path of this method.

Finally, we register this new view class into our one and only Flask application instance. This will make request having our defined route will be handled by our designated class member method. The default behaviour of Flask-Classy, is to create the URL with the format /<first_phrase_of_class_name_in_lower_case>/<full_method_name_in_lower_case>. As I am okay with that behaviour, I don't need to specifically set route prefix or route_base to a custom value. Hence, we will have a new route named : /biography/edit_biography, /biography/edit_fullname, /biography/edit_tagline and /biography/upload_avatar.

The last modification that we need is to change all url_for() or manual hand coded URL in the existing template from the previous form into this new form. For example, if in the previous base_layout.html we use url_for('signin'), now after we have create an AuthView class to group all authentication related request, we will have to change it into url_for('AuthView:signin'). As for manual hand coded URL changes, you can see the change from this into this.

If you inspect the structure of the views modules using PyCharm, you will have image as depicted below:

Structure of views module after using Flask-Classy to manage its views complexity

Structure of views module after using Flask-Classy to manage its views complexity

Although there are still dangling functions there, where some are needed by Flask-Login to remain that way, now we already have a nice structure of views module: you can clearly see that our modules already handle Authentication, Biography, Portfolio and Settings. The future task of adding new routes/views will become easier and more maintainable after we introduce Flask Classy to our project.

Whats Next?

The intention of this part of the series is to prepare our project for a bigger application scale. The previous single module/file approach is not suited for a large scale application. After splitting the main module, we then restructure views module even more by organizing all of routes/views method into classes using Flask-Classy. This makes the code more maintainable.

There is another approach of making our Flask project more modular: using Blueprints. I will revisit this application by implementing Blueprints to make a modular application development approach to our existing Flask application. Among other things, using Blueprints will make our application easier to be enhance as a web API.

As always you can download the current state of the application here : bio-part-13.zip.

Or follow its public Github repository here : pythonthusiast/bio.

Stay tune for my next article!

 




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