由 neevop 十月 24, 2022
pytest hook编写
hook解释
-
例程一
""" An example of the Template pattern in Python *TL;DR Defines the skeleton of a base algorithm, deferring definition of exact steps to subclasses. *Examples in Python ecosystem: Django class based views: https://docs.djangoproject.com/en/2.1/topics/class-based-views/ """ def get_text() -> str: return "plain-text" def get_pdf() -> str: return "pdf" def get_csv() -> str: return "csv" def convert_to_text(data: str) -> str: print("[CONVERT]") return f"{data} as text" def saver() -> None: print("[SAVE]") def template_function(getter, converter=False, to_save=False) -> None: data = getter() print(f"Got `{data}`") if len(data) <= 3 and converter: data = converter(data) else: print("Skip conversion") if to_save: saver() print(f"`{data}` was processed") def main(): """ >>> template_function(get_text, to_save=True) Got `plain-text` Skip conversion [SAVE] `plain-text` was processed >>> template_function(get_pdf, converter=convert_to_text) Got `pdf` [CONVERT] `pdf as text` was processed >>> template_function(get_csv, to_save=True) Got `csv` Skip conversion [SAVE] `csv` was processed """ if __name__ == "__main__": import doctest doctest.testmod()
-
例程二
class ContentStash(object): """ content stash for online operation pipeline is 1. input_filter: filter some contents, no use to user 2. insert_queue(redis or other broker): insert useful content to queue """ def __init__(self): self.input_filter_fn = None self.broker = [] def register_input_filter_hook(self, input_filter_fn): """ register input filter function, parameter is content dict Args: input_filter_fn: input filter function Returns: """ self.input_filter_fn = input_filter_fn def insert_queue(self, content): """ insert content to queue Args: content: dict Returns: """ self.broker.append(content) def input_pipeline(self, content, use=False): """ pipeline of input for content stash Args: use: is use, defaul False content: dict Returns: """ if not use: return # input filter if self.input_filter_fn: _filter = self.input_filter_fn(content) # insert to queue if not _filter: self.insert_queue(content) # test ## 实现一个你所需要的钩子实现:比如如果content 包含time就过滤掉,否则插入队列 def input_filter_hook(content): """ test input filter hook Args: content: dict Returns: None or content """ if content.get('time') is None: return else: return content # 原有程序 content = {'filename': 'test.jpg', 'b64_file': "#test", 'data': {"result": "cat", "probility": 0.9}} content_stash = ContentStash('audit', work_dir='') # 挂上钩子函数, 可以有各种不同钩子函数的实现,但是要主要函数输入输出必须保持原有程序中一致,比如这里是content content_stash.register_input_filter_hook(input_filter_hook) # 执行流程 content_stash.input_pipeline(content)
-
例程三
hook管理
-
例程一
# toy_example.py import pluggy hookspec = pluggy.HookspecMarker("myproject") hookimpl = pluggy.HookimplMarker("myproject") class MySpec: """A hook specification namespace.""" @hookspec def myhook(self, arg1, arg2): """My special little hook that you can customize.""" class Plugin_1: """A hook implementation namespace.""" @hookimpl def myhook(self, arg1, arg2): print("inside Plugin_1.myhook()") return arg1 + arg2 class Plugin_2: """A 2nd hook implementation namespace.""" @hookimpl def myhook(self, arg1, arg2): print("inside Plugin_2.myhook()") return arg1 - arg2 # create a manager and add the spec pm = pluggy.PluginManager("myproject") pm.add_hookspecs(MySpec) # register plugins pm.register(Plugin_1()) pm.register(Plugin_2()) # call our `myhook` hook results = pm.hook.myhook(arg1=1, arg2=2) print(results)
$ python3 toy_example.py inside Plugin_2.myhook() inside Plugin_1.myhook() [-1, 3]
-
例程二
示例场景: 其中将准备一些鸡蛋并与装有调味品的托盘一起上桌。=涉及的厨师越多,食物就越好,所以让我们让这个过程可插拔,并编写一个插件。
# eggsample/eggsample/__init__.py import pluggy hookimpl = pluggy.HookimplMarker("eggsample") """Marker to be imported and used in plugins (and for own implementations)"""
# eggsample/eggsample/hookspecs.py import pluggy hookspec = pluggy.HookspecMarker("eggsample") @hookspec def eggsample_add_ingredients(ingredients: tuple): """Have a look at the ingredients and offer your own. :param ingredients: the ingredients, don't touch them! :return: a list of ingredients """ @hookspec def eggsample_prep_condiments(condiments: dict): """Reorganize the condiments tray to your heart's content. :param condiments: some sauces and stuff :return: a witty comment about your activity """
# eggsample/eggsample/lib.py import eggsample @eggsample.hookimpl def eggsample_add_ingredients(): spices = ["salt", "pepper"] you_can_never_have_enough_eggs = ["egg", "egg"] ingredients = spices + you_can_never_have_enough_eggs return ingredients @eggsample.hookimpl def eggsample_prep_condiments(condiments): condiments["mint sauce"] = 1
# eggsample/eggsample/host.py import itertools import random import pluggy from eggsample import hookspecs, lib condiments_tray = {"pickled walnuts": 13, "steak sauce": 4, "mushy peas": 2} def main(): pm = get_plugin_manager() cook = EggsellentCook(pm.hook) cook.add_ingredients() cook.prepare_the_food() cook.serve_the_food() def get_plugin_manager(): pm = pluggy.PluginManager("eggsample") pm.add_hookspecs(hookspecs) pm.load_setuptools_entrypoints("eggsample") pm.register(lib) return pm class EggsellentCook: FAVORITE_INGREDIENTS = ("egg", "egg", "egg") def __init__(self, hook): self.hook = hook self.ingredients = None def add_ingredients(self): results = self.hook.eggsample_add_ingredients( ingredients=self.FAVORITE_INGREDIENTS ) my_ingredients = list(self.FAVORITE_INGREDIENTS) # Each hook returns a list - so we chain this list of lists other_ingredients = list(itertools.chain(*results)) self.ingredients = my_ingredients + other_ingredients def prepare_the_food(self): random.shuffle(self.ingredients) def serve_the_food(self): condiment_comments = self.hook.eggsample_prep_condiments( condiments=condiments_tray ) print(f"Your food. Enjoy some {', '.join(self.ingredients)}") print(f"Some condiments? We have {', '.join(condiments_tray.keys())}") if any(condiment_comments): print("\n".join(condiment_comments)) if __name__ == "__main__": main()
# eggsample/setup.py from setuptools import setup, find_packages setup( name="eggsample", install_requires="pluggy>=0.3,<1.0", entry_points={"console_scripts": ["eggsample=eggsample.host:main"]}, packages=find_packages(), )
$ pip3 install --editable eggsample $ eggsample Your food. Enjoy some egg, egg, salt, egg, egg, pepper, egg Some condiments? We have pickled walnuts, steak sauce, mushy peas, mint sauc
插件
# eggsample-spam/eggsample_spam.py import eggsample @eggsample.hookimpl def eggsample_add_ingredients(ingredients): """Here the caller expects us to return a list.""" if "egg" in ingredients: spam = ["lovely spam", "wonderous spam"] else: spam = ["splendiferous spam", "magnificent spam"] return spam @eggsample.hookimpl def eggsample_prep_condiments(condiments): """Here the caller passes a mutable object, so we mess with it directly.""" try: del condiments["steak sauce"] except KeyError: pass condiments["spam sauce"] = 42
# eggsample-spam/setup.py from setuptools import setup setup( name="eggsample-spam", install_requires="eggsample", entry_points={"eggsample": ["spam = eggsample_spam"]}, py_modules=["eggsample_spam"], )
$ pip install --editable eggsample-spam $ eggsample Your food. Enjoy some egg, lovely spam, salt, egg, egg, egg, wonderous spam, egg, pepper Some condiments? We have pickled walnuts, mushy peas, mint sauce, spam sauce Now this is what I call a condiments tray!