Build a Flask pluggable view that dispatches by object method (by function name)¶
Flask already provides MethodView that dispatches by HTTP method. But, can we dispatch by object method name?
For example, when we call http://127.0.0.1:5000/user/hello, the hello method of User class is triggered. When we call http://127.0.0.1:5000/user/world, the world method of User class is triggered.
How can we make this happen?
Define a base view class¶
from flask.views import View
class BaseActionView(View):
methods = ['POST'] # modify this as you wish
def dispatch_request(self, *args, **kwargs):
action = kwargs.pop('action') # action corresponds to the method name, we define it in the url rule
assert action is not None, 'Miss action'
meth = getattr(self, action, None)
assert meth is not None and getattr(meth, '_api', False), f"Unimplemented action {action}"
return meth()
Notice the second to last code line, getattr(meth, '_api', False) is used to distinguish published APIs and inner methods. We will further explain this later.
Define a decorator¶
from functools import wraps
def api(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._api = True # Mark as API
return wrapper
The api decorator marks a function as an API. If a function is not marked, it's illegal to call it through a http request.
Create a view and register to the app¶
from flask import Flask
app = Flask(__name__)
class UserActionView(BaseActionView): # inherit the base class we defined earlier
def say(self, msg): # inner method
return {
'msg': msg
}
@api
def hello(self): # API method
return self.say('hello')
@api
def world(self): # API method
return self.say('world')
# "aciton" part in the url rule will be treated as the view class's method name
app.add_url_rule('/user/<string:action>', view_func=UserActionView.as_view('user'))
app.run()
In class UserActionView, we define three methods:
sayis an inner method that we cannot call it through a http request.helloandworldare marked as APIs withapidecorator. We can call them through http requests.
The second to last code line use add_url_rule to register APIs of UserActionView.
Test UserActionView¶
Test hello method of UserActionView.
curl -X POST http://127.0.0.1:5000/user/hello
# output: {"msg":"hello"}
Test world method of UserActionView.
curl -X POST http://127.0.0.1:5000/user/world
# output: {"msg":"world"}
What if we call a non-API method?
curl -X POST http://127.0.0.1:5000/user/say
# got a 500 error
Look at the server console, you'll see AssertionError: Unimplemented action say.
Put together¶
from flask import Flask
from flask.views import View
from functools import wraps
app = Flask(__name__)
def api(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._api = True # Mark as API
return wrapper
class BaseActionView(View):
methods = ['POST'] # modify this as you wish
def dispatch_request(self, *args, **kwargs):
action = kwargs.pop('action') # action corresponds to the method name, we define it in the url rule
assert action is not None, 'Miss action'
meth = getattr(self, action, None)
assert meth is not None and getattr(meth, '_api', False), f"Unimplemented action {action}"
return meth()
class UserActionView(BaseActionView): # inherit the base class we defined earlier
def say(self, msg): # inner method
return {
'msg': msg
}
@api
def hello(self): # API method
return self.say('hello')
@api
def world(self): # API method
return self.say('world')
# "aciton" part in the url rule will be treated as the view class's method name
app.add_url_rule('/user/<string:action>', view_func=UserActionView.as_view('user'))
app.run(debug=False)
This article is originally created by tooli.top. Please indicate the source when reprinting : https://www.tooli.top/posts/flask_action_view