Adding REST behavior to a class with flask, case for blueprints?
I am dealing with a python application that consists of multiple distributed lightweight components that communicate using RabbitMQ & Kombu.
A component listens on two queues and can receive multiple message types on each queue. Subclasses can override how each message type is processed by registering custom handlers. All this works fine.
I now have the added requirement that each component must have a basic REST/HTML interface. The idea being you point your browser at the running component and get realtime information on what it is currently doing (what messages it is processing, cpu usage, state info, log, etc.)
It needs to be lightweight, so after some research I have settled on Flask (but am open to suggestions). In pseudocode this means taking:
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):
...
...
and adding a number of view methods:
@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
...
However, bolting a web UI onto a class like this breaks SOLID principles and brings problems 开发者_开发知识库with inheritance (a subclass may want to display more/less information). Decorators are not inherited so every view method would need to be explicitly overridden and redecorated. Maybe using a mixin + reflection could work somehow but it feels hackish.
Instead, using composition could work: put the web stuff in a separate class that delegates the url routes to a fixed, predefined set of polymorphic methods on the nested component. This way components remain unaware of Flask at the cost of some loss in flexibility (the set of available methods is fixed).
I have now discovered Flask blueprints and Application Dispatching and it looks like they could bring a better, more extensible solution. However, I have yet to wrap my head around them.
I feel like I am missing a design pattern here and hopefully somebody with more flask-fu or experience with this type of problem can comment.
Something else was quietly introduced in Flask 0.7 that might be of interest to you - Pluggable Views. These are class based rather than function based endpoints - so you can use the dispatch_request
method to manage your state transitions (only overriding it when needed).
The benefit of doing it this way, as opposed to using Application Dispatching, is that you get url_for
support all across your application (as opposed to having to hard code in URLs that cross application boundaries.) You'll have to decide if this is something that is likely to be an issue for your application.
In pseudo-code:
# 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)
You can add blueprints to the mix if you have a series of components that are all logically connected together and you don't want to have to remember to put /prefix
in front of each one's add_url_rule
call. But if you just have a series of components that are mostly independent of each other, this is the pattern I'd use*.
*. On the other hand, if they need to be isolated from each other I'd use the Application Dispatch pattern recommended in the docs.
精彩评论