Rewiring-Flask to work like Django

Ranty Ramone
4 min readDec 13, 2018

I recently started work on a project which was using flask and mongodb. The reasons to use flask by the previous dev were because of how simple/easy it is to get going with flask as compared to the learning curve that comes with Django.

In my opinion while flask may be easy to get going with and have minimal learning required to build the first few endpoints of an API, it can quickly become a mess if you don’t know how to structure web applications.

This is because as compared to Django or other web frameworks flask is very open ended, it leaves the choices up to the developers. Which choices?

a. Choosing an ORM

b. Choosing an template engine (if you need one)

c. Directory structure of your entire project

d. Configurations setup

c. in particular can have dramatic effect on how modular your monolith (i.e. if you are building a monolith) is. To get this right you need to some years of experience, an idea of how you want to structure the project and then an appetite to read through the flask documentation to know how the structure can be achieved.

This makes writing a project in flask relatively difficult for a new developer.

Not all developers are made equal and some avoid or are oblivious to documentation beyond the first few pages. This leads to poorly written monoliths which are hard to maintain, have a lot of redundant code and have a direct impact on time to develop a feature and/or service availability.

Knowing all of this, when the project came about I decided to go through the flask documentation as much as the project deadlines would allow and for things which were of prime concern. The objective was to restructure the project to be structured like a django project. Why django?

I have always liked the django structuring of creating apps (or modules) that pertain to business logic and contain all that is required (models, templates, views, serializers) within themselves as compared to having them in different directories. It makes life extremely easy when dealing with large projects, helps isolate bugs much faster, keeps files thin and manageable.

Disclaimer: I dived into flask only as much as required and by no means can I claim to know it beyond what I needed

So what was needed to get this done:

  1. Configuration of modules (similar to installed apps in django)
  2. Getting urls from those modules and adding them to our app

First I structured the flask project like this:

flask_project
- settings
- users
-- urls.py
-- views.py
-- models.py
-- serializers.py
- orders
- blog
- inventory

It is already starting to look like the structure of a django project. I always prefer class based views as compared to method based views.

However, I wanted to do something extra with urls.py:

a. Each url config/map should have a view class

b. It should be configured to HTTP verbs that are available

c. It should be possible to configure which HTTP verb for that URL requires authentication and which doesn’t

d. In future it should be possible to add data-parsers or any other kind of middleware for each URL or for each URL+Verb combination

For this sort of a config I settled on the following dictionary structure:

from views import SampleView, SampleTwoViewAPI_VIEWS = {
'/sample/': {
'class_view': SampleView,
'name': 'SampleView',
'short_description': 'CRUD on samples resource',
'authentication_rules': {
'GET': True,
'POST': True,
'PUT': True,
'DELETE': True
}
},
'/sample_two/': {
'class_view': SampleTwoView,
'name': 'SampleTwoView',
'short_description': 'Fetches a single sample given a sample slug',
'authentication_rules': {
'GET': False,
}
},
}

And then in a settings dict/class:

REGISTERED_MODULES = (
'users',
'blog',
'inventory',
'orders'
)

To make all of this work you need to be aware of two python concepts:

a. Importing a module/file using it’s string path
b. Python classes/methods can be accessed/called even from the string name

Inside app.py where the main app launches

sample_app = Flask(__name__)def register_app_modules():

for index, registered_module in enumerate(REGISTERED_MODULES):

endpoints =getattr(importlib.import_module(registered_module + '.urls'), 'API_VIEWS')

for url_path, endpoint_config in endpoints.iteritems():
# GET THE class_view param from the endpoint config
class_view = endpoint_config['class_view']
sample_app.add_url_rule(url_path,
view_func=getattr(class_view, 'as_view')(url_path)
)


register_app_modules()

Here is what register_app_modules does:

  1. Iterate over all registered modules and import API_VIEWS variable from the urls.py file for each one of them
  2. Iterate over the API_VIEWS dict
  3. For each url config get the class_view and then using getattr() get the as_view function (this is already defined for any class derived from MethodView provided by flask).
  4. Call the as_view function of the class with url_path as param => getattr(SampleView, ‘as_view’)(url_path)

5. add_url_rule is provided by flask to add urls (you can find it in flask documentation)

As far as the authentication goes for this, it depends again on how you have configured it, I used flask middleware decorator @app.before_request

Inside the same I would look at the incoming URL path and the HTTP verb, based on the config it would be easy to determine if the authentication was required or if it was a public URL.

Do suggest improvements or pitfalls in the outlined approach :)

--

--