index.rst (33764B)
1 ``pluggy`` 2 ========== 3 **The pytest plugin system** 4 5 What is it? 6 *********** 7 ``pluggy`` is the crystallized core of :ref:`plugin management and hook 8 calling <pytest:writing-plugins>` for :std:doc:`pytest <pytest:index>`. 9 It enables `1400+ plugins`_ to extend and customize ``pytest``'s default 10 behaviour. Even ``pytest`` itself is composed as a set of ``pluggy`` plugins 11 which are invoked in sequence according to a well defined set of protocols. 12 13 It gives users the ability to extend or modify the behaviour of a 14 ``host program`` by installing a ``plugin`` for that program. 15 The plugin code will run as part of normal program execution, changing or 16 enhancing certain aspects of it. 17 18 In essence, ``pluggy`` enables function `hooking`_ so you can build 19 "pluggable" systems. 20 21 Why is it useful? 22 ***************** 23 There are some established mechanisms for modifying the behavior of other 24 programs/libraries in Python like 25 `method overriding <https://en.wikipedia.org/wiki/Method_overriding>`_ 26 (e.g. Jinja2) or 27 `monkey patching <https://en.wikipedia.org/wiki/Monkey_patch>`_ (e.g. gevent 28 or for :std:doc:`writing tests <pytest:how-to/monkeypatch>`). 29 These strategies become problematic though when several parties want to 30 participate in the modification of the same program. Therefore ``pluggy`` 31 does not rely on these mechanisms to enable a more structured approach and 32 avoid unnecessary exposure of state and behaviour. This leads to a more 33 `loosely coupled <https://en.wikipedia.org/wiki/Loose_coupling>`_ relationship 34 between ``host`` and ``plugins``. 35 36 The ``pluggy`` approach puts the burden on the designer of the 37 ``host program`` to think carefully about which objects are really 38 needed in a hook implementation. This gives ``plugin`` creators a clear 39 framework for how to extend the ``host`` via a well defined set of functions 40 and objects to work with. 41 42 How does it work? 43 ***************** 44 Let us start with a short overview of what is involved: 45 46 * ``host`` or ``host program``: the program offering extensibility 47 by specifying ``hook functions`` and invoking their implementation(s) as 48 part of program execution 49 * ``plugin``: the program implementing (a subset of) the specified hooks and 50 participating in program execution when the implementations are invoked 51 by the ``host`` 52 * ``pluggy``: connects ``host`` and ``plugins`` by using ... 53 54 - the hook :ref:`specifications <specs>` defining call signatures 55 provided by the ``host`` (a.k.a ``hookspecs`` - see :ref:`marking_hooks`) 56 - the hook :ref:`implementations <impls>` provided by registered 57 ``plugins`` (a.k.a ``hookimpl`` - see `callbacks`_) 58 - the hook :ref:`caller <calling>` - a call loop triggered at appropriate 59 program positions in the ``host`` invoking the implementations and 60 collecting the results 61 62 ... where for each registered hook *specification*, a hook *call* will 63 invoke up to ``N`` registered hook *implementations*. 64 * ``user``: the person who installed the ``host program`` and wants to 65 extend its functionality with ``plugins``. In the simplest case they install 66 the ``plugin`` in the same environment as the ``host`` and the magic will 67 happen when the ``host program`` is run the next time. Depending on 68 the ``plugin``, there might be other things they need to do. For example, 69 they might have to call the host with an additional commandline parameter 70 to the host that the ``plugin`` added. 71 72 A toy example 73 ------------- 74 Let us demonstrate the core functionality in one module and show how you can 75 start experimenting with pluggy functionality. 76 77 .. literalinclude:: examples/toy-example.py 78 79 Running this directly gets us:: 80 81 $ python docs/examples/toy-example.py 82 83 inside Plugin_2.myhook() 84 inside Plugin_1.myhook() 85 [-1, 3] 86 87 A complete example 88 ------------------ 89 Now let us demonstrate how this plays together in a vaguely real world scenario. 90 91 Let's assume our ``host program`` is called **eggsample** where some eggs will 92 be prepared and served with a tray containing condiments. As everybody knows: 93 the more cooks are involved the better the food, so let us make the process 94 pluggable and write a plugin that improves the meal with some spam and replaces 95 the steak sauce (nobody likes that anyway) with spam sauce (it's a thing - trust me). 96 97 .. note:: 98 99 **naming markers**: ``HookSpecMarker`` and ``HookImplMarker`` must be 100 initialized with the name of the ``host`` project (the ``name`` 101 parameter in ``setup()``) - so **eggsample** in our case. 102 103 **naming plugin projects**: they should be named in the form of 104 ``<host>-<plugin>`` (e.g. ``pytest-xdist``), therefore we call our 105 plugin *eggsample-spam*. 106 107 The host 108 ^^^^^^^^ 109 ``eggsample/eggsample/__init__.py`` 110 111 .. literalinclude:: examples/eggsample/eggsample/__init__.py 112 113 ``eggsample/eggsample/hookspecs.py`` 114 115 .. literalinclude:: examples/eggsample/eggsample/hookspecs.py 116 117 ``eggsample/eggsample/lib.py`` 118 119 .. literalinclude:: examples/eggsample/eggsample/lib.py 120 121 ``eggsample/eggsample/host.py`` 122 123 .. literalinclude:: examples/eggsample/eggsample/host.py 124 125 ``eggsample/setup.py`` 126 127 .. literalinclude:: examples/eggsample/setup.py 128 129 Let's get cooking - we install the host and see what a program run looks like:: 130 131 $ pip install --editable pluggy/docs/examples/eggsample 132 $ eggsample 133 134 Your food. Enjoy some egg, egg, salt, egg, egg, pepper, egg 135 Some condiments? We have pickled walnuts, steak sauce, mushy peas, mint sauce 136 137 The plugin 138 ^^^^^^^^^^ 139 ``eggsample-spam/eggsample_spam.py`` 140 141 .. literalinclude:: examples/eggsample-spam/eggsample_spam.py 142 143 ``eggsample-spam/setup.py`` 144 145 .. literalinclude:: examples/eggsample-spam/setup.py 146 147 Let's get cooking with more cooks - we install the plugin and and see what 148 we get:: 149 150 $ pip install --editable pluggy/docs/examples/eggsample-spam 151 $ eggsample 152 153 Your food. Enjoy some egg, lovely spam, salt, egg, egg, egg, wonderous spam, egg, pepper 154 Some condiments? We have pickled walnuts, mushy peas, mint sauce, spam sauce 155 Now this is what I call a condiments tray! 156 157 More real world examples 158 ------------------------ 159 To see how ``pluggy`` is used in the real world, have a look at these projects 160 documentation and source code: 161 162 * :ref:`pytest <pytest:writing-plugins>` 163 * :std:doc:`tox <tox:plugins>` 164 * :std:doc:`devpi <devpi:devguide/index>` 165 * :std:doc:`kedro <kedro:hooks/introduction>` 166 167 For more details and advanced usage please read on. 168 169 .. _define: 170 171 Define and collect hooks 172 ************************ 173 A *plugin* is a :ref:`namespace <python:tut-scopes>` type (currently one of a 174 ``class`` or module) which defines a set of *hook* functions. 175 176 As mentioned in :ref:`manage`, all *plugins* which specify *hooks* 177 are managed by an instance of a :py:class:`pluggy.PluginManager` which 178 defines the primary ``pluggy`` API. 179 180 In order for a :py:class:`~pluggy.PluginManager` to detect functions in a namespace 181 intended to be *hooks*, they must be decorated using special ``pluggy`` *marks*. 182 183 .. _marking_hooks: 184 185 Marking hooks 186 ------------- 187 The :py:class:`~pluggy.HookspecMarker` and :py:class:`~pluggy.HookimplMarker` 188 decorators are used to *mark* functions for detection by a 189 :py:class:`~pluggy.PluginManager`: 190 191 .. code-block:: python 192 193 from pluggy import HookspecMarker, HookimplMarker 194 195 hookspec = HookspecMarker("project_name") 196 hookimpl = HookimplMarker("project_name") 197 198 199 Each decorator type takes a single ``project_name`` string as its 200 lone argument the value of which is used to mark hooks for detection by 201 a similarly configured :py:class:`~pluggy.PluginManager` instance. 202 203 That is, a *mark* type called with ``project_name`` returns an object which 204 can be used to decorate functions which will then be detected by a 205 :py:class:`~pluggy.PluginManager` which was instantiated with the same 206 ``project_name`` value. 207 208 Furthermore, each *hookimpl* or *hookspec* decorator can configure the 209 underlying call-time behavior of each *hook* object by providing special 210 *options* passed as keyword arguments. 211 212 213 .. note:: 214 The following sections correspond to similar documentation in 215 ``pytest`` for :ref:`pytest:writinghooks` and can be used as 216 a supplementary resource. 217 218 .. _impls: 219 220 Implementations 221 --------------- 222 A hook *implementation* (*hookimpl*) is just a (callback) function 223 which has been appropriately marked. 224 225 *hookimpls* are loaded from a plugin using the 226 :py:meth:`~pluggy.PluginManager.register()` method: 227 228 *hookimpls* must be hashable. 229 230 .. code-block:: python 231 232 import sys 233 from pluggy import PluginManager, HookimplMarker 234 235 hookimpl = HookimplMarker("myproject") 236 237 238 @hookimpl 239 def setup_project(config, args): 240 """This hook is used to process the initial config 241 and possibly input arguments. 242 """ 243 if args: 244 config.process_args(args) 245 246 return config 247 248 249 pm = PluginManager("myproject") 250 251 # load all hookimpls from the local module's namespace 252 plugin_name = pm.register(sys.modules[__name__]) 253 254 .. _optionalhook: 255 256 Optional validation 257 ^^^^^^^^^^^^^^^^^^^ 258 Normally each *hookimpl* should be validated against a corresponding 259 hook :ref:`specification <specs>`. If you want to make an exception 260 then the *hookimpl* should be marked with the ``"optionalhook"`` option: 261 262 .. code-block:: python 263 264 @hookimpl(optionalhook=True) 265 def setup_project(config, args): 266 """This hook is used to process the initial config 267 and possibly input arguments. 268 """ 269 if args: 270 config.process_args(args) 271 272 return config 273 274 .. _specname: 275 276 Hookspec name matching 277 ^^^^^^^^^^^^^^^^^^^^^^ 278 279 During plugin :ref:`registration <registration>`, pluggy attempts to match each 280 hook implementation declared by the *plugin* to a hook 281 :ref:`specification <specs>` in the *host* program with the **same name** as 282 the function being decorated by ``@hookimpl`` (e.g. ``setup_project`` in the 283 example above). Note: there is *no* strict requirement that each *hookimpl* 284 has a corresponding *hookspec* (see 285 :ref:`enforcing spec validation <enforcing>`). 286 287 *new in version 1.0.0:* 288 289 To override the default behavior, a *hookimpl* may also be matched to a 290 *hookspec* in the *host* program with a non-matching function name by using 291 the ``specname`` option. Continuing the example above, the *hookimpl* function 292 does not need to be named ``setup_project``, but if the argument 293 ``specname="setup_project"`` is provided to the ``hookimpl`` decorator, it will 294 be matched and checked against the ``setup_project`` hookspec. 295 296 This is useful for registering multiple implementations of the same plugin in 297 the same Python file, for example: 298 299 .. code-block:: python 300 301 @hookimpl(specname="setup_project") 302 def setup_1(config, args): 303 """This hook is used to process the initial config 304 and possibly input arguments. 305 """ 306 if args: 307 config.process_args(args) 308 309 return config 310 311 312 @hookimpl(specname="setup_project") 313 def setup_2(config, args): 314 """Perform additional setup steps""" 315 # ... 316 return config 317 318 319 .. _callorder: 320 321 Call time order 322 ^^^^^^^^^^^^^^^ 323 By default hooks are :ref:`called <calling>` in LIFO registered order, however, 324 a *hookimpl* can influence its call-time invocation position using special 325 attributes. If marked with a ``"tryfirst"`` or ``"trylast"`` option it 326 will be executed *first* or *last* respectively in the hook call loop: 327 328 .. code-block:: python 329 330 import sys 331 from pluggy import PluginManager, HookimplMarker 332 333 hookimpl = HookimplMarker("myproject") 334 335 336 @hookimpl(trylast=True) 337 def setup_project(config, args): 338 """Default implementation.""" 339 if args: 340 config.process_args(args) 341 342 return config 343 344 345 class SomeOtherPlugin: 346 """Some other plugin defining the same hook.""" 347 348 @hookimpl(tryfirst=True) 349 def setup_project(self, config, args): 350 """Report what args were passed before calling 351 downstream hooks. 352 """ 353 if args: 354 print("Got args: {}".format(args)) 355 356 return config 357 358 359 pm = PluginManager("myproject") 360 361 # load from the local module's namespace 362 pm.register(sys.modules[__name__]) 363 # load a plugin defined on a class 364 pm.register(SomeOtherPlugin()) 365 366 For another example see the :ref:`pytest:plugin-hookorder` section of the 367 ``pytest`` docs. 368 369 .. note:: 370 ``tryfirst`` and ``trylast`` hooks are still invoked in LIFO order within 371 each category. 372 373 374 .. _hookwrappers: 375 376 Wrappers 377 ^^^^^^^^ 378 379 .. note:: 380 This section describes "new-style hook wrappers", which were added in Pluggy 381 1.1. For earlier versions, see the "old-style hook wrappers" section below. 382 383 New-style hooks wrappers are declared with ``wrapper=True``, while 384 old-style hook wrappers are declared with ``hookwrapper=True``. 385 386 The two styles are fully interoperable between plugins using different 387 styles. However within the same plugin we recommend using only one style, 388 for consistency. 389 390 A *hookimpl* can be marked with the ``"wrapper"`` option, which indicates 391 that the function will be called to *wrap* (or surround) all other normal 392 *hookimpl* calls. A *hook wrapper* can thus execute some code ahead and after the 393 execution of all corresponding non-wrapper *hookimpls*. 394 395 Much in the same way as a :py:func:`@contextlib.contextmanager <python:contextlib.contextmanager>`, 396 *hook wrappers* must be implemented as generator function with a single ``yield`` in its body: 397 398 .. code-block:: python 399 400 @hookimpl(wrapper=True) 401 def setup_project(config, args): 402 """Wrap calls to ``setup_project()`` implementations which 403 should return json encoded config options. 404 """ 405 # get initial default config 406 defaults = config.tojson() 407 408 if config.debug: 409 print("Pre-hook config is {}".format(config.tojson())) 410 411 # all corresponding hookimpls are invoked here 412 result = yield 413 414 for item in result: 415 print("JSON config override is {}".format(item)) 416 417 if config.debug: 418 print("Post-hook config is {}".format(config.tojson())) 419 420 if config.use_defaults: 421 return defaults 422 else: 423 return result 424 425 The generator is :py:meth:`sent <python:generator.send>` the return value 426 of the hook thus far, or, if the previous calls raised an exception, it is 427 :py:meth:`thrown <python:generator.throw>` the exception. 428 429 The function should do one of two things: 430 431 - Return a value, which can be the same value as received from the ``yield``, or something else entirely. 432 433 - Raise an exception. 434 435 The return value or exception propagate to further hook wrappers, and finally 436 to the hook caller. 437 438 Also see the :ref:`pytest:hookwrapper` section in the ``pytest`` docs. 439 440 .. _old_style_hookwrappers: 441 442 Old-style wrappers 443 ^^^^^^^^^^^^^^^^^^ 444 445 .. note:: 446 Prefer to use new-style hook wrappers, unless you need to support Pluggy 447 versions before 1.1. 448 449 A *hookimpl* can be marked with the ``"hookwrapper"`` option, which indicates 450 that the function will be called to *wrap* (or surround) all other normal 451 *hookimpl* calls. A *hookwrapper* can thus execute some code ahead and after the 452 execution of all corresponding non-wrapper *hookimpls*. 453 454 *hookwrappers* must be implemented as generator function with a single ``yield`` in its body: 455 456 457 .. code-block:: python 458 459 @hookimpl(hookwrapper=True) 460 def setup_project(config, args): 461 """Wrap calls to ``setup_project()`` implementations which 462 should return json encoded config options. 463 """ 464 # get initial default config 465 defaults = config.tojson() 466 467 if config.debug: 468 print("Pre-hook config is {}".format(config.tojson())) 469 470 # all corresponding hookimpls are invoked here 471 outcome = yield 472 473 try: 474 result = outcome.get_result() 475 except BaseException as e: 476 outcome.force_exception(e) 477 return 478 479 for item in result: 480 print("JSON config override is {}".format(item)) 481 482 if config.debug: 483 print("Post-hook config is {}".format(config.tojson())) 484 485 if config.use_defaults: 486 outcome.force_result(defaults) 487 488 The generator is :py:meth:`sent <python:generator.send>` a :py:class:`pluggy.Result` object which can 489 be assigned in the ``yield`` expression and used to inspect 490 the final result(s) or exceptions returned back to the caller using the 491 :py:meth:`~pluggy.Result.get_result` method, override the result 492 using the :py:meth:`~pluggy.Result.force_result`, or override 493 the exception using the :py:meth:`~pluggy.Result.force_exception` 494 method. 495 496 .. note:: 497 Old-style hook wrappers can **not** return results; they can only modify 498 them using the :py:meth:`~pluggy.Result.force_result` API. 499 500 Old-style Hook wrappers should **not** raise exceptions; this will cause 501 further hookwrappers to be skipped. They should use 502 :py:meth:`~pluggy.Result.force_exception` to adjust the 503 exception. 504 505 .. _specs: 506 507 Specifications 508 -------------- 509 A hook *specification* (*hookspec*) is a definition used to validate each 510 *hookimpl* ensuring that an extension writer has correctly defined their 511 callback function *implementation* . 512 513 *hookspecs* are defined using similarly marked functions however only the 514 function *signature* (its name and names of all its arguments) is analyzed 515 and stored. As such, often you will see a *hookspec* defined with only 516 a docstring in its body. 517 518 *hookspecs* are loaded using the 519 :py:meth:`~pluggy.PluginManager.add_hookspecs()` method and normally 520 should be added before registering corresponding *hookimpls*: 521 522 .. code-block:: python 523 524 import sys 525 from pluggy import PluginManager, HookspecMarker 526 527 hookspec = HookspecMarker("myproject") 528 529 530 @hookspec 531 def setup_project(config, args): 532 """This hook is used to process the initial config and input 533 arguments. 534 """ 535 536 537 pm = PluginManager("myproject") 538 539 # load from the local module's namespace 540 pm.add_hookspecs(sys.modules[__name__]) 541 542 543 Registering a *hookimpl* which does not meet the constraints of its 544 corresponding *hookspec* will result in an error. 545 546 A *hookspec* can also be added **after** some *hookimpls* have been 547 registered however this is not normally recommended as it results in 548 delayed hook validation. 549 550 .. note:: 551 The term *hookspec* can sometimes refer to the plugin-namespace 552 which defines ``hookspec`` decorated functions as in the case of 553 ``pytest``'s `hookspec module`_ 554 555 .. _enforcing: 556 557 Enforcing spec validation 558 ^^^^^^^^^^^^^^^^^^^^^^^^^ 559 By default there is no strict requirement that each *hookimpl* has 560 a corresponding *hookspec*. However, if you'd like you enforce this 561 behavior you can run a check with the 562 :py:meth:`~pluggy.PluginManager.check_pending()` method. If you'd like 563 to enforce requisite *hookspecs* but with certain exceptions for some hooks 564 then make sure to mark those hooks as :ref:`optional <optionalhook>`. 565 566 Opt-in arguments 567 ^^^^^^^^^^^^^^^^ 568 To allow for *hookspecs* to evolve over the lifetime of a project, 569 *hookimpls* can accept **less** arguments than defined in the spec. 570 This allows for extending hook arguments (and thus semantics) without 571 breaking existing *hookimpls*. 572 573 In other words this is ok: 574 575 .. code-block:: python 576 577 @hookspec 578 def myhook(config, args): 579 pass 580 581 582 @hookimpl 583 def myhook(args): 584 print(args) 585 586 587 whereas this is not: 588 589 .. code-block:: python 590 591 @hookspec 592 def myhook(config, args): 593 pass 594 595 596 @hookimpl 597 def myhook(config, args, extra_arg): 598 print(args) 599 600 .. note:: 601 The one exception to this rule (that a *hookspec* must have as least as 602 many arguments as its *hookimpls*) is the conventional :ref:`self <python:tut-remarks>` arg; this 603 is always ignored when *hookimpls* are defined as :ref:`methods <python:tut-methodobjects>`. 604 605 .. _firstresult: 606 607 First result only 608 ^^^^^^^^^^^^^^^^^ 609 A *hookspec* can be marked such that when the *hook* is called the call loop 610 will only invoke up to the first *hookimpl* which returns a result other 611 than ``None``. 612 613 .. code-block:: python 614 615 @hookspec(firstresult=True) 616 def myhook(config, args): 617 pass 618 619 This can be useful for optimizing a call loop for which you are only 620 interested in a single core *hookimpl*. An example is the 621 :func:`~_pytest.hookspec.pytest_cmdline_main` central routine of ``pytest``. 622 Note that all hook wrappers are still invoked with the first result. 623 624 Also see the :ref:`pytest:firstresult` section in the ``pytest`` docs. 625 626 .. _historic: 627 628 Historic hooks 629 ^^^^^^^^^^^^^^ 630 You can mark a *hookspec* as being *historic* meaning that the hook 631 can be called with :py:meth:`~pluggy.HookCaller.call_historic()` **before** 632 having been registered: 633 634 .. code-block:: python 635 636 @hookspec(historic=True) 637 def myhook(config, args): 638 pass 639 640 The implication is that late registered *hookimpls* will be called back 641 immediately at register time and **can not** return a result to the caller. 642 643 This turns out to be particularly useful when dealing with lazy or 644 dynamically loaded plugins. 645 646 For more info see :ref:`call_historic`. 647 648 649 .. _warn_on_impl: 650 651 Warnings on hook implementation 652 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 653 654 As projects evolve new hooks may be introduced and/or deprecated. 655 656 If a hookspec specifies a ``warn_on_impl``, pluggy will trigger it for any plugin implementing the hook. 657 658 659 .. code-block:: python 660 661 @hookspec( 662 warn_on_impl=DeprecationWarning("old_hook is deprecated and will be removed soon") 663 ) 664 def old_hook(): 665 pass 666 667 668 If you don't want to deprecate implementing the entire hook, but just specific 669 parameters of it, you can specify ``warn_on_impl_args``, a dict mapping 670 parameter names to warnings. The warnings will trigger whenever any plugin 671 implements the hook requesting one of the specified parameters. 672 673 .. code-block:: python 674 675 @hookspec( 676 warn_on_impl_args={ 677 "lousy_arg": DeprecationWarning( 678 "The lousy_arg parameter of refreshed_hook is deprecated and will be removed soon; " 679 "use awesome_arg instead" 680 ), 681 }, 682 ) 683 def refreshed_hook(lousy_arg, awesome_arg): 684 pass 685 686 .. versionadded:: 1.5 687 The ``warn_on_impl_args`` parameter. 688 689 690 .. _manage: 691 692 The Plugin registry 693 ******************* 694 ``pluggy`` manages plugins using instances of the 695 :py:class:`pluggy.PluginManager`. 696 697 A :py:class:`~pluggy.PluginManager` is instantiated with a single 698 ``str`` argument, the ``project_name``: 699 700 .. code-block:: python 701 702 import pluggy 703 704 pm = pluggy.PluginManager("my_project_name") 705 706 707 The ``project_name`` value is used when a :py:class:`~pluggy.PluginManager` 708 scans for *hook* functions :ref:`defined on a plugin <define>`. 709 This allows for multiple plugin managers from multiple projects 710 to define hooks alongside each other. 711 712 .. _registration: 713 714 Registration 715 ------------ 716 Each :py:class:`~pluggy.PluginManager` maintains a *plugin* registry where each *plugin* 717 contains a set of *hookimpl* definitions. Loading *hookimpl* and *hookspec* 718 definitions to populate the registry is described in detail in the section on 719 :ref:`define`. 720 721 In summary, you pass a plugin namespace object to the 722 :py:meth:`~pluggy.PluginManager.register()` and 723 :py:meth:`~pluggy.PluginManager.add_hookspecs()` methods to collect 724 hook *implementations* and *specifications* from *plugin* namespaces respectively. 725 726 You can unregister any *plugin*'s hooks using 727 :py:meth:`~pluggy.PluginManager.unregister()` and check if a plugin is 728 registered by passing its name to the 729 :py:meth:`~pluggy.PluginManager.is_registered()` method. 730 731 Loading ``setuptools`` entry points 732 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 733 You can automatically load plugins registered through 734 :ref:`setuptools entry points <setuptools:entry_points>` 735 with the :py:meth:`~pluggy.PluginManager.load_setuptools_entrypoints()` 736 method. 737 738 An example use of this is the :ref:`pytest entry point <pytest:pip-installable plugins>`. 739 740 741 Blocking 742 -------- 743 You can block any plugin from being registered using 744 :py:meth:`~pluggy.PluginManager.set_blocked()` and check if a given 745 *plugin* is blocked by name using :py:meth:`~pluggy.PluginManager.is_blocked()`. 746 747 748 Inspection 749 ---------- 750 You can use a variety of methods to inspect both the registry 751 and particular plugins in it: 752 753 - :py:meth:`~pluggy.PluginManager.list_name_plugin()` - 754 return a list of name-plugin pairs 755 - :py:meth:`~pluggy.PluginManager.get_plugins()` - retrieve all plugins 756 - :py:meth:`~pluggy.PluginManager.get_canonical_name()`- get a *plugin*'s 757 canonical name (the name it was registered with) 758 - :py:meth:`~pluggy.PluginManager.get_plugin()` - retrieve a plugin by its 759 canonical name 760 761 762 Parsing mark options 763 ^^^^^^^^^^^^^^^^^^^^ 764 You can retrieve the *options* applied to a particular 765 *hookspec* or *hookimpl* as per :ref:`marking_hooks` using the 766 :py:meth:`~pluggy.PluginManager.parse_hookspec_opts()` and 767 :py:meth:`~pluggy.PluginManager.parse_hookimpl_opts()` respectively. 768 769 770 .. _calling: 771 772 Calling hooks 773 ************* 774 The core functionality of ``pluggy`` enables an extension provider 775 to override function calls made at certain points throughout a program. 776 777 A particular *hook* is invoked by calling an instance of 778 a :py:class:`pluggy.HookCaller` which in turn *loops* through the 779 ``1:N`` registered *hookimpls* and calls them in sequence. 780 781 Every :py:class:`~pluggy.PluginManager` has a ``hook`` attribute 782 which is an instance of :py:class:`pluggy.HookRelay`. 783 The :py:class:`~pluggy.HookRelay` itself contains references 784 (by hook name) to each registered *hookimpl*'s :py:class:`~pluggy.HookCaller` instance. 785 786 More practically you call a *hook* like so: 787 788 .. code-block:: python 789 790 import sys 791 import pluggy 792 import mypluginspec 793 import myplugin 794 from configuration import config 795 796 pm = pluggy.PluginManager("myproject") 797 pm.add_hookspecs(mypluginspec) 798 pm.register(myplugin) 799 800 # we invoke the HookCaller and thus all underlying hookimpls 801 result_list = pm.hook.myhook(config=config, args=sys.argv) 802 803 Note that you **must** call hooks using keyword :std:term:`python:argument` syntax! 804 805 Hook implementations are called in LIFO registered order: *the last 806 registered plugin's hooks are called first*. As an example, the below 807 assertion should not error: 808 809 .. code-block:: python 810 811 from pluggy import PluginManager, HookimplMarker 812 813 hookimpl = HookimplMarker("myproject") 814 815 816 class Plugin1: 817 @hookimpl 818 def myhook(self, args): 819 """Default implementation.""" 820 return 1 821 822 823 class Plugin2: 824 @hookimpl 825 def myhook(self, args): 826 """Default implementation.""" 827 return 2 828 829 830 class Plugin3: 831 @hookimpl 832 def myhook(self, args): 833 """Default implementation.""" 834 return 3 835 836 837 pm = PluginManager("myproject") 838 pm.register(Plugin1()) 839 pm.register(Plugin2()) 840 pm.register(Plugin3()) 841 842 assert pm.hook.myhook(args=()) == [3, 2, 1] 843 844 Collecting results 845 ------------------ 846 By default calling a hook results in all underlying :ref:`hookimpls 847 <impls>` functions to be invoked in sequence via a loop. Any function 848 which returns a value other than a ``None`` result will have that result 849 appended to a :py:class:`list` which is returned by the call. 850 851 The only exception to this behaviour is if the hook has been marked to return 852 its :ref:`first result only <firstresult>` in which case only the first 853 single value (which is not ``None``) will be returned. 854 855 .. _call_historic: 856 857 Exception handling 858 ------------------ 859 If any *hookimpl* errors with an exception no further callbacks are invoked and 860 the exception is delivered to any :ref:`wrappers <hookwrappers>` before being 861 re-raised at the hook invocation point: 862 863 .. code-block:: python 864 865 from pluggy import PluginManager, HookimplMarker 866 867 hookimpl = HookimplMarker("myproject") 868 869 870 class Plugin1: 871 @hookimpl 872 def myhook(self, args): 873 return 1 874 875 876 class Plugin2: 877 @hookimpl 878 def myhook(self, args): 879 raise RuntimeError 880 881 882 class Plugin3: 883 @hookimpl 884 def myhook(self, args): 885 return 3 886 887 888 @hookimpl(wrapper=True) 889 def myhook(self, args): 890 try: 891 return (yield) 892 except RuntimeError as exc: 893 # log runtime error details 894 print(exc) 895 raise 896 897 898 pm = PluginManager("myproject") 899 900 # register plugins 901 pm.register(Plugin1()) 902 pm.register(Plugin2()) 903 pm.register(Plugin3()) 904 905 # register wrapper 906 pm.register(sys.modules[__name__]) 907 908 # this raises RuntimeError due to Plugin2 909 pm.hook.myhook(args=()) 910 911 Historic calls 912 -------------- 913 A *historic call* allows for all newly registered functions to receive all hook 914 calls that happened before their registration. The implication is that this is 915 only useful if you expect that some *hookimpls* may be registered **after** the 916 hook is initially invoked. 917 918 Historic hooks must be :ref:`specially marked <historic>` and called 919 using the :py:meth:`~pluggy.HookCaller.call_historic()` method: 920 921 .. code-block:: python 922 923 def callback(result): 924 print("historic call result is {result}".format(result=result)) 925 926 927 # call with history; no results returned 928 pm.hook.myhook.call_historic( 929 kwargs={"config": config, "args": sys.argv}, result_callback=callback 930 ) 931 932 # ... more of our program ... 933 934 # late loading of some plugin 935 import mylateplugin 936 937 # historic callback is invoked here 938 pm.register(mylateplugin) 939 940 Note that if you :py:meth:`~pluggy.HookCaller.call_historic()` 941 the :py:class:`~pluggy.HookCaller` (and thus your calling code) 942 can not receive results back from the underlying *hookimpl* functions. 943 Instead you can provide a *callback* for processing results (like the 944 ``callback`` function above) which will be called as each new plugin 945 is registered. 946 947 .. note:: 948 *historic* calls are incompatible with :ref:`firstresult` marked 949 hooks since only the first registered plugin's hook(s) would 950 ever be called. 951 952 .. _call_extra: 953 954 Calling with extras 955 ------------------- 956 You can call a hook with temporarily participating *implementation* functions 957 (that aren't in the registry) using the 958 :py:meth:`pluggy.HookCaller.call_extra()` method. 959 960 961 Calling with a subset of registered plugins 962 ------------------------------------------- 963 You can make a call using a subset of plugins by asking the 964 :py:class:`~pluggy.PluginManager` first for a 965 :py:class:`~pluggy.HookCaller` with those plugins removed 966 using the :py:meth:`pluggy.PluginManager.subset_hook_caller()` method. 967 968 You then can use that :py:class:`~pluggy.HookCaller` 969 to make normal, :py:meth:`~pluggy.HookCaller.call_historic`, or 970 :py:meth:`~pluggy.HookCaller.call_extra` calls as necessary. 971 972 973 .. _tracing: 974 975 Built-in tracing 976 **************** 977 ``pluggy`` comes with some batteries included hook tracing for your 978 debugging needs. 979 980 981 Call tracing 982 ------------ 983 To enable tracing use the 984 :py:meth:`pluggy.PluginManager.enable_tracing()` method which returns an 985 undo function to disable the behaviour. 986 987 .. code-block:: python 988 989 pm = PluginManager("myproject") 990 # magic line to set a writer function 991 pm.trace.root.setwriter(print) 992 undo = pm.enable_tracing() 993 994 995 Call monitoring 996 --------------- 997 Instead of using the built-in tracing mechanism you can also add your 998 own ``before`` and ``after`` monitoring functions using 999 :py:class:`pluggy.PluginManager.add_hookcall_monitoring()`. 1000 1001 The expected signature and default implementations for these functions is: 1002 1003 .. code-block:: python 1004 1005 def before(hook_name, hook_impls, kwargs): 1006 pass 1007 1008 1009 def after(outcome, hook_name, hook_impls, kwargs): 1010 pass 1011 1012 Public API 1013 ********** 1014 Please see the :doc:`api_reference`. 1015 1016 Development 1017 *********** 1018 Great care must taken when hacking on ``pluggy`` since multiple mature 1019 projects rely on it. Our Github integrated CI process runs the full 1020 `tox test suite`_ on each commit so be sure your changes can run on 1021 all required `Python interpreters`_ and ``pytest`` versions. 1022 1023 For development, we suggest to create a virtual environment and install ``pluggy`` in 1024 editable mode and ``dev`` dependencies:: 1025 1026 $ python3 -m venv .env 1027 $ source .env/bin/activate 1028 $ pip install -e .[dev] 1029 1030 To make sure you follow the code style used in the project, install pre-commit_ which 1031 will run style checks before each commit:: 1032 1033 $ pre-commit install 1034 1035 1036 Release Policy 1037 ************** 1038 Pluggy uses `Semantic Versioning`_. Breaking changes are only foreseen for 1039 Major releases (incremented X in "X.Y.Z"). If you want to use ``pluggy`` 1040 in your project you should thus use a dependency restriction like 1041 ``"pluggy>=0.1.0,<1.0"`` to avoid surprises. 1042 1043 1044 Table of contents 1045 ***************** 1046 1047 .. toctree:: 1048 :maxdepth: 2 1049 1050 api_reference 1051 changelog 1052 1053 1054 1055 .. hyperlinks 1056 .. _hookspec module: 1057 https://docs.pytest.org/en/latest/_modules/_pytest/hookspec.html 1058 .. _request-response pattern: 1059 https://en.wikipedia.org/wiki/Request%E2%80%93response 1060 .. _publish-subscribe: 1061 https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern 1062 .. _hooking: 1063 https://en.wikipedia.org/wiki/Hooking 1064 .. _callbacks: 1065 https://en.wikipedia.org/wiki/Callback_(computer_programming) 1066 .. _tox test suite: 1067 https://github.com/pytest-dev/pluggy/blob/main/tox.ini 1068 .. _Semantic Versioning: 1069 https://semver.org/ 1070 .. _Python interpreters: 1071 https://github.com/pytest-dev/pluggy/blob/main/tox.ini#L2 1072 .. _1400+ plugins: 1073 https://docs.pytest.org/en/latest/reference/plugin_list.html 1074 .. _pre-commit: 1075 https://pre-commit.com/ 1076 1077 1078 .. Indices and tables 1079 .. ================== 1080 .. * :ref:`genindex` 1081 .. * :ref:`modindex` 1082 .. * :ref:`search`