pluggy 是 Python 的一个插件管理库。pytest 利用 pluggy 实现插件化,在 Flask 或 Django 等 web 框架中,可以使用 pluggy 为 Middleware 或 View 功能扩展钩子。在数据流管道中,通过 pluggy 添加钩子实现数据采集、过滤、聚合等功能可用于处理数据流的不同阶段。
1. 创建过滤器接口
python
from pluggy import HookimplMarker
from pluggy import HookspecMarker
from pluggy import PluginManager
hookspec = HookspecMarker("example.filter")
hookimpl = HookimplMarker("example.filter")
class FilterSpec:
"""拦截器接口 ."""
@hookspec
def process_request(self, request):
pass
@hookspec
def process_response(self, request, response):
pass
@hookspec
def process_view(self, request, view_func, view_args, view_kwargs):
pass
FilterSpec
定义了一个插件的接口,其中每个用 @hookspec
装饰的函数可以理解为 hook 钩子
的接口声明。
2. 创建插件即过滤器的实现
python
class FilterPlugin1:
@hookimpl
def process_request(self, request):
request.data["result"].append("FilterPlugin1")
@hookimpl(wrapper=True)
def process_response(self, request, response):
response.data["data"].append("FilterPlugin1 before yield")
# 接受插件 FilterPlugin2 的返回结果
results = yield
results.append("FilterPlugin1")
response.data["data"].append("FilterPlugin1 after yield")
return results
class FilterPlugin2:
@hookimpl
def process_request(self, request):
request.data["result"].append("FilterPlugin2")
@hookimpl(wrapper=True)
def process_response(self, request, response):
response.data["data"].append("FilterPlugin2 before yield")
# 接受插件 FilterPlugin3 的返回结果
results = yield
results.append("FilterPlugin2")
response.data["data"].append("FilterPlugin2 after yield")
return results
class FilterPlugin3:
@hookimpl
def process_request(self, request):
request.data["result"].append("FilterPlugin3")
@hookimpl(wrapper=True)
def process_response(self, request, response):
response.data["data"].append("FilterPlugin3 before yield")
# 接受其他插件的执行结果
results = yield
results.append("FilterPlugin3")
response.data["data"].append("FilterPlugin3 after yield")
return results
@hookimpl
装饰器实现了 hook
钩子 的业务逻辑。
wrapper=True
可以使得 hook
可以接收其它插件的执行结果。
3. 注册插件
python
# 初始化 PluginManager
pm = PluginManager("example.filter")
# 登记 hook 集合(hook函数声明)
pm.add_hookspecs(FilterSpec)
# 注册插件(hook函数实现)
pm.register(FilterPlugin3()) # 第一个注册的插件最后执行
pm.register(FilterPlugin2())
pm.register(FilterPlugin1()) # 最后注册的插件最先执行
class Request:
def __init__(self):
self.data = {"result": []}
class Response:
def __init__(self):
self.data = {
"code": 0,
"data": [],
"message": "",
}
4. 单元测试
4.1 模拟请求中间件实现
python
def test_hook_request():
# 调用两个插件类中的同名hook函数, 后注册的插件中的函数会先被调用
request = Request()
results = pm.hook.process_request(request=request)
assert results == [] # 钩子实现返回 None
assert request.data == {'result': ['FilterPlugin1', 'FilterPlugin2', 'FilterPlugin3']}
4.2 模拟响应中间件实现
python
def test_hook_response():
request = Request()
response = Response()
results = pm.hook.process_response(request=request, response=response)
assert results == ['FilterPlugin3', 'FilterPlugin2', 'FilterPlugin1']
assert response.data == {
'code': 0,
'data': [
'FilterPlugin1 before yield',
'FilterPlugin2 before yield',
'FilterPlugin3 before yield',
'FilterPlugin3 after yield',
'FilterPlugin2 after yield',
'FilterPlugin1 after yield',
],
'message': '',
}