Skip to content
Advertisement

More elegant way to call arbitrary Python functions from Flask

Setting up a Flask REST API is easy. I use the following approach:

from flask import Flask
from flask_cors import CORS

from utility import *

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

@app.after_request
def add_headers(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Headers'] =  "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
    response.headers['Access-Control-Allow-Methods']=  "POST, GET, PUT, DELETE, OPTIONS"
    return response

@app.route("/", methods=["GET"])
def call_function():
    res = request_return()
    return(res)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

In this example I import functions sitting inside utility.py:

from flask import request, jsonify

def my_function(arg):
    return(arg)

def string_to_number(str):
    if("." in str):
        try:
            res = float(str)
        except:
            res = str
    elif(str.isdigit()):
        res = int(str)
    else:
        res = str
    return(res)

def request_return():
    passed_function = request.args.get('function')
    args = dict(request.args)
    values = list(args.values())[1:]
    values = list(map(string_to_number, values))
    res = globals()[passed_function](*tuple(values))
    return(jsonify(res))

This allows me to pass any function name as an argument and get the result back. So if I ran the following in my browser:

https:localhost:5000/?function=my_function&args=hello

I would see:

"hello"

The way I process the request is quite bloated. You can see in request_return that I must take a series of steps to move from the function name to its execution and return. I also use a string_to_number function to handle any numbers that might be passed. This ensures I can call any function inside utility AND any number of parameters to that function.

Any Flask tutorial I’ve seen doesn’t discuss how to call arbitrary functions like this. Is there a more elegant way to handle this? Specifically, without all the bloat in my request_return function.

Advertisement

Answer

Allowing the client to call any global function is dangerous, they could call open() to overwrite a file.

Create a dictionary of the operations that should be callable in your application.

And rather than trying to convert numbers automatically, let the individual functions decide how they want to parse their args.

my_operations = {'my_function': my_function, ...}

def request_return():
    passed_function = request.args.get('function')
    try:
        args = dict(request.args)
        del args['function'] # This is processed above
        res = my_operations[passed_function](**args)
        return(jsonify(res))
    except:
        # report error to caller
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement