pytest钩子函数使用

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!
    

pytest hook

参考一