在Flask中为类添加REST行为,蓝图的用途?

12 投票
1 回答
9639 浏览
提问于 2025-04-17 00:53

我正在处理一个Python应用程序,这个程序由多个轻量级的分布式组件组成,它们通过RabbitMQKombu进行通信。

其中一个组件监听两个队列,并且可以在每个队列上接收多种类型的消息。子类可以通过注册自定义处理程序来覆盖每种消息类型的处理方式。所有这些都运行得很好。

现在我有了一个新的要求:每个组件必须有一个基本的REST/HTML接口。也就是说,你可以用浏览器访问这个正在运行的组件,实时查看它当前在做什么(比如正在处理哪些消息、CPU使用情况、状态信息、日志等)。

这个接口需要轻量,所以经过一些研究,我决定使用Flask(但我也愿意听取其他建议)。用伪代码来说,这意味着我需要:

class Component:
   Queue A
   Queue B
   ...
   def setup(..):
     # connect to the broker & other initialization

   def start(..):
     # start the event loop and wait for work  

   def handle_msg_on_A(self,msg):
     # dispatch a msg to a handler depending on the msg type

   def handle_msg_on_B(self,msg):
     ...

   ...

并添加一些视图方法:

   @app.route('/')
   def web_ui(self):
      # render to a template

   @app.route('/state')
   def get_state(self):
      # REST method to return some internal state info as JSON

   ...

不过,把一个网页界面直接加到这样的类上,会破坏SOLID原则,并且会带来继承方面的问题(子类可能想显示更多或更少的信息)。装饰器是不会被继承的,所以每个视图方法都需要明确地被重写和重新装饰。也许使用混合类和反射可以解决这个问题,但感觉有点不太靠谱。

相反,使用组合的方式可能会更好:把网页相关的内容放在一个单独的类中,这个类将URL路由委托给嵌套组件上固定的、多态的方法。这样,组件就不会知道Flask的存在,但会牺牲一些灵活性(可用的方法集合是固定的)。

我现在发现了Flask蓝图应用调度,看起来它们可以提供一个更好、更可扩展的解决方案。不过,我还没有完全理解它们。

我觉得我在这里缺少一个设计模式,希望有更多Flask经验的人能给我一些建议。

1 个回答

11

在Flask 0.7版本中,还有一个新功能可能会引起你的兴趣——可插拔视图。这个功能是基于类的,而不是基于函数的端点。也就是说,你可以使用dispatch_request方法来管理状态的变化(只有在需要的时候才重写这个方法)。

这样做的好处是,相比于使用应用程序调度,你可以在整个应用中使用url_for,而不需要在跨应用的地方硬编码网址。你需要考虑一下,这是否会成为你应用中的一个问题。

用伪代码表示:

# File: Components.py
from flask.views import View

class Component(View):
    # Define your Component-specific application logic here

    dispatch_request(self, *url_args, **url_kwargs):
        # Define route-specific logic that all Components should have here.
        # Call Component-specific methods as necessary

class Tool_1(Component):
    pass

class Tool_2(Component):
    # Override methods here

# File: app.py
from flask import Flask
from yourapplication import Tool_1, Tool_2

app = Flask()

# Assuming you want to pass all additional parameters as one argument
app.add_url_rule("/tool_1/<path:options>", "tool1", view_func=Tool_1.as_view())

# Assuming you want to pass additional parameters separately
tool_2_view = Tool_2.as_view()
app.add_url_rule("/tool_2/", "tool2", view_func=tool_2_view )
app.add_url_rule("/tool_2/<option>", "tool2", view_func=tool_2_view)
app.add_url_rule("/tool_2/<option>/<filter>", "tool2", view_func=tool_2_view)

如果你有一系列逻辑上相互关联的组件,你可以添加蓝图,这样就不需要记得在每个add_url_rule调用前加上/prefix。但是如果你的组件大多数是独立的,我建议使用这种模式*。

*.另一方面,如果它们需要相互隔离,我会使用文档中推荐的应用程序调度模式。

撰写回答