### Setup development environment with pip Source: https://pluggy.readthedocs.io Manual setup of the virtual environment and installation of dependencies using pip. ```bash $ python3 -m venv .venv $ source .venv/bin/activate $ pip install -e . --group dev --group testing ``` -------------------------------- ### Setup Configuration for Host Source: https://pluggy.readthedocs.io The setup.py file for the host application. ```python from setuptools import find_packages from setuptools import setup setup( name="eggsample", install_requires="pluggy>=0.3,<1.0", entry_points={"console_scripts": ["eggsample=eggsample.host:main"]}, packages=find_packages(), ) ``` -------------------------------- ### Setup Configuration for Plugin Source: https://pluggy.readthedocs.io The setup.py file for the plugin, registering it via entry points. ```python from setuptools import setup setup( name="eggsample-spam", install_requires="eggsample", entry_points={"eggsample": ["spam = eggsample_spam"]}, py_modules=["eggsample_spam"], ) ``` -------------------------------- ### Install pre-commit hooks Source: https://pluggy.readthedocs.io Install pre-commit to ensure code style compliance before each commit. ```bash $ uv run pre-commit install ``` -------------------------------- ### Execution output of the toy example Source: https://pluggy.readthedocs.io The console output generated by running the toy example script. ```bash $ python docs/examples/toy-example.py inside Plugin_2.myhook() inside Plugin_1.myhook() [-1, 3] ``` -------------------------------- ### Run Host with Plugin Source: https://pluggy.readthedocs.io Commands to install the plugin and run the host, followed by the updated output. ```bash $ pip install --editable pluggy/docs/examples/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! ``` -------------------------------- ### Run Host Application Source: https://pluggy.readthedocs.io Commands to install and run the host application, followed by the expected output. ```bash $ pip install --editable pluggy/docs/examples/eggsample $ eggsample Your food. Enjoy some egg, egg, salt, egg, egg, pepper, egg Some condiments? We have pickled walnuts, steak sauce, mushy peas, mint sauce ``` -------------------------------- ### Toy example of pluggy functionality Source: https://pluggy.readthedocs.io Demonstrates defining hook specifications, implementing hooks in plugins, and using a PluginManager to execute hooks. ```python 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) ``` -------------------------------- ### Example hook trace output Source: https://pluggy.readthedocs.io This shows the format of a traced hook call, including keyword arguments and the final result. ```text he_method1 [hook] plugin_name: example path: PosixPath('/tmp') reason: 'needs a network connection' status: explanation: first line second line finish he_method1 --> ['value'] [hook] ``` -------------------------------- ### Enable hook tracing with enable_tracing Source: https://pluggy.readthedocs.io Use the enable_tracing method to start tracing hook calls. The method returns an undo function to disable the behavior. ```python pm = PluginManager("myproject") # magic line to set a writer function pm.trace.root.setwriter(print) undo = pm.enable_tracing() ``` -------------------------------- ### Sync and test with uv Source: https://pluggy.readthedocs.io Use uv to sync the environment and run tests. ```bash $ uv sync $ uv run pytest ``` -------------------------------- ### Initialize Hook Markers Source: https://pluggy.readthedocs.io Create HookspecMarker and HookimplMarker instances with a specific project name to enable hook detection. ```python from pluggy import HookspecMarker, HookimplMarker hookspec = HookspecMarker("project_name") hookimpl = HookimplMarker("project_name") ``` -------------------------------- ### Define Hook Markers in eggsample/__init__.py Source: https://pluggy.readthedocs.io Initializes the hook markers for the eggsample project. ```python import pluggy hookimpl = pluggy.HookimplMarker("eggsample") """Marker to be imported and used in plugins (and for own implementations)""" ``` -------------------------------- ### Run tox matrix with uvx Source: https://pluggy.readthedocs.io Run the full tox test suite using uvx to avoid dependency conflicts. ```bash $ uvx tox ``` -------------------------------- ### Instantiate a PluginManager Source: https://pluggy.readthedocs.io Create a new PluginManager instance by providing a project name string, which is used to identify hook functions. ```python import pluggy pm = pluggy.PluginManager("my_project_name") ``` -------------------------------- ### Implement Default Hooks in eggsample/lib.py Source: https://pluggy.readthedocs.io Provides default implementations for the hooks defined in the host. ```python 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 ``` -------------------------------- ### Host Application Logic in eggsample/host.py Source: https://pluggy.readthedocs.io The main host application that manages the plugin lifecycle and executes hooks. ```python import itertools import random from eggsample import hookspecs from eggsample import lib import pluggy 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() ``` -------------------------------- ### Implement Plugin in eggsample-spam/eggsample_spam.py Source: https://pluggy.readthedocs.io A plugin implementation that modifies ingredients and the condiments tray. ```python 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 return "Now this is what I call a condiments tray!" ``` -------------------------------- ### Invoke a hook using PluginManager Source: https://pluggy.readthedocs.io Demonstrates how to invoke a hook by calling the HookCaller instance on the PluginManager. Hooks must be called using keyword argument syntax. ```python import sys import pluggy import mypluginspec import myplugin from configuration import config pm = pluggy.PluginManager("myproject") pm.add_hookspecs(mypluginspec) pm.register(myplugin) # we invoke the HookCaller and thus all underlying hookimpls result_list = pm.hook.myhook(config=config, args=sys.argv) ``` -------------------------------- ### pm.hook.myhook(config=config, args=sys.argv) Source: https://pluggy.readthedocs.io Invokes a hook by calling the HookCaller instance for the hook name 'myhook'. All registered hook implementations are called in LIFO order, and results are collected into a list. ```APIDOC ## pm.hook.myhook(config=config, args=sys.argv) ### Description Invokes the hook named 'myhook' on the PluginManager's hook relay. All registered hook implementations are called in LIFO order (last registered first). Results are collected into a list unless the hook is marked to return only the first result. ### Method Hook call (Python method invocation) ### Endpoint pm.hook.myhook(**kwargs) ### Parameters #### Keyword Arguments - **kwargs** (dict) - Required - Must use keyword argument syntax. The arguments are passed to each hook implementation. In the example, 'config' and 'args' are passed. ### Request Example ```python result_list = pm.hook.myhook(config=config, args=sys.argv) ``` ### Response #### Success Response - **result_list** (list) - A list of non-None return values from each hook implementation, in LIFO order. #### Response Example ```python [3, 2, 1] ``` ### Error Handling If any hook implementation raises an exception, no further callbacks are invoked and the exception is delivered to any wrappers before being re-raised at the hook invocation point. ``` -------------------------------- ### Implement custom hook call monitoring Source: https://pluggy.readthedocs.io Define before and after functions to monitor hook calls using add_hookcall_monitoring. ```python def before(hook_name, hook_impls, kwargs): pass def after(outcome, hook_name, hook_impls, kwargs): pass ``` -------------------------------- ### Handle exceptions in hook wrappers Source: https://pluggy.readthedocs.io Demonstrates how a wrapper can catch exceptions raised by hook implementations. If an error occurs, no further callbacks are invoked. ```python from pluggy import PluginManager, HookimplMarker hookimpl = HookimplMarker("myproject") class Plugin1: @hookimpl def myhook(self, args): return 1 class Plugin2: @hookimpl def myhook(self, args): raise RuntimeError class Plugin3: @hookimpl def myhook(self, args): return 3 @hookimpl(wrapper=True) def myhook(self, args): try: return (yield) except RuntimeError as exc: # log runtime error details print(exc) raise pm = PluginManager("myproject") # register plugins pm.register(Plugin1()) pm.register(Plugin2()) pm.register(Plugin3()) # register wrapper pm.register(sys.modules[__name__]) # this raises RuntimeError due to Plugin2 pm.hook.myhook(args=()) ``` -------------------------------- ### Define Hook Specifications in eggsample/hookspecs.py Source: https://pluggy.readthedocs.io Defines the hook specifications that plugins must implement. ```python 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 """ ``` -------------------------------- ### Verify LIFO hook execution order Source: https://pluggy.readthedocs.io Shows that hook implementations are called in LIFO order, meaning the last registered plugin's hooks are executed first. ```python from pluggy import PluginManager, HookimplMarker hookimpl = HookimplMarker("myproject") class Plugin1: @hookimpl def myhook(self, args): """Default implementation.""" return 1 class Plugin2: @hookimpl def myhook(self, args): """Default implementation.""" return 2 class Plugin3: @hookimpl def myhook(self, args): """Default implementation.""" return 3 pm = PluginManager("myproject") pm.register(Plugin1()) pm.register(Plugin2()) pm.register(Plugin3()) assert pm.hook.myhook(args=()) == [3, 2, 1] ``` -------------------------------- ### Register Hook Implementations Source: https://pluggy.readthedocs.io Define a hook implementation using the HookimplMarker and register it with a PluginManager instance. ```python import sys from pluggy import PluginManager, HookimplMarker hookimpl = HookimplMarker("myproject") @hookimpl def setup_project(config, args): """This hook is used to process the initial config and possibly input arguments. """ if args: config.process_args(args) return config pm = PluginManager("myproject") # load all hookimpls from the local module's namespace plugin_name = pm.register(sys.modules[__name__]) ``` -------------------------------- ### pluggy.PluginManager Source: https://pluggy.readthedocs.io The PluginManager class is instantiated with a project name and provides methods to register, unregister, block, inspect, and load plugins. ```APIDOC ## pluggy.PluginManager ### Description Manages plugins and their hook functions for a given project name. ### Constructor - **project_name** (str) - Required - The name of the project, used to scope hook functions. ### Methods #### register() Registers a plugin namespace object, collecting its hook implementations. #### add_hookspecs() Collects hook specifications from a plugin namespace object. #### unregister() Unregisters a plugin's hooks. #### is_registered() Checks if a plugin is registered by its name. #### load_setuptools_entrypoints() Automatically loads plugins registered through setuptools entry points. #### set_blocked() Blocks a plugin from being registered. #### is_blocked() Checks if a plugin is blocked by name. #### list_name_plugin() Returns a list of name-plugin pairs. #### get_plugins() Retrieves all plugins. #### get_canonical_name() Gets a plugin's canonical name (the name it was registered with). #### get_plugin() Retrieves a plugin by its canonical name. #### parse_hookspec_opts() Retrieves the options applied to a particular hookspec. #### parse_hookimpl_opts() Retrieves the options applied to a particular hookimpl. ### Example ```python import pluggy pm = pluggy.PluginManager("my_project_name") ``` ``` -------------------------------- ### pm.hook.myhook.call_extra(..., ...) Source: https://pluggy.readthedocs.io Calls a hook with temporarily participating implementation functions that are not in the registry. This is done using the pluggy.HookCaller.call_extra() method. ```APIDOC ## pm.hook.myhook.call_extra(..., ...) ### Description Calls a hook with temporarily participating implementation functions that are not in the registry. This is done using the pluggy.HookCaller.call_extra() method. ### Method Hook call (Python method invocation) ### Endpoint pm.hook.myhook.call_extra(extra_impls, kwargs) ### Parameters #### Arguments - **extra_impls** (list) - Required - A list of hook implementation functions to temporarily include in the call. - **kwargs** (dict) - Required - Keyword arguments to pass to the hook implementations. ### Request Example ```python # Example not provided in source ``` ### Response #### Success Response Returns the result of the hook call, typically a list of results. ### Error Handling No specific error handling documented. ``` -------------------------------- ### Influence hook call order with tryfirst and trylast Source: https://pluggy.readthedocs.io Use HookimplMarker with tryfirst=True or trylast=True to control the invocation position of hook implementations within the hook call loop. ```python import sys from pluggy import PluginManager, HookimplMarker hookimpl = HookimplMarker("myproject") @hookimpl(trylast=True) def setup_project(config, args): """Default implementation.""" if args: config.process_args(args) return config class SomeOtherPlugin: """Some other plugin defining the same hook.""" @hookimpl(tryfirst=True) def setup_project(self, config, args): """Report what args were passed before calling downstream hooks. """ if args: print("Got args: {}".format(args)) return config pm = PluginManager("myproject") # load from the local module's namespace pm.register(sys.modules[__name__]) # load a plugin defined on a class pm.register(SomeOtherPlugin()) ``` -------------------------------- ### Triggering Warnings on Hook Implementation Source: https://pluggy.readthedocs.io Use warn_on_impl to trigger a warning whenever a plugin implements a specific hook. ```python @hookspec( warn_on_impl=DeprecationWarning("old_hook is deprecated and will be removed soon") ) def old_hook(): pass ``` -------------------------------- ### Implement a new-style hook wrapper Source: https://pluggy.readthedocs.io Define a hook wrapper using wrapper=True as a generator function with a single yield to execute code before and after other hook implementations. ```python @hookimpl(wrapper=True) def setup_project(config, args): """Wrap calls to ``setup_project()`` implementations which should return json encoded config options. """ # get initial default config defaults = config.tojson() if config.debug: print("Pre-hook config is {}".format(config.tojson())) # all corresponding hookimpls are invoked here result = yield for item in result: print("JSON config override is {}".format(item)) if config.debug: print("Post-hook config is {}".format(config.tojson())) if config.use_defaults: return defaults else: return result ``` -------------------------------- ### Define and load hook specifications Source: https://pluggy.readthedocs.io Hook specifications define the required signature for implementations. They are registered using the PluginManager's add_hookspecs method, typically before registering implementations. ```python import sys from pluggy import PluginManager, HookspecMarker hookspec = HookspecMarker("myproject") @hookspec def setup_project(config, args): """This hook is used to process the initial config and input arguments. """ pm = PluginManager("myproject") # load from the local module's namespace pm.add_hookspecs(sys.modules[__name__]) ``` -------------------------------- ### Define hookimpl argument defaults Source: https://pluggy.readthedocs.io Declaring default values in hook implementations allows plugins to remain compatible with older host applications that do not pass all arguments defined in the hook specification. ```python class CompatiblePlugin: @hookimpl def setup_project(self, config, args, version=1): ... ``` -------------------------------- ### pm.hook.myhook.call_historic(kwargs=..., result_callback=...) Source: https://pluggy.readthedocs.io Performs a historic hook call, allowing all newly registered plugins to receive the hook call that happened before their registration. Results are not returned directly; instead a callback is invoked for each result as plugins are registered. ```APIDOC ## pm.hook.myhook.call_historic(kwargs=..., result_callback=...) ### Description Performs a historic hook call. This allows plugins registered after the call to still receive the hook call. The HookCaller does not return results; instead, a result_callback is invoked for each result as new plugins are registered. ### Method Hook call (Python method invocation) ### Endpoint pm.hook.myhook.call_historic(kwargs, result_callback) ### Parameters #### Keyword Arguments - **kwargs** (dict) - Required - A dictionary of keyword arguments to pass to the hook implementations. - **result_callback** (callable) - Required - A callback function that receives the result of each hook implementation as plugins are registered. ### Request Example ```python def callback(result): print("historic call result is {result}".format(result=result)) pm.hook.myhook.call_historic( kwargs={"config": config, "args": sys.argv}, result_callback=callback ) ``` ### Response #### Success Response No direct return value. The result_callback is invoked for each result as new plugins are registered. ### Error Handling Historic calls are incompatible with hooks marked to return only the first result, since only the first registered plugin's hooks would ever be called. ``` -------------------------------- ### Valid Hook Implementation with Fewer Arguments Source: https://pluggy.readthedocs.io Hook implementations can accept fewer arguments than defined in the hookspec to allow for project evolution without breaking existing implementations. ```python @hookspec def myhook(config, args): pass @hookimpl def myhook(args): print(args) ``` -------------------------------- ### pm.subset_hook_caller(hook_name, remove_plugins) Source: https://pluggy.readthedocs.io Creates a HookCaller for a subset of plugins by removing specified plugins from the registry. The resulting HookCaller can be used for normal, historic, or extra calls. ```APIDOC ## pm.subset_hook_caller(hook_name, remove_plugins) ### Description Creates a HookCaller for a subset of plugins by removing specified plugins from the registry. The resulting HookCaller can be used for normal, call_historic(), or call_extra() calls as necessary. ### Method Hook call (Python method invocation) ### Endpoint pm.subset_hook_caller(hook_name, remove_plugins) ### Parameters #### Arguments - **hook_name** (str) - Required - The name of the hook to call. - **remove_plugins** (list) - Required - A list of plugin instances to remove from the call. ### Request Example ```python # Example not provided in source ``` ### Response #### Success Response Returns a HookCaller instance that can be used to invoke the hook with the subset of plugins. ### Error Handling No specific error handling documented. ``` -------------------------------- ### Triggering Warnings for Specific Hook Arguments Source: https://pluggy.readthedocs.io Use warn_on_impl_args to trigger warnings when a plugin implements a hook requesting specific deprecated parameters. ```python @hookspec( warn_on_impl_args={ "lousy_arg": DeprecationWarning( "The lousy_arg parameter of refreshed_hook is deprecated and will be removed soon; " "use awesome_arg instead" ), }, ) def refreshed_hook(lousy_arg, awesome_arg): pass ``` -------------------------------- ### Marking Hookspec for First Result Only Source: https://pluggy.readthedocs.io Using firstresult=True ensures the call loop stops after the first hook implementation returns a non-None result. ```python @hookspec(firstresult=True) def myhook(config, args): pass ``` -------------------------------- ### Mark Optional Hook Implementations Source: https://pluggy.readthedocs.io Use the optionalhook=True option to bypass validation against a corresponding hook specification. ```python @hookimpl(optionalhook=True) def setup_project(config, args): """This hook is used to process the initial config and possibly input arguments. """ if args: config.process_args(args) return config ``` -------------------------------- ### Invalid Hook Implementation with Extra Arguments Source: https://pluggy.readthedocs.io Hook implementations cannot accept more arguments than defined in the hookspec. ```python @hookspec def myhook(config, args): pass @hookimpl def myhook(config, args, extra_arg): print(args) ``` -------------------------------- ### Implement an old-style hook wrapper Source: https://pluggy.readthedocs.io Hook wrappers must be generator functions with a single yield. They allow executing code before and after non-wrapper hook implementations, using the yield outcome to inspect or modify results and exceptions. ```python @hookimpl(hookwrapper=True) def setup_project(config, args): """Wrap calls to ``setup_project()`` implementations which should return json encoded config options. """ # get initial default config defaults = config.tojson() if config.debug: print("Pre-hook config is {}".format(config.tojson())) # all corresponding hookimpls are invoked here outcome = yield try: result = outcome.get_result() except BaseException as e: outcome.force_exception(e) return for item in result: print("JSON config override is {}".format(item)) if config.debug: print("Post-hook config is {}".format(config.tojson())) if config.use_defaults: outcome.force_result(defaults) ``` -------------------------------- ### Defining Hookspec Argument Defaults Source: https://pluggy.readthedocs.io Hookspecs can declare default values for arguments to maintain backward compatibility with existing callers. Pluggy supplies the default if the caller omits the argument. ```python @hookspec def myhook(config, args, new_arg=None): pass # Existing caller; hookimpls will get new_arg=None. pm.hook.myhook(config=config, args=args) # New caller; hookimpls will get new_arg="get this". pm.hook.myhook(config=config, args=args, new_arg="get this") ``` -------------------------------- ### Invoke historic hook calls Source: https://pluggy.readthedocs.io Uses call_historic() to ensure late-registered plugins receive hook calls that occurred before their registration. Results are processed via a callback function. ```python def callback(result): print("historic call result is {result}".format(result=result)) # call with history; no results returned pm.hook.myhook.call_historic( kwargs={"config": config, "args": sys.argv}, result_callback=callback ) # ... more of our program ... # late loading of some plugin import mylateplugin # historic callback is invoked here pm.register(mylateplugin) ``` -------------------------------- ### Marking Hookspec as Historic Source: https://pluggy.readthedocs.io Historic hooks can be called before they are registered. Late-registered implementations are called back immediately upon registration. ```python @hookspec(historic=True) def myhook(config, args): pass ``` -------------------------------- ### Override Hookspec Name Matching Source: https://pluggy.readthedocs.io Use the specname option to map a hook implementation to a hook specification with a different function name. ```python @hookimpl(specname="setup_project") def setup_1(config, args): """This hook is used to process the initial config and possibly input arguments. """ if args: config.process_args(args) return config @hookimpl(specname="setup_project") def setup_2(config, args): """Perform additional setup steps""" # ... return config ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.