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