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:
say
is an inner method that we cannot call it through a http request.hello
andworld
are marked as APIs withapi
decorator. 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