I am currently refactoring a Flask app I wrote about two years ago and I suspect having done a few things not as elegantly and clean as it would have been possible using the library. I would therefore ask for some advice on how to improve upon the status quo:
- The app provides a number of API-Endpoints each reachable via a route of the form
/<category>/<endpoint>where<category>is one of 10 distinct categories. - For each
<category>I created a distinct Python module<category>.py, placed it anapi/subdirectory and a distinctflask.Blueprint(actually a subclass thereof, see later in the text) and registered that with the mainapp = Flask(__name__). - Each module
<category>.pycontains a number of functions that serve as endpoints for the respective category and that are each decorated with a route-Decorator.
So far so good.
Each endpoint function accepts a number of parameters that may either be passed as parameters of a GET-request or as part of a parameter field in the JSON-payload of a POST-request. So before calling the respective endpoint function it is checked whether or not the correct number of parameters with the correct names are provided.
Also, the app needs to check whether or not the client is allowed to call a certain function. To do this, the environment variable SSL_CLIENT_CERT that was set by the webserver (lighttpd over FCGI in my case) is read and its fingerprint compared to some internal permission file.
Since I did not quite know where to put the logic to do the above I subclassed flask.Blueprint and wrote my own (modified) route decorator (custom_route). This decorator now either returns a custom made error response (flask.Response object) or a custom-made success response (thereby calling the endpoint function with the parameters passed from the client).
So a module category_xy.py looks something like this:
category_xy = CustomBlueprint('category_xy', __name__)
@category_xy.custom_route('/category_xy/some_endpoint',
auth_required=True,
methods=['GET', 'POST'])
def some_endpoint(foo, bar):
# do stuff
return '123'
With CustomBlueprint defined in a separate file as (partly pseudoish-code):
from flask import Blueprint, Response, g, request
from functools import wraps
class CustomBlueprint(Blueprint):
def custom_route(self, rule, **options):
def decorator(f):
# don't pass the custom parameter 'auth_required' on to
# self.route
modified_options = dict(options)
if 'auth_required' in modified_options:
del modified_options['auth_required']
@self.route(rule, **modified_options)
@wraps(f)
def wrapper():
# do some authentication checks...
if not authenticiated():
return Response(...)
# check if correct paramters have been passed
if not correct_paramters():
return Response(...)
# extract parameter values from either GET or POST request
arg_values = get_arg_values(request)
# call the decorated function with these parameters
result = f(*arg_values)
return Response(result, ...)
return wrapper
return decorator
This works but it does not feel clean at all and I think that there should be a better and cleaner way of doing this. Putting all this logic in a custom decorator feels quite wrong.
Could somebody more experienced with Flask provide some thoughts and/or best practice?
As a start you can switch to Pluggable Views — API that allows you to write your view functions as classes:
It becomes really helpful when you have base view class with common methods (database objects handling, validation, authorization checking) and "category" classes are inherited from it:
If one of categories has extended functionality you can just override one of the common methods:
In order to automate routes generation you can write a metaclass for
BaseView. Routes for every new class will be generated after class definition: