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?
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.
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.
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
and world
are marked as APIs with api
decorator. We can call them through http requests.The second to last code line use add_url_rule
to register APIs of UserActionView
.
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
.
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)