URL路由函数与现实世界对象方法的API设计中的代码重复



我的API设计中有对象方法和URL路由函数的代码重复:

# door_model.py
class Door:                               
def open(self):                       # "Door.open" written once...
...
# http_api.py (the HTTP server is separated from the real-world object models)
@app.route('/api/door/open')              # ... written twice
def dooropen():                           # ... written three times
d.open()                              # ... written four times!
d = Door()

如何在类似的API设计中避免这种不必要的名称重复?(同时保持真实世界对象模型与HTTP服务器之间的分离)。

当使用对象模型(带方法)和URL路由函数时,是否有一个通用的模式来避免不必要的名称重复?(几乎是模型-视图-控制器模式)

参见将对象的方法关联到Python Flask的API URL路由。

如果我们为每个模型动作声明一个路由,并对每个动作做同样的事情(在你的例子中,调用相应的带参数或不带参数的方法),它将复制代码。通常,人们使用设计模式(主要用于大型项目)和算法来避免代码重复。我想展示一个简单的例子,它定义了一个通用路由,并在一个handler函数中处理所有请求。

假设我们有如下的文件结构:
application/
├─ models/
│  ├─ door.py
│  ├─ window.py
├─ main.py

Door的原型看起来像

# door.py
class Door:
def open(self):
try:
# open the door
return 0
except:
return 1
def close(self):
try:
# close the door
return 0
except:
return 1
def openlater(self, waitseconds=2):
print("Waiting for ", waitseconds)
try:
# wait and open the door
return 0
except:
return 1

其中我有条件地设置C的退出码,0表示成功,1表示错误或失败。

我们必须将模型操作分离并分组为一个,因为它们具有共同的结构。

+----------+----------+------------+----------------------+
| API base |  model   | action     | arguments (optional) |
+----------+----------+------------+----------------------+
| /api     | /door    | /open      |                      |
| /api     | /door    | /close     |                      |
| /api     | /door    | /openlater | ?waitseconds=10      |
| /api     | /window  | /open      |                      |
| /api     | /<model> | /<action>  |                      |
+----------+----------+------------+----------------------+

按用法接口分隔组后,可以为每个组实现一个泛型处理程序。

通用handler实现

# main.py
from flask import Flask, Response, request
import json
from models.door import Door
from models.window import Window
app = Flask(__name__)
door = Door()
window = Window()
MODELS = {
"door": door,
"window": window,
}
@app.route("/api/<model>/<action>")
def handler(model, action):
model_ = MODELS.get(model)
action_ = getattr(model_, action, None)
if callable(action_):
try:
error = action_(**request.args)
if not error:
return Response(json.dumps({
"message": "Operation succeeded"
}), status=200, mimetype="application/json")
return Response(json.dumps({
"message": "Operation failed"
}), status=400, mimetype="application/json")
except (TypeError, Exception):
return Response(json.dumps({
"message": "Invalid parameters"
}), status=400, mimetype="application/json")
return Response(json.dumps({
"message": "Wrong action"
}), status=404, mimetype="application/json")
if __name__ == "__main__":
app.run()

所以你可以通过使用不同的API路径和查询参数来控制模型的动作。

可以创建动态路由。您的案例的动态路由将是api/door/<action>

创建一个动态的路由:

@app.route('api/door/<action:str>')
def door(action):

door = Door()
if action in door.actions:
if action.lower() == 'open':
door.open()
r = 'oppened door'
return r


创建一个名为actions的类变量,以使代码工作。例如:actions = ['open','close']

您可以利用flask蓝图模式

在你的http_api.py中使用

app = Flask(__name__)
# ... configs to app instance ...
app.register_blueprint(door_routes, url_prefix="/api/door")

In Yourapi/door.py

door_routes = Blueprint("door_routes", __name__)
door = Door()
@door_routes.route("/open")
def open():
d.open() 
return 
#other routes

或者,您也可以使用:

class Door():
def open(self):
print("opened")
d = Door()
@app.route("/api/door/<action>", methods=["POST", "GET"])
def catch_all(action):
try:
function = getattr(d,action)
resp = function()
#return jsonify(resp),200 or the below line
return f"Executed {action}"
except Exception as e:
return f"URL {action} not found"