HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonflaskMinor

Extension pattern in a flask controller using importlib

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
importlibflaskcontrollerextensionusingpattern

Problem

I am trying to load a module according to some settings. I have found a working solution but I need a confirmation from an advanced python developer that this solution is the best performance wise as the API endpoint which will use it will be under heavy load.

The idea is to change the working of an endpoint based on parameters from the user and other systems configuration. I am loading the correct handler class based on these settings. The goal is to be able to easily create new handlers without having to modify the code calling the handlers.

This is a working example:

./run.py:

from flask import Flask, abort 
import importlib 
import handlers  

app = Flask(__name__)  

@app.route('/') 
def api_endpoint():     
    try:         
        endpoint = "simple" # Custom logic to choose the right handler        
        handlerClass = getattr(importlib.import_module('.'+str(endpoint), 'handlers'), 'Handler')         
        handler = handlerClass()     
    except Exception as e:         
        print(e)         
        abort(404)      

    print(handlerClass, handler, handler.value, handler.name())      

    # Handler processing. Not yet implemented

    return "Hello World"  

if __name__ == "__main__":     
    app.run(host='0.0.0.0', port=8080, debug=True)


One "simple" handler example. A handler is a module which needs to define an Handler class:

./handlers/simple.py:

import os  

class Handler:     
    def __init__(self):         
        self.value = os.urandom(5)      

    def name(self):        
        return "simple"


If I understand correctly, the import is done on each query to the endpoint. It means IO in the filesystem with lookup for the modules, ...

Is it the correct/"pythonic" way to implement this strategy?

The answer so far from the previous Stack Overflow exchange:


I would whitelist if possible.

I already whitelist when I fill in the endpoint variable with my custom logic.

It does not seem to be a very clean so

Solution

the idea is nice, but I have few concerns.
Firs of all, trying to call importlib every time you are accessing the API endpoint is a bit expensive. It will be a way better if you will introduce some cache first.

You can use 2 approaches to build that cache:

  • Lazy, where you lookup cache first and if you cannot find the proper handler, you are trying to load it using your trick with importlib



  • The standard approach where you can use e.g. pkgutil.iter_modules (https://docs.python.org/2/library/pkgutil.html#pkgutil.iter_modules) and build the handler cache before you are actually start handling the API endpoints.



I have also few notes regarding your code:

-
Use format functions instead of concatenating strings:

".{}".format(endpoint)


-
Don't catch general exceptions (just use more specific classes than Exception class) and try not to put more then 1 statement under the try...except block.

-
Use pythonic naming handlerClass -> handler_class

-
In your handlers you can make names as read-only properties:

@property
def name(self):
    return "sample"


-
Try to follow PEP8 in module imports:

# System modules
import importlib 

# 3rd party
from flask import Flask, abort 

# your modules
import handlers

Code Snippets

".{}".format(endpoint)
@property
def name(self):
    return "sample"
# System modules
import importlib 

# 3rd party
from flask import Flask, abort 

# your modules
import handlers

Context

StackExchange Code Review Q#96533, answer score: 6

Revisions (0)

No revisions yet.