diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/pluggy | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/pluggy')
43 files changed, 5282 insertions, 5282 deletions
diff --git a/contrib/python/pluggy/py2/.dist-info/METADATA b/contrib/python/pluggy/py2/.dist-info/METADATA index cc993ee777..eaad7534d8 100644 --- a/contrib/python/pluggy/py2/.dist-info/METADATA +++ b/contrib/python/pluggy/py2/.dist-info/METADATA @@ -1,482 +1,482 @@ -Metadata-Version: 2.1 -Name: pluggy -Version: 0.13.1 -Summary: plugin and hook calling mechanisms for python -Home-page: https://github.com/pytest-dev/pluggy -Author: Holger Krekel -Author-email: holger@merlinux.eu -License: MIT license -Platform: unix -Platform: linux -Platform: osx -Platform: win32 -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: POSIX -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Topic :: Software Development :: Testing -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Utilities -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* -Requires-Dist: importlib-metadata (>=0.12) ; python_version < "3.8" -Provides-Extra: dev -Requires-Dist: pre-commit ; extra == 'dev' -Requires-Dist: tox ; extra == 'dev' - -==================================================== -pluggy - A minimalist production ready plugin system -==================================================== - -|pypi| |conda-forge| |versions| |travis| |appveyor| |gitter| |black| |codecov| - -This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. - -Please `read the docs`_ to learn more! - -A definitive example -==================== -.. code-block:: python - - import pluggy - - hookspec = pluggy.HookspecMarker("myproject") - hookimpl = pluggy.HookimplMarker("myproject") - - - class MySpec(object): - """A hook specification namespace. - """ - - @hookspec - def myhook(self, arg1, arg2): - """My special little hook that you can customize. - """ - - - class Plugin_1(object): - """A hook implementation namespace. - """ - - @hookimpl - def myhook(self, arg1, arg2): - print("inside Plugin_1.myhook()") - return arg1 + arg2 - - - class Plugin_2(object): - """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) - - -Running this directly gets us:: - - $ python docs/examples/toy-example.py - inside Plugin_2.myhook() - inside Plugin_1.myhook() - [-1, 3] - - -.. badges - -.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg - :target: https://travis-ci.org/pytest-dev/pluggy - -.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg - :target: https://ci.appveyor.com/project/pytestbot/pluggy - -.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg - :target: https://anaconda.org/conda-forge/pytest - -.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg - :alt: Join the chat at https://gitter.im/pytest-dev/pluggy - :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black - -.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg - :target: https://codecov.io/gh/pytest-dev/pluggy - :alt: Code coverage Status - -.. links -.. _pytest: - http://pytest.org -.. _tox: - https://tox.readthedocs.org -.. _devpi: - http://doc.devpi.net -.. _read the docs: - https://pluggy.readthedocs.io/en/latest/ - - -========= -Changelog -========= - -.. towncrier release notes start - -pluggy 0.13.1 (2019-11-21) -========================== - -Trivial/Internal Changes ------------------------- - -- `#236 <https://github.com/pytest-dev/pluggy/pull/236>`_: Improved documentation, especially with regard to references. - - -pluggy 0.13.0 (2019-09-10) -========================== - -Trivial/Internal Changes ------------------------- - -- `#222 <https://github.com/pytest-dev/pluggy/issues/222>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the - standard library on Python 3.8+. - - -pluggy 0.12.0 (2019-05-27) -========================== - -Features --------- - -- `#215 <https://github.com/pytest-dev/pluggy/issues/215>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. This time with ``.egg`` support. - - -pluggy 0.11.0 (2019-05-07) -========================== - -Bug Fixes ---------- - -- `#205 <https://github.com/pytest-dev/pluggy/issues/205>`_: Revert changes made in 0.10.0 release breaking ``.egg`` installs. - - -pluggy 0.10.0 (2019-05-07) -========================== - -Features --------- - -- `#199 <https://github.com/pytest-dev/pluggy/issues/199>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. - - -pluggy 0.9.0 (2019-02-21) -========================= - -Features --------- - -- `#189 <https://github.com/pytest-dev/pluggy/issues/189>`_: ``PluginManager.load_setuptools_entrypoints`` now accepts a ``name`` parameter that when given will - load only entry points with that name. - - ``PluginManager.load_setuptools_entrypoints`` also now returns the number of plugins loaded by the - call, as opposed to the number of all plugins loaded by all calls to this method. - - - -Bug Fixes ---------- - -- `#187 <https://github.com/pytest-dev/pluggy/issues/187>`_: Fix internal ``varnames`` function for PyPy3. - - -pluggy 0.8.1 (2018-11-09) -========================= - -Trivial/Internal Changes ------------------------- - -- `#166 <https://github.com/pytest-dev/pluggy/issues/166>`_: Add ``stacklevel=2`` to implprefix warning so that the reported location of warning is the caller of PluginManager. - - -pluggy 0.8.0 (2018-10-15) -========================= - -Features --------- - -- `#177 <https://github.com/pytest-dev/pluggy/issues/177>`_: Add ``get_hookimpls()`` method to hook callers. - - - -Trivial/Internal Changes ------------------------- - -- `#165 <https://github.com/pytest-dev/pluggy/issues/165>`_: Add changelog in long package description and documentation. - - -- `#172 <https://github.com/pytest-dev/pluggy/issues/172>`_: Add a test exemplifying the opt-in nature of spec defined args. - - -- `#57 <https://github.com/pytest-dev/pluggy/issues/57>`_: Encapsulate hook specifications in a type for easier introspection. - - -pluggy 0.7.1 (2018-07-28) -========================= - -Deprecations and Removals -------------------------- - -- `#116 <https://github.com/pytest-dev/pluggy/issues/116>`_: Deprecate the ``implprefix`` kwarg to ``PluginManager`` and instead - expect users to start using explicit ``HookimplMarker`` everywhere. - - - -Features --------- - -- `#122 <https://github.com/pytest-dev/pluggy/issues/122>`_: Add ``.plugin`` member to ``PluginValidationError`` to access failing plugin during post-mortem. - - -- `#138 <https://github.com/pytest-dev/pluggy/issues/138>`_: Add per implementation warnings support for hookspecs allowing for both - deprecation and future warnings of legacy and (future) experimental hooks - respectively. - - - -Bug Fixes ---------- - -- `#110 <https://github.com/pytest-dev/pluggy/issues/110>`_: Fix a bug where ``_HookCaller.call_historic()`` would call the ``proc`` - arg even when the default is ``None`` resulting in a ``TypeError``. - -- `#160 <https://github.com/pytest-dev/pluggy/issues/160>`_: Fix problem when handling ``VersionConflict`` errors when loading setuptools plugins. - - - -Improved Documentation ----------------------- - -- `#123 <https://github.com/pytest-dev/pluggy/issues/123>`_: Document how exceptions are handled and how the hook call loop - terminates immediately on the first error which is then delivered - to any surrounding wrappers. - - -- `#136 <https://github.com/pytest-dev/pluggy/issues/136>`_: Docs rework including a much better introduction and comprehensive example - set for new users. A big thanks goes out to @obestwalter for the great work! - - - -Trivial/Internal Changes ------------------------- - -- `#117 <https://github.com/pytest-dev/pluggy/issues/117>`_: Break up the main monolithic package modules into separate modules by concern - - -- `#131 <https://github.com/pytest-dev/pluggy/issues/131>`_: Automate ``setuptools`` wheels building and PyPi upload using TravisCI. - - -- `#153 <https://github.com/pytest-dev/pluggy/issues/153>`_: Reorganize tests more appropriately by modules relating to each - internal component/feature. This is in an effort to avoid (future) - duplication and better separation of concerns in the test set. - - -- `#156 <https://github.com/pytest-dev/pluggy/issues/156>`_: Add ``HookImpl.__repr__()`` for better debugging. - - -- `#66 <https://github.com/pytest-dev/pluggy/issues/66>`_: Start using ``towncrier`` and a custom ``tox`` environment to prepare releases! - - -pluggy 0.7.0 (Unreleased) -========================= - -* `#160 <https://github.com/pytest-dev/pluggy/issues/160>`_: We discovered a deployment issue so this version was never released to PyPI, only the tag exists. - -pluggy 0.6.0 (2017-11-24) -========================= - -- Add CI testing for the features, release, and master - branches of ``pytest`` (PR `#79`_). -- Document public API for ``_Result`` objects passed to wrappers - (PR `#85`_). -- Document and test hook LIFO ordering (PR `#85`_). -- Turn warnings into errors in test suite (PR `#89`_). -- Deprecate ``_Result.result`` (PR `#88`_). -- Convert ``_Multicall`` to a simple function distinguishing it from - the legacy version (PR `#90`_). -- Resolve E741 errors (PR `#96`_). -- Test and bug fix for unmarked hook collection (PRs `#97`_ and - `#102`_). -- Drop support for EOL Python 2.6 and 3.3 (PR `#103`_). -- Fix ``inspect`` based arg introspection on py3.6 (PR `#94`_). - -.. _#79: https://github.com/pytest-dev/pluggy/pull/79 -.. _#85: https://github.com/pytest-dev/pluggy/pull/85 -.. _#88: https://github.com/pytest-dev/pluggy/pull/88 -.. _#89: https://github.com/pytest-dev/pluggy/pull/89 -.. _#90: https://github.com/pytest-dev/pluggy/pull/90 -.. _#94: https://github.com/pytest-dev/pluggy/pull/94 -.. _#96: https://github.com/pytest-dev/pluggy/pull/96 -.. _#97: https://github.com/pytest-dev/pluggy/pull/97 -.. _#102: https://github.com/pytest-dev/pluggy/pull/102 -.. _#103: https://github.com/pytest-dev/pluggy/pull/103 - - -pluggy 0.5.2 (2017-09-06) -========================= - -- fix bug where ``firstresult`` wrappers were being sent an incorrectly configured - ``_Result`` (a list was set instead of a single value). Add tests to check for - this as well as ``_Result.force_result()`` behaviour. Thanks to `@tgoodlet`_ - for the PR `#72`_. - -- fix incorrect ``getattr`` of ``DeprecationWarning`` from the ``warnings`` - module. Thanks to `@nicoddemus`_ for the PR `#77`_. - -- hide ``pytest`` tracebacks in certain core routines. Thanks to - `@nicoddemus`_ for the PR `#80`_. - -.. _#72: https://github.com/pytest-dev/pluggy/pull/72 -.. _#77: https://github.com/pytest-dev/pluggy/pull/77 -.. _#80: https://github.com/pytest-dev/pluggy/pull/80 - - -pluggy 0.5.1 (2017-08-29) -========================= - -- fix a bug and add tests for case where ``firstresult`` hooks return - ``None`` results. Thanks to `@RonnyPfannschmidt`_ and `@tgoodlet`_ - for the issue (`#68`_) and PR (`#69`_) respectively. - -.. _#69: https://github.com/pytest-dev/pluggy/pull/69 -.. _#68: https://github.com/pytest-dev/pluggy/issues/68 - - -pluggy 0.5.0 (2017-08-28) -========================= - -- fix bug where callbacks for historic hooks would not be called for - already registered plugins. Thanks `@vodik`_ for the PR - and `@hpk42`_ for further fixes. - -- fix `#17`_ by considering only actual functions for hooks - this removes the ability to register arbitrary callable objects - which at first glance is a reasonable simplification, - thanks `@RonnyPfannschmidt`_ for report and pr. - -- fix `#19`_: allow registering hookspecs from instances. The PR from - `@tgoodlet`_ also modernized the varnames implementation. - -- resolve `#32`_: split up the test set into multiple modules. - Thanks to `@RonnyPfannschmidt`_ for the PR and `@tgoodlet`_ for - the initial request. - -- resolve `#14`_: add full sphinx docs. Thanks to `@tgoodlet`_ for - PR `#39`_. - -- add hook call mismatch warnings. Thanks to `@tgoodlet`_ for the - PR `#42`_. - -- resolve `#44`_: move to new-style classes. Thanks to `@MichalTHEDUDE`_ - for PR `#46`_. - -- add baseline benchmarking/speed tests using ``pytest-benchmark`` - in PR `#54`_. Thanks to `@tgoodlet`_. - -- update the README to showcase the API. Thanks to `@tgoodlet`_ for the - issue and PR `#55`_. - -- deprecate ``__multicall__`` and add a faster call loop implementation. - Thanks to `@tgoodlet`_ for PR `#58`_. - -- raise a comprehensible error when a ``hookimpl`` is called with positional - args. Thanks to `@RonnyPfannschmidt`_ for the issue and `@tgoodlet`_ for - PR `#60`_. - -- fix the ``firstresult`` test making it more complete - and remove a duplicate of that test. Thanks to `@tgoodlet`_ - for PR `#62`_. - -.. _#62: https://github.com/pytest-dev/pluggy/pull/62 -.. _#60: https://github.com/pytest-dev/pluggy/pull/60 -.. _#58: https://github.com/pytest-dev/pluggy/pull/58 -.. _#55: https://github.com/pytest-dev/pluggy/pull/55 -.. _#54: https://github.com/pytest-dev/pluggy/pull/54 -.. _#46: https://github.com/pytest-dev/pluggy/pull/46 -.. _#44: https://github.com/pytest-dev/pluggy/issues/44 -.. _#42: https://github.com/pytest-dev/pluggy/pull/42 -.. _#39: https://github.com/pytest-dev/pluggy/pull/39 -.. _#32: https://github.com/pytest-dev/pluggy/pull/32 -.. _#19: https://github.com/pytest-dev/pluggy/issues/19 -.. _#17: https://github.com/pytest-dev/pluggy/issues/17 -.. _#14: https://github.com/pytest-dev/pluggy/issues/14 - - -pluggy 0.4.0 (2016-09-25) -========================= - -- add ``has_plugin(name)`` method to pluginmanager. thanks `@nicoddemus`_. - -- fix `#11`_: make plugin parsing more resilient against exceptions - from ``__getattr__`` functions. Thanks `@nicoddemus`_. - -- fix issue `#4`_: specific ``HookCallError`` exception for when a hook call - provides not enough arguments. - -- better error message when loading setuptools entrypoints fails - due to a ``VersionConflict``. Thanks `@blueyed`_. - -.. _#11: https://github.com/pytest-dev/pluggy/issues/11 -.. _#4: https://github.com/pytest-dev/pluggy/issues/4 - - -pluggy 0.3.1 (2015-09-17) -========================= - -- avoid using deprecated-in-python3.5 getargspec method. Thanks - `@mdboom`_. - - -pluggy 0.3.0 (2015-05-07) -========================= - -initial release - -.. contributors -.. _@hpk42: https://github.com/hpk42 -.. _@tgoodlet: https://github.com/goodboy -.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE -.. _@vodik: https://github.com/vodik -.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt -.. _@blueyed: https://github.com/blueyed -.. _@nicoddemus: https://github.com/nicoddemus -.. _@mdboom: https://github.com/mdboom - - +Metadata-Version: 2.1 +Name: pluggy +Version: 0.13.1 +Summary: plugin and hook calling mechanisms for python +Home-page: https://github.com/pytest-dev/pluggy +Author: Holger Krekel +Author-email: holger@merlinux.eu +License: MIT license +Platform: unix +Platform: linux +Platform: osx +Platform: win32 +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Requires-Dist: importlib-metadata (>=0.12) ; python_version < "3.8" +Provides-Extra: dev +Requires-Dist: pre-commit ; extra == 'dev' +Requires-Dist: tox ; extra == 'dev' + +==================================================== +pluggy - A minimalist production ready plugin system +==================================================== + +|pypi| |conda-forge| |versions| |travis| |appveyor| |gitter| |black| |codecov| + +This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. + +Please `read the docs`_ to learn more! + +A definitive example +==================== +.. code-block:: python + + import pluggy + + hookspec = pluggy.HookspecMarker("myproject") + hookimpl = pluggy.HookimplMarker("myproject") + + + class MySpec(object): + """A hook specification namespace. + """ + + @hookspec + def myhook(self, arg1, arg2): + """My special little hook that you can customize. + """ + + + class Plugin_1(object): + """A hook implementation namespace. + """ + + @hookimpl + def myhook(self, arg1, arg2): + print("inside Plugin_1.myhook()") + return arg1 + arg2 + + + class Plugin_2(object): + """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) + + +Running this directly gets us:: + + $ python docs/examples/toy-example.py + inside Plugin_2.myhook() + inside Plugin_1.myhook() + [-1, 3] + + +.. badges + +.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg + :target: https://travis-ci.org/pytest-dev/pluggy + +.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg + :target: https://ci.appveyor.com/project/pytestbot/pluggy + +.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg + :target: https://anaconda.org/conda-forge/pytest + +.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg + :alt: Join the chat at https://gitter.im/pytest-dev/pluggy + :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + +.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pluggy + :alt: Code coverage Status + +.. links +.. _pytest: + http://pytest.org +.. _tox: + https://tox.readthedocs.org +.. _devpi: + http://doc.devpi.net +.. _read the docs: + https://pluggy.readthedocs.io/en/latest/ + + +========= +Changelog +========= + +.. towncrier release notes start + +pluggy 0.13.1 (2019-11-21) +========================== + +Trivial/Internal Changes +------------------------ + +- `#236 <https://github.com/pytest-dev/pluggy/pull/236>`_: Improved documentation, especially with regard to references. + + +pluggy 0.13.0 (2019-09-10) +========================== + +Trivial/Internal Changes +------------------------ + +- `#222 <https://github.com/pytest-dev/pluggy/issues/222>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the + standard library on Python 3.8+. + + +pluggy 0.12.0 (2019-05-27) +========================== + +Features +-------- + +- `#215 <https://github.com/pytest-dev/pluggy/issues/215>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. This time with ``.egg`` support. + + +pluggy 0.11.0 (2019-05-07) +========================== + +Bug Fixes +--------- + +- `#205 <https://github.com/pytest-dev/pluggy/issues/205>`_: Revert changes made in 0.10.0 release breaking ``.egg`` installs. + + +pluggy 0.10.0 (2019-05-07) +========================== + +Features +-------- + +- `#199 <https://github.com/pytest-dev/pluggy/issues/199>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. + + +pluggy 0.9.0 (2019-02-21) +========================= + +Features +-------- + +- `#189 <https://github.com/pytest-dev/pluggy/issues/189>`_: ``PluginManager.load_setuptools_entrypoints`` now accepts a ``name`` parameter that when given will + load only entry points with that name. + + ``PluginManager.load_setuptools_entrypoints`` also now returns the number of plugins loaded by the + call, as opposed to the number of all plugins loaded by all calls to this method. + + + +Bug Fixes +--------- + +- `#187 <https://github.com/pytest-dev/pluggy/issues/187>`_: Fix internal ``varnames`` function for PyPy3. + + +pluggy 0.8.1 (2018-11-09) +========================= + +Trivial/Internal Changes +------------------------ + +- `#166 <https://github.com/pytest-dev/pluggy/issues/166>`_: Add ``stacklevel=2`` to implprefix warning so that the reported location of warning is the caller of PluginManager. + + +pluggy 0.8.0 (2018-10-15) +========================= + +Features +-------- + +- `#177 <https://github.com/pytest-dev/pluggy/issues/177>`_: Add ``get_hookimpls()`` method to hook callers. + + + +Trivial/Internal Changes +------------------------ + +- `#165 <https://github.com/pytest-dev/pluggy/issues/165>`_: Add changelog in long package description and documentation. + + +- `#172 <https://github.com/pytest-dev/pluggy/issues/172>`_: Add a test exemplifying the opt-in nature of spec defined args. + + +- `#57 <https://github.com/pytest-dev/pluggy/issues/57>`_: Encapsulate hook specifications in a type for easier introspection. + + +pluggy 0.7.1 (2018-07-28) +========================= + +Deprecations and Removals +------------------------- + +- `#116 <https://github.com/pytest-dev/pluggy/issues/116>`_: Deprecate the ``implprefix`` kwarg to ``PluginManager`` and instead + expect users to start using explicit ``HookimplMarker`` everywhere. + + + +Features +-------- + +- `#122 <https://github.com/pytest-dev/pluggy/issues/122>`_: Add ``.plugin`` member to ``PluginValidationError`` to access failing plugin during post-mortem. + + +- `#138 <https://github.com/pytest-dev/pluggy/issues/138>`_: Add per implementation warnings support for hookspecs allowing for both + deprecation and future warnings of legacy and (future) experimental hooks + respectively. + + + +Bug Fixes +--------- + +- `#110 <https://github.com/pytest-dev/pluggy/issues/110>`_: Fix a bug where ``_HookCaller.call_historic()`` would call the ``proc`` + arg even when the default is ``None`` resulting in a ``TypeError``. + +- `#160 <https://github.com/pytest-dev/pluggy/issues/160>`_: Fix problem when handling ``VersionConflict`` errors when loading setuptools plugins. + + + +Improved Documentation +---------------------- + +- `#123 <https://github.com/pytest-dev/pluggy/issues/123>`_: Document how exceptions are handled and how the hook call loop + terminates immediately on the first error which is then delivered + to any surrounding wrappers. + + +- `#136 <https://github.com/pytest-dev/pluggy/issues/136>`_: Docs rework including a much better introduction and comprehensive example + set for new users. A big thanks goes out to @obestwalter for the great work! + + + +Trivial/Internal Changes +------------------------ + +- `#117 <https://github.com/pytest-dev/pluggy/issues/117>`_: Break up the main monolithic package modules into separate modules by concern + + +- `#131 <https://github.com/pytest-dev/pluggy/issues/131>`_: Automate ``setuptools`` wheels building and PyPi upload using TravisCI. + + +- `#153 <https://github.com/pytest-dev/pluggy/issues/153>`_: Reorganize tests more appropriately by modules relating to each + internal component/feature. This is in an effort to avoid (future) + duplication and better separation of concerns in the test set. + + +- `#156 <https://github.com/pytest-dev/pluggy/issues/156>`_: Add ``HookImpl.__repr__()`` for better debugging. + + +- `#66 <https://github.com/pytest-dev/pluggy/issues/66>`_: Start using ``towncrier`` and a custom ``tox`` environment to prepare releases! + + +pluggy 0.7.0 (Unreleased) +========================= + +* `#160 <https://github.com/pytest-dev/pluggy/issues/160>`_: We discovered a deployment issue so this version was never released to PyPI, only the tag exists. + +pluggy 0.6.0 (2017-11-24) +========================= + +- Add CI testing for the features, release, and master + branches of ``pytest`` (PR `#79`_). +- Document public API for ``_Result`` objects passed to wrappers + (PR `#85`_). +- Document and test hook LIFO ordering (PR `#85`_). +- Turn warnings into errors in test suite (PR `#89`_). +- Deprecate ``_Result.result`` (PR `#88`_). +- Convert ``_Multicall`` to a simple function distinguishing it from + the legacy version (PR `#90`_). +- Resolve E741 errors (PR `#96`_). +- Test and bug fix for unmarked hook collection (PRs `#97`_ and + `#102`_). +- Drop support for EOL Python 2.6 and 3.3 (PR `#103`_). +- Fix ``inspect`` based arg introspection on py3.6 (PR `#94`_). + +.. _#79: https://github.com/pytest-dev/pluggy/pull/79 +.. _#85: https://github.com/pytest-dev/pluggy/pull/85 +.. _#88: https://github.com/pytest-dev/pluggy/pull/88 +.. _#89: https://github.com/pytest-dev/pluggy/pull/89 +.. _#90: https://github.com/pytest-dev/pluggy/pull/90 +.. _#94: https://github.com/pytest-dev/pluggy/pull/94 +.. _#96: https://github.com/pytest-dev/pluggy/pull/96 +.. _#97: https://github.com/pytest-dev/pluggy/pull/97 +.. _#102: https://github.com/pytest-dev/pluggy/pull/102 +.. _#103: https://github.com/pytest-dev/pluggy/pull/103 + + +pluggy 0.5.2 (2017-09-06) +========================= + +- fix bug where ``firstresult`` wrappers were being sent an incorrectly configured + ``_Result`` (a list was set instead of a single value). Add tests to check for + this as well as ``_Result.force_result()`` behaviour. Thanks to `@tgoodlet`_ + for the PR `#72`_. + +- fix incorrect ``getattr`` of ``DeprecationWarning`` from the ``warnings`` + module. Thanks to `@nicoddemus`_ for the PR `#77`_. + +- hide ``pytest`` tracebacks in certain core routines. Thanks to + `@nicoddemus`_ for the PR `#80`_. + +.. _#72: https://github.com/pytest-dev/pluggy/pull/72 +.. _#77: https://github.com/pytest-dev/pluggy/pull/77 +.. _#80: https://github.com/pytest-dev/pluggy/pull/80 + + +pluggy 0.5.1 (2017-08-29) +========================= + +- fix a bug and add tests for case where ``firstresult`` hooks return + ``None`` results. Thanks to `@RonnyPfannschmidt`_ and `@tgoodlet`_ + for the issue (`#68`_) and PR (`#69`_) respectively. + +.. _#69: https://github.com/pytest-dev/pluggy/pull/69 +.. _#68: https://github.com/pytest-dev/pluggy/issues/68 + + +pluggy 0.5.0 (2017-08-28) +========================= + +- fix bug where callbacks for historic hooks would not be called for + already registered plugins. Thanks `@vodik`_ for the PR + and `@hpk42`_ for further fixes. + +- fix `#17`_ by considering only actual functions for hooks + this removes the ability to register arbitrary callable objects + which at first glance is a reasonable simplification, + thanks `@RonnyPfannschmidt`_ for report and pr. + +- fix `#19`_: allow registering hookspecs from instances. The PR from + `@tgoodlet`_ also modernized the varnames implementation. + +- resolve `#32`_: split up the test set into multiple modules. + Thanks to `@RonnyPfannschmidt`_ for the PR and `@tgoodlet`_ for + the initial request. + +- resolve `#14`_: add full sphinx docs. Thanks to `@tgoodlet`_ for + PR `#39`_. + +- add hook call mismatch warnings. Thanks to `@tgoodlet`_ for the + PR `#42`_. + +- resolve `#44`_: move to new-style classes. Thanks to `@MichalTHEDUDE`_ + for PR `#46`_. + +- add baseline benchmarking/speed tests using ``pytest-benchmark`` + in PR `#54`_. Thanks to `@tgoodlet`_. + +- update the README to showcase the API. Thanks to `@tgoodlet`_ for the + issue and PR `#55`_. + +- deprecate ``__multicall__`` and add a faster call loop implementation. + Thanks to `@tgoodlet`_ for PR `#58`_. + +- raise a comprehensible error when a ``hookimpl`` is called with positional + args. Thanks to `@RonnyPfannschmidt`_ for the issue and `@tgoodlet`_ for + PR `#60`_. + +- fix the ``firstresult`` test making it more complete + and remove a duplicate of that test. Thanks to `@tgoodlet`_ + for PR `#62`_. + +.. _#62: https://github.com/pytest-dev/pluggy/pull/62 +.. _#60: https://github.com/pytest-dev/pluggy/pull/60 +.. _#58: https://github.com/pytest-dev/pluggy/pull/58 +.. _#55: https://github.com/pytest-dev/pluggy/pull/55 +.. _#54: https://github.com/pytest-dev/pluggy/pull/54 +.. _#46: https://github.com/pytest-dev/pluggy/pull/46 +.. _#44: https://github.com/pytest-dev/pluggy/issues/44 +.. _#42: https://github.com/pytest-dev/pluggy/pull/42 +.. _#39: https://github.com/pytest-dev/pluggy/pull/39 +.. _#32: https://github.com/pytest-dev/pluggy/pull/32 +.. _#19: https://github.com/pytest-dev/pluggy/issues/19 +.. _#17: https://github.com/pytest-dev/pluggy/issues/17 +.. _#14: https://github.com/pytest-dev/pluggy/issues/14 + + +pluggy 0.4.0 (2016-09-25) +========================= + +- add ``has_plugin(name)`` method to pluginmanager. thanks `@nicoddemus`_. + +- fix `#11`_: make plugin parsing more resilient against exceptions + from ``__getattr__`` functions. Thanks `@nicoddemus`_. + +- fix issue `#4`_: specific ``HookCallError`` exception for when a hook call + provides not enough arguments. + +- better error message when loading setuptools entrypoints fails + due to a ``VersionConflict``. Thanks `@blueyed`_. + +.. _#11: https://github.com/pytest-dev/pluggy/issues/11 +.. _#4: https://github.com/pytest-dev/pluggy/issues/4 + + +pluggy 0.3.1 (2015-09-17) +========================= + +- avoid using deprecated-in-python3.5 getargspec method. Thanks + `@mdboom`_. + + +pluggy 0.3.0 (2015-05-07) +========================= + +initial release + +.. contributors +.. _@hpk42: https://github.com/hpk42 +.. _@tgoodlet: https://github.com/goodboy +.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE +.. _@vodik: https://github.com/vodik +.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt +.. _@blueyed: https://github.com/blueyed +.. _@nicoddemus: https://github.com/nicoddemus +.. _@mdboom: https://github.com/mdboom + + diff --git a/contrib/python/pluggy/py2/.dist-info/top_level.txt b/contrib/python/pluggy/py2/.dist-info/top_level.txt index 11bdb5c1f5..b78af1723b 100644 --- a/contrib/python/pluggy/py2/.dist-info/top_level.txt +++ b/contrib/python/pluggy/py2/.dist-info/top_level.txt @@ -1 +1 @@ -pluggy +pluggy diff --git a/contrib/python/pluggy/py2/LICENSE b/contrib/python/pluggy/py2/LICENSE index 85f4dd63d2..58f08de42e 100644 --- a/contrib/python/pluggy/py2/LICENSE +++ b/contrib/python/pluggy/py2/LICENSE @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/contrib/python/pluggy/py2/README.rst b/contrib/python/pluggy/py2/README.rst index b79ef02460..a226e9f8b1 100644 --- a/contrib/python/pluggy/py2/README.rst +++ b/contrib/python/pluggy/py2/README.rst @@ -1,108 +1,108 @@ -==================================================== -pluggy - A minimalist production ready plugin system -==================================================== - -|pypi| |conda-forge| |versions| |travis| |appveyor| |gitter| |black| |codecov| - -This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. - -Please `read the docs`_ to learn more! - -A definitive example -==================== -.. code-block:: python - - import pluggy - - hookspec = pluggy.HookspecMarker("myproject") - hookimpl = pluggy.HookimplMarker("myproject") - - - class MySpec(object): - """A hook specification namespace. - """ - - @hookspec - def myhook(self, arg1, arg2): - """My special little hook that you can customize. - """ - - - class Plugin_1(object): - """A hook implementation namespace. - """ - - @hookimpl - def myhook(self, arg1, arg2): - print("inside Plugin_1.myhook()") - return arg1 + arg2 - - - class Plugin_2(object): - """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) - - -Running this directly gets us:: - - $ python docs/examples/toy-example.py - inside Plugin_2.myhook() - inside Plugin_1.myhook() - [-1, 3] - - -.. badges - -.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg - :target: https://travis-ci.org/pytest-dev/pluggy - -.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg - :target: https://ci.appveyor.com/project/pytestbot/pluggy - -.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg - :target: https://anaconda.org/conda-forge/pytest - -.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg - :alt: Join the chat at https://gitter.im/pytest-dev/pluggy - :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black - -.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg - :target: https://codecov.io/gh/pytest-dev/pluggy - :alt: Code coverage Status - -.. links -.. _pytest: - http://pytest.org -.. _tox: - https://tox.readthedocs.org -.. _devpi: - http://doc.devpi.net -.. _read the docs: - https://pluggy.readthedocs.io/en/latest/ +==================================================== +pluggy - A minimalist production ready plugin system +==================================================== + +|pypi| |conda-forge| |versions| |travis| |appveyor| |gitter| |black| |codecov| + +This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. + +Please `read the docs`_ to learn more! + +A definitive example +==================== +.. code-block:: python + + import pluggy + + hookspec = pluggy.HookspecMarker("myproject") + hookimpl = pluggy.HookimplMarker("myproject") + + + class MySpec(object): + """A hook specification namespace. + """ + + @hookspec + def myhook(self, arg1, arg2): + """My special little hook that you can customize. + """ + + + class Plugin_1(object): + """A hook implementation namespace. + """ + + @hookimpl + def myhook(self, arg1, arg2): + print("inside Plugin_1.myhook()") + return arg1 + arg2 + + + class Plugin_2(object): + """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) + + +Running this directly gets us:: + + $ python docs/examples/toy-example.py + inside Plugin_2.myhook() + inside Plugin_1.myhook() + [-1, 3] + + +.. badges + +.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg + :target: https://travis-ci.org/pytest-dev/pluggy + +.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg + :target: https://ci.appveyor.com/project/pytestbot/pluggy + +.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg + :target: https://anaconda.org/conda-forge/pytest + +.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg + :alt: Join the chat at https://gitter.im/pytest-dev/pluggy + :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + +.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pluggy + :alt: Code coverage Status + +.. links +.. _pytest: + http://pytest.org +.. _tox: + https://tox.readthedocs.org +.. _devpi: + http://doc.devpi.net +.. _read the docs: + https://pluggy.readthedocs.io/en/latest/ diff --git a/contrib/python/pluggy/py2/pluggy/_tracing.py b/contrib/python/pluggy/py2/pluggy/_tracing.py index 8b9715f026..b041b7fa0b 100644 --- a/contrib/python/pluggy/py2/pluggy/_tracing.py +++ b/contrib/python/pluggy/py2/pluggy/_tracing.py @@ -5,14 +5,14 @@ Tracing utils class TagTracer(object): def __init__(self): - self._tags2proc = {} - self._writer = None + self._tags2proc = {} + self._writer = None self.indent = 0 def get(self, name): return TagTracerSub(self, (name,)) - def _format_message(self, tags, args): + def _format_message(self, tags, args): if isinstance(args[-1], dict): extra = args[-1] args = args[:-1] @@ -27,27 +27,27 @@ class TagTracer(object): for name, value in extra.items(): lines.append("%s %s: %s\n" % (indent, name, value)) - return "".join(lines) - - def _processmessage(self, tags, args): - if self._writer is not None and args: - self._writer(self._format_message(tags, args)) + return "".join(lines) + + def _processmessage(self, tags, args): + if self._writer is not None and args: + self._writer(self._format_message(tags, args)) try: - processor = self._tags2proc[tags] + processor = self._tags2proc[tags] except KeyError: pass - else: - processor(tags, args) + else: + processor(tags, args) def setwriter(self, writer): - self._writer = writer + self._writer = writer def setprocessor(self, tags, processor): if isinstance(tags, str): tags = tuple(tags.split(":")) else: assert isinstance(tags, tuple) - self._tags2proc[tags] = processor + self._tags2proc[tags] = processor class TagTracerSub(object): @@ -56,7 +56,7 @@ class TagTracerSub(object): self.tags = tags def __call__(self, *args): - self.root._processmessage(self.tags, args) + self.root._processmessage(self.tags, args) def get(self, name): return self.__class__(self.root, self.tags + (name,)) diff --git a/contrib/python/pluggy/py2/pluggy/_version.py b/contrib/python/pluggy/py2/pluggy/_version.py index 2ba90cb83e..846afe23bf 100644 --- a/contrib/python/pluggy/py2/pluggy/_version.py +++ b/contrib/python/pluggy/py2/pluggy/_version.py @@ -1,4 +1,4 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '0.13.1' +version = '0.13.1' diff --git a/contrib/python/pluggy/py2/pluggy/hooks.py b/contrib/python/pluggy/py2/pluggy/hooks.py index 0a1c287198..fa66eb80df 100644 --- a/contrib/python/pluggy/py2/pluggy/hooks.py +++ b/contrib/python/pluggy/py2/pluggy/hooks.py @@ -2,7 +2,7 @@ Internal hook annotation, representation and calling machinery. """ import inspect -import sys +import sys import warnings from .callers import _legacymulticall, _multicall @@ -11,8 +11,8 @@ class HookspecMarker(object): """ Decorator helper class for marking functions as hook specifications. You can instantiate it with a project_name to get a decorator. - Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions - if the :py:class:`.PluginManager` uses the same project_name. + Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions + if the :py:class:`.PluginManager` uses the same project_name. """ def __init__(self, project_name): @@ -22,15 +22,15 @@ class HookspecMarker(object): self, function=None, firstresult=False, historic=False, warn_on_impl=None ): """ if passed a function, directly sets attributes on the function - which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`. - If passed no function, returns a decorator which can be applied to a function + which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`. + If passed no function, returns a decorator which can be applied to a function later using the attributes supplied. - If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered + If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered hook implementation functions) will stop at I<=N when the I'th function - returns a non-``None`` result. + returns a non-``None`` result. - If ``historic`` is ``True`` calls to a hook will be memorized and replayed + If ``historic`` is ``True`` calls to a hook will be memorized and replayed on later registered plugins. """ @@ -58,9 +58,9 @@ class HookspecMarker(object): class HookimplMarker(object): """ Decorator helper class for marking functions as hook implementations. - You can instantiate with a ``project_name`` to get a decorator. - Calling :py:meth:`.PluginManager.register` later will discover all marked functions - if the :py:class:`.PluginManager` uses the same project_name. + You can instantiate with a ``project_name`` to get a decorator. + Calling :py:meth:`.PluginManager.register` later will discover all marked functions + if the :py:class:`.PluginManager` uses the same project_name. """ def __init__(self, project_name): @@ -76,25 +76,25 @@ class HookimplMarker(object): ): """ if passed a function, directly sets attributes on the function - which will make it discoverable to :py:meth:`.PluginManager.register`. - If passed no function, returns a decorator which can be applied to a - function later using the attributes supplied. + which will make it discoverable to :py:meth:`.PluginManager.register`. + If passed no function, returns a decorator which can be applied to a + function later using the attributes supplied. - If ``optionalhook`` is ``True`` a missing matching hook specification will not result + If ``optionalhook`` is ``True`` a missing matching hook specification will not result in an error (by default it is an error if no matching spec is found). - If ``tryfirst`` is ``True`` this hook implementation will run as early as possible - in the chain of N hook implementations for a specification. + If ``tryfirst`` is ``True`` this hook implementation will run as early as possible + in the chain of N hook implementations for a specification. - If ``trylast`` is ``True`` this hook implementation will run as late as possible + If ``trylast`` is ``True`` this hook implementation will run as late as possible in the chain of N hook implementations. - If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly - one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper - function is run. The code after the ``yield`` is run after all non-hookwrapper - function have run. The ``yield`` receives a :py:class:`.callers._Result` object - representing the exception or result outcome of the inner calls (including other - hookwrapper calls). + If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly + one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper + function is run. The code after the ``yield`` is run after all non-hookwrapper + function have run. The ``yield`` receives a :py:class:`.callers._Result` object + representing the exception or result outcome of the inner calls (including other + hookwrapper calls). """ @@ -136,9 +136,9 @@ else: return inspect.getargspec(func) -_PYPY3 = hasattr(sys, "pypy_version_info") and sys.version_info.major == 3 - - +_PYPY3 = hasattr(sys, "pypy_version_info") and sys.version_info.major == 3 + + def varnames(func): """Return tuple of positional and keywrord argument names for a function, method, class or callable. @@ -161,7 +161,7 @@ def varnames(func): try: func = getattr(func, "__call__", func) except Exception: - return (), () + return (), () try: # func MUST be a function or method here or we won't parse any args spec = _getargspec(func) @@ -171,24 +171,24 @@ def varnames(func): args, defaults = tuple(spec.args), spec.defaults if defaults: index = -len(defaults) - args, kwargs = args[:index], tuple(args[index:]) + args, kwargs = args[:index], tuple(args[index:]) else: - kwargs = () + kwargs = () # strip any implicit instance arg - # pypy3 uses "obj" instead of "self" for default dunder methods - implicit_names = ("self",) if not _PYPY3 else ("self", "obj") + # pypy3 uses "obj" instead of "self" for default dunder methods + implicit_names = ("self",) if not _PYPY3 else ("self", "obj") if args: if inspect.ismethod(func) or ( - "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names + "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names ): args = args[1:] try: - cache["_varnames"] = args, kwargs + cache["_varnames"] = args, kwargs except TypeError: pass - return args, kwargs + return args, kwargs class _HookRelay(object): @@ -290,7 +290,7 @@ class _HookCaller(object): for all plugins which will be registered afterwards. If ``result_callback`` is not ``None`` it will be called for for each - non-``None`` result obtained from a hook implementation. + non-``None`` result obtained from a hook implementation. .. note:: The ``proc`` argument is now deprecated. @@ -314,7 +314,7 @@ class _HookCaller(object): def call_extra(self, methods, kwargs): """ Call the hook with some additional temporarily participating - methods using the specified ``kwargs`` as call parameters. """ + methods using the specified ``kwargs`` as call parameters. """ old = list(self._nonwrappers), list(self._wrappers) for method in methods: opts = dict(hookwrapper=False, trylast=False, tryfirst=False) diff --git a/contrib/python/pluggy/py2/pluggy/manager.py b/contrib/python/pluggy/py2/pluggy/manager.py index 07b42cba2d..28b2ed0df7 100644 --- a/contrib/python/pluggy/py2/pluggy/manager.py +++ b/contrib/python/pluggy/py2/pluggy/manager.py @@ -1,16 +1,16 @@ import inspect -import sys +import sys from . import _tracing -from .callers import _Result +from .callers import _Result from .hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts import warnings -if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata -else: - import importlib_metadata - +if sys.version_info >= (3, 8): + from importlib import metadata as importlib_metadata +else: + import importlib_metadata + def _warn_for_function(warning, function): warnings.warn_explicit( warning, @@ -32,53 +32,53 @@ class PluginValidationError(Exception): super(Exception, self).__init__(message) -class DistFacade(object): - """Emulate a pkg_resources Distribution""" - - def __init__(self, dist): - self._dist = dist - - @property - def project_name(self): - return self.metadata["name"] - - def __getattr__(self, attr, default=None): - return getattr(self._dist, attr, default) - - def __dir__(self): - return sorted(dir(self._dist) + ["_dist", "project_name"]) - - +class DistFacade(object): + """Emulate a pkg_resources Distribution""" + + def __init__(self, dist): + self._dist = dist + + @property + def project_name(self): + return self.metadata["name"] + + def __getattr__(self, attr, default=None): + return getattr(self._dist, attr, default) + + def __dir__(self): + return sorted(dir(self._dist) + ["_dist", "project_name"]) + + class PluginManager(object): - """ Core :py:class:`.PluginManager` class which manages registration + """ Core :py:class:`.PluginManager` class which manages registration of plugin objects and 1:N hook calling. - You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class) - <.PluginManager.add_hookspecs>`. + You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class) + <.PluginManager.add_hookspecs>`. You can register plugin objects (which contain hooks) by calling - :py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager` - is initialized with a prefix that is searched for in the names of the dict - of registered plugin objects. + :py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager` + is initialized with a prefix that is searched for in the names of the dict + of registered plugin objects. - For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing` + For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing` which will subsequently send debug information to the trace helper. """ def __init__(self, project_name, implprefix=None): """If ``implprefix`` is given implementation functions - will be recognized if their name matches the ``implprefix``. """ + will be recognized if their name matches the ``implprefix``. """ self.project_name = project_name self._name2plugin = {} self._plugin2hookcallers = {} self._plugin_distinfo = [] self.trace = _tracing.TagTracer().get("pluginmanage") - self.hook = _HookRelay() + self.hook = _HookRelay() if implprefix is not None: warnings.warn( "Support for the `implprefix` arg is now deprecated and will " "be removed in an upcoming release. Please use HookimplMarker.", DeprecationWarning, - stacklevel=2, + stacklevel=2, ) self._implprefix = implprefix self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall( @@ -93,9 +93,9 @@ class PluginManager(object): return self._inner_hookexec(hook, methods, kwargs) def register(self, plugin, name=None): - """ Register a plugin and return its canonical name or ``None`` if the name - is blocked from registering. Raise a :py:class:`ValueError` if the plugin - is already registered. """ + """ Register a plugin and return its canonical name or ``None`` if the name + is blocked from registering. Raise a :py:class:`ValueError` if the plugin + is already registered. """ plugin_name = name or self.get_canonical_name(plugin) if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: @@ -177,11 +177,11 @@ class PluginManager(object): self._name2plugin[name] = None def is_blocked(self, name): - """ return ``True`` if the given plugin name is blocked. """ + """ return ``True`` if the given plugin name is blocked. """ return name in self._name2plugin and self._name2plugin[name] is None def add_hookspecs(self, module_or_class): - """ add new hook specifications defined in the given ``module_or_class``. + """ add new hook specifications defined in the given ``module_or_class``. Functions are recognized if they have been decorated accordingly. """ names = [] for name in dir(module_or_class): @@ -212,27 +212,27 @@ class PluginManager(object): return set(self._plugin2hookcallers) def is_registered(self, plugin): - """ Return ``True`` if the plugin is already registered. """ + """ Return ``True`` if the plugin is already registered. """ return plugin in self._plugin2hookcallers def get_canonical_name(self, plugin): """ Return canonical name for a plugin object. Note that a plugin may be registered under a different name which was specified - by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`. - To obtain the name of an registered plugin use :py:meth:`get_name(plugin) - <.PluginManager.get_name>` instead.""" + by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`. + To obtain the name of an registered plugin use :py:meth:`get_name(plugin) + <.PluginManager.get_name>` instead.""" return getattr(plugin, "__name__", None) or str(id(plugin)) def get_plugin(self, name): - """ Return a plugin or ``None`` for the given name. """ + """ Return a plugin or ``None`` for the given name. """ return self._name2plugin.get(name) def has_plugin(self, name): - """ Return ``True`` if a plugin with the given name is registered. """ + """ Return ``True`` if a plugin with the given name is registered. """ return self.get_plugin(name) is not None def get_name(self, plugin): - """ Return name for registered plugin or ``None`` if not registered. """ + """ Return name for registered plugin or ``None`` if not registered. """ for name, val in self._name2plugin.items(): if plugin == val: return name @@ -264,7 +264,7 @@ class PluginManager(object): def check_pending(self): """ Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`.""" + a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`.""" for name in self.hook.__dict__: if name[0] != "_": hook = getattr(self.hook, name) @@ -277,30 +277,30 @@ class PluginManager(object): % (name, hookimpl.plugin), ) - def load_setuptools_entrypoints(self, group, name=None): - """ Load modules from querying the specified setuptools ``group``. - - :param str group: entry point group to load plugins - :param str name: if given, loads only plugins with the given ``name``. - :rtype: int - :return: return the number of loaded plugins by this call. - """ - count = 0 - for dist in importlib_metadata.distributions(): - for ep in dist.entry_points: - if ( - ep.group != group - or (name is not None and ep.name != name) - # already registered - or self.get_plugin(ep.name) - or self.is_blocked(ep.name) - ): - continue + def load_setuptools_entrypoints(self, group, name=None): + """ Load modules from querying the specified setuptools ``group``. + + :param str group: entry point group to load plugins + :param str name: if given, loads only plugins with the given ``name``. + :rtype: int + :return: return the number of loaded plugins by this call. + """ + count = 0 + for dist in importlib_metadata.distributions(): + for ep in dist.entry_points: + if ( + ep.group != group + or (name is not None and ep.name != name) + # already registered + or self.get_plugin(ep.name) + or self.is_blocked(ep.name) + ): + continue plugin = ep.load() - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((plugin, DistFacade(dist))) - count += 1 - return count + self.register(plugin, name=ep.name) + self._plugin_distinfo.append((plugin, DistFacade(dist))) + count += 1 + return count def list_plugin_distinfo(self): """ return list of distinfo/plugin tuples for all setuptools registered @@ -325,27 +325,27 @@ class PluginManager(object): of HookImpl instances and the keyword arguments for the hook call. ``after(outcome, hook_name, hook_impls, kwargs)`` receives the - same arguments as ``before`` but also a :py:class:`pluggy.callers._Result` object + same arguments as ``before`` but also a :py:class:`pluggy.callers._Result` object which represents the result of the overall hook call. """ - oldcall = self._inner_hookexec - - def traced_hookexec(hook, hook_impls, kwargs): - before(hook.name, hook_impls, kwargs) - outcome = _Result.from_call(lambda: oldcall(hook, hook_impls, kwargs)) - after(outcome, hook.name, hook_impls, kwargs) - return outcome.get_result() - - self._inner_hookexec = traced_hookexec - - def undo(): - self._inner_hookexec = oldcall - - return undo - + oldcall = self._inner_hookexec + + def traced_hookexec(hook, hook_impls, kwargs): + before(hook.name, hook_impls, kwargs) + outcome = _Result.from_call(lambda: oldcall(hook, hook_impls, kwargs)) + after(outcome, hook.name, hook_impls, kwargs) + return outcome.get_result() + + self._inner_hookexec = traced_hookexec + + def undo(): + self._inner_hookexec = oldcall + + return undo + def enable_tracing(self): """ enable tracing of hook calls and return an undo function. """ - hooktrace = self.trace.root.get("hook") + hooktrace = self.trace.root.get("hook") def before(hook_name, methods, kwargs): hooktrace.root.indent += 1 @@ -359,7 +359,7 @@ class PluginManager(object): return self.add_hookcall_monitoring(before, after) def subset_hook_caller(self, name, remove_plugins): - """ Return a new :py:class:`.hooks._HookCaller` instance for the named method + """ Return a new :py:class:`.hooks._HookCaller` instance for the named method which manages calls to all registered plugins except the ones from remove_plugins. """ orig = getattr(self.hook, name) diff --git a/contrib/python/pluggy/py2/tests/benchmark.py b/contrib/python/pluggy/py2/tests/benchmark.py index aa8de92911..cb99660adb 100644 --- a/contrib/python/pluggy/py2/tests/benchmark.py +++ b/contrib/python/pluggy/py2/tests/benchmark.py @@ -1,51 +1,51 @@ -""" -Benchmarking and performance tests. -""" -import pytest -from pluggy import HookspecMarker, HookimplMarker -from pluggy.hooks import HookImpl -from pluggy.callers import _multicall, _legacymulticall - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def MC(methods, kwargs, callertype, firstresult=False): - hookfuncs = [] - for method in methods: - f = HookImpl(None, "<temp>", method, method.example_impl) - hookfuncs.append(f) - return callertype(hookfuncs, kwargs, firstresult=firstresult) - - -@hookimpl -def hook(arg1, arg2, arg3): - return arg1, arg2, arg3 - - -@hookimpl(hookwrapper=True) -def wrapper(arg1, arg2, arg3): - yield - - -@pytest.fixture(params=[10, 100], ids="hooks={}".format) -def hooks(request): - return [hook for i in range(request.param)] - - -@pytest.fixture(params=[10, 100], ids="wrappers={}".format) -def wrappers(request): - return [wrapper for i in range(request.param)] - - -@pytest.fixture(params=[_multicall, _legacymulticall], ids=lambda item: item.__name__) -def callertype(request): - return request.param - - -def inner_exec(methods, callertype): - return MC(methods, {"arg1": 1, "arg2": 2, "arg3": 3}, callertype) - - -def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype): - benchmark(inner_exec, hooks + wrappers, callertype) +""" +Benchmarking and performance tests. +""" +import pytest +from pluggy import HookspecMarker, HookimplMarker +from pluggy.hooks import HookImpl +from pluggy.callers import _multicall, _legacymulticall + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def MC(methods, kwargs, callertype, firstresult=False): + hookfuncs = [] + for method in methods: + f = HookImpl(None, "<temp>", method, method.example_impl) + hookfuncs.append(f) + return callertype(hookfuncs, kwargs, firstresult=firstresult) + + +@hookimpl +def hook(arg1, arg2, arg3): + return arg1, arg2, arg3 + + +@hookimpl(hookwrapper=True) +def wrapper(arg1, arg2, arg3): + yield + + +@pytest.fixture(params=[10, 100], ids="hooks={}".format) +def hooks(request): + return [hook for i in range(request.param)] + + +@pytest.fixture(params=[10, 100], ids="wrappers={}".format) +def wrappers(request): + return [wrapper for i in range(request.param)] + + +@pytest.fixture(params=[_multicall, _legacymulticall], ids=lambda item: item.__name__) +def callertype(request): + return request.param + + +def inner_exec(methods, callertype): + return MC(methods, {"arg1": 1, "arg2": 2, "arg3": 3}, callertype) + + +def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype): + benchmark(inner_exec, hooks + wrappers, callertype) diff --git a/contrib/python/pluggy/py2/tests/conftest.py b/contrib/python/pluggy/py2/tests/conftest.py index e44667ef5f..3e40b0f9ca 100644 --- a/contrib/python/pluggy/py2/tests/conftest.py +++ b/contrib/python/pluggy/py2/tests/conftest.py @@ -1,26 +1,26 @@ -import pytest - - -@pytest.fixture( - params=[lambda spec: spec, lambda spec: spec()], - ids=["spec-is-class", "spec-is-instance"], -) -def he_pm(request, pm): - from pluggy import HookspecMarker - - hookspec = HookspecMarker("example") - - class Hooks(object): - @hookspec - def he_method1(self, arg): - return arg + 1 - - pm.add_hookspecs(request.param(Hooks)) - return pm - - -@pytest.fixture -def pm(): - from pluggy import PluginManager - - return PluginManager("example") +import pytest + + +@pytest.fixture( + params=[lambda spec: spec, lambda spec: spec()], + ids=["spec-is-class", "spec-is-instance"], +) +def he_pm(request, pm): + from pluggy import HookspecMarker + + hookspec = HookspecMarker("example") + + class Hooks(object): + @hookspec + def he_method1(self, arg): + return arg + 1 + + pm.add_hookspecs(request.param(Hooks)) + return pm + + +@pytest.fixture +def pm(): + from pluggy import PluginManager + + return PluginManager("example") diff --git a/contrib/python/pluggy/py2/tests/test_deprecations.py b/contrib/python/pluggy/py2/tests/test_deprecations.py index 7151921b66..72048397d7 100644 --- a/contrib/python/pluggy/py2/tests/test_deprecations.py +++ b/contrib/python/pluggy/py2/tests/test_deprecations.py @@ -1,54 +1,54 @@ -""" -Deprecation warnings testing roundup. -""" -import pytest -from pluggy.callers import _Result -from pluggy import PluginManager, HookimplMarker, HookspecMarker - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_result_deprecated(): - r = _Result(10, None) - with pytest.deprecated_call(): - assert r.result == 10 - - -def test_implprefix_deprecated(): - with pytest.deprecated_call(): - pm = PluginManager("blah", implprefix="blah_") - - class Plugin: - def blah_myhook(self, arg1): - return arg1 - - with pytest.deprecated_call(): - pm.register(Plugin()) - - -def test_callhistoric_proc_deprecated(pm): - """``proc`` kwarg to `PluginMananger.call_historic()` is now officially - deprecated. - """ - - class P1(object): - @hookspec(historic=True) - @hookimpl - def m(self, x): - pass - - p1 = P1() - pm.add_hookspecs(p1) - pm.register(p1) - with pytest.deprecated_call(): - pm.hook.m.call_historic(kwargs=dict(x=10), proc=lambda res: res) - - -def test_multicall_deprecated(pm): - class P1(object): - @hookimpl - def m(self, __multicall__, x): - pass - - pytest.deprecated_call(pm.register, P1()) +""" +Deprecation warnings testing roundup. +""" +import pytest +from pluggy.callers import _Result +from pluggy import PluginManager, HookimplMarker, HookspecMarker + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_result_deprecated(): + r = _Result(10, None) + with pytest.deprecated_call(): + assert r.result == 10 + + +def test_implprefix_deprecated(): + with pytest.deprecated_call(): + pm = PluginManager("blah", implprefix="blah_") + + class Plugin: + def blah_myhook(self, arg1): + return arg1 + + with pytest.deprecated_call(): + pm.register(Plugin()) + + +def test_callhistoric_proc_deprecated(pm): + """``proc`` kwarg to `PluginMananger.call_historic()` is now officially + deprecated. + """ + + class P1(object): + @hookspec(historic=True) + @hookimpl + def m(self, x): + pass + + p1 = P1() + pm.add_hookspecs(p1) + pm.register(p1) + with pytest.deprecated_call(): + pm.hook.m.call_historic(kwargs=dict(x=10), proc=lambda res: res) + + +def test_multicall_deprecated(pm): + class P1(object): + @hookimpl + def m(self, __multicall__, x): + pass + + pytest.deprecated_call(pm.register, P1()) diff --git a/contrib/python/pluggy/py2/tests/test_details.py b/contrib/python/pluggy/py2/tests/test_details.py index fc49fc308d..4745a155df 100644 --- a/contrib/python/pluggy/py2/tests/test_details.py +++ b/contrib/python/pluggy/py2/tests/test_details.py @@ -1,135 +1,135 @@ -import warnings -import pytest -from pluggy import PluginManager, HookimplMarker, HookspecMarker - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_parse_hookimpl_override(): - class MyPluginManager(PluginManager): - def parse_hookimpl_opts(self, module_or_class, name): - opts = PluginManager.parse_hookimpl_opts(self, module_or_class, name) - if opts is None: - if name.startswith("x1"): - opts = {} - return opts - - class Plugin(object): - def x1meth(self): - pass - - @hookimpl(hookwrapper=True, tryfirst=True) - def x1meth2(self): - pass - - class Spec(object): - @hookspec - def x1meth(self): - pass - - @hookspec - def x1meth2(self): - pass - - pm = MyPluginManager(hookspec.project_name) - pm.register(Plugin()) - pm.add_hookspecs(Spec) - assert not pm.hook.x1meth._nonwrappers[0].hookwrapper - assert not pm.hook.x1meth._nonwrappers[0].tryfirst - assert not pm.hook.x1meth._nonwrappers[0].trylast - assert not pm.hook.x1meth._nonwrappers[0].optionalhook - - assert pm.hook.x1meth2._wrappers[0].tryfirst - assert pm.hook.x1meth2._wrappers[0].hookwrapper - - -def test_warn_when_deprecated_specified(recwarn): - warning = DeprecationWarning("foo is deprecated") - - class Spec(object): - @hookspec(warn_on_impl=warning) - def foo(self): - pass - - class Plugin(object): - @hookimpl - def foo(self): - pass - - pm = PluginManager(hookspec.project_name) - pm.add_hookspecs(Spec) - - with pytest.warns(DeprecationWarning) as records: - pm.register(Plugin()) - (record,) = records - assert record.message is warning - assert record.filename == Plugin.foo.__code__.co_filename - assert record.lineno == Plugin.foo.__code__.co_firstlineno - - -def test_plugin_getattr_raises_errors(): - """Pluggy must be able to handle plugins which raise weird exceptions - when getattr() gets called (#11). - """ - - class DontTouchMe(object): - def __getattr__(self, x): - raise Exception("cant touch me") - - class Module(object): - pass - - module = Module() - module.x = DontTouchMe() - - pm = PluginManager(hookspec.project_name) - # register() would raise an error - pm.register(module, "donttouch") - assert pm.get_plugin("donttouch") is module - - -def test_warning_on_call_vs_hookspec_arg_mismatch(): - """Verify that is a hook is called with less arguments then defined in the - spec that a warning is emitted. - """ - - class Spec: - @hookspec - def myhook(self, arg1, arg2): - pass - - class Plugin: - @hookimpl - def myhook(self, arg1): - pass - - pm = PluginManager(hookspec.project_name) - pm.register(Plugin()) - pm.add_hookspecs(Spec()) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - - # calling should trigger a warning - pm.hook.myhook(arg1=1) - - assert len(warns) == 1 - warning = warns[-1] - assert issubclass(warning.category, Warning) - assert "Argument(s) ('arg2',)" in str(warning.message) - - -def test_repr(): - class Plugin: - @hookimpl - def myhook(): - raise NotImplementedError() - - pm = PluginManager(hookspec.project_name) - - plugin = Plugin() - pname = pm.register(plugin) - assert repr(pm.hook.myhook._nonwrappers[0]) == ( - "<HookImpl plugin_name=%r, plugin=%r>" % (pname, plugin) - ) +import warnings +import pytest +from pluggy import PluginManager, HookimplMarker, HookspecMarker + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_parse_hookimpl_override(): + class MyPluginManager(PluginManager): + def parse_hookimpl_opts(self, module_or_class, name): + opts = PluginManager.parse_hookimpl_opts(self, module_or_class, name) + if opts is None: + if name.startswith("x1"): + opts = {} + return opts + + class Plugin(object): + def x1meth(self): + pass + + @hookimpl(hookwrapper=True, tryfirst=True) + def x1meth2(self): + pass + + class Spec(object): + @hookspec + def x1meth(self): + pass + + @hookspec + def x1meth2(self): + pass + + pm = MyPluginManager(hookspec.project_name) + pm.register(Plugin()) + pm.add_hookspecs(Spec) + assert not pm.hook.x1meth._nonwrappers[0].hookwrapper + assert not pm.hook.x1meth._nonwrappers[0].tryfirst + assert not pm.hook.x1meth._nonwrappers[0].trylast + assert not pm.hook.x1meth._nonwrappers[0].optionalhook + + assert pm.hook.x1meth2._wrappers[0].tryfirst + assert pm.hook.x1meth2._wrappers[0].hookwrapper + + +def test_warn_when_deprecated_specified(recwarn): + warning = DeprecationWarning("foo is deprecated") + + class Spec(object): + @hookspec(warn_on_impl=warning) + def foo(self): + pass + + class Plugin(object): + @hookimpl + def foo(self): + pass + + pm = PluginManager(hookspec.project_name) + pm.add_hookspecs(Spec) + + with pytest.warns(DeprecationWarning) as records: + pm.register(Plugin()) + (record,) = records + assert record.message is warning + assert record.filename == Plugin.foo.__code__.co_filename + assert record.lineno == Plugin.foo.__code__.co_firstlineno + + +def test_plugin_getattr_raises_errors(): + """Pluggy must be able to handle plugins which raise weird exceptions + when getattr() gets called (#11). + """ + + class DontTouchMe(object): + def __getattr__(self, x): + raise Exception("cant touch me") + + class Module(object): + pass + + module = Module() + module.x = DontTouchMe() + + pm = PluginManager(hookspec.project_name) + # register() would raise an error + pm.register(module, "donttouch") + assert pm.get_plugin("donttouch") is module + + +def test_warning_on_call_vs_hookspec_arg_mismatch(): + """Verify that is a hook is called with less arguments then defined in the + spec that a warning is emitted. + """ + + class Spec: + @hookspec + def myhook(self, arg1, arg2): + pass + + class Plugin: + @hookimpl + def myhook(self, arg1): + pass + + pm = PluginManager(hookspec.project_name) + pm.register(Plugin()) + pm.add_hookspecs(Spec()) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + + # calling should trigger a warning + pm.hook.myhook(arg1=1) + + assert len(warns) == 1 + warning = warns[-1] + assert issubclass(warning.category, Warning) + assert "Argument(s) ('arg2',)" in str(warning.message) + + +def test_repr(): + class Plugin: + @hookimpl + def myhook(): + raise NotImplementedError() + + pm = PluginManager(hookspec.project_name) + + plugin = Plugin() + pname = pm.register(plugin) + assert repr(pm.hook.myhook._nonwrappers[0]) == ( + "<HookImpl plugin_name=%r, plugin=%r>" % (pname, plugin) + ) diff --git a/contrib/python/pluggy/py2/tests/test_helpers.py b/contrib/python/pluggy/py2/tests/test_helpers.py index 64c3edaa0a..63e56ebde8 100644 --- a/contrib/python/pluggy/py2/tests/test_helpers.py +++ b/contrib/python/pluggy/py2/tests/test_helpers.py @@ -1,90 +1,90 @@ -from pluggy.hooks import varnames -from pluggy.manager import _formatdef - -import sys -import pytest - - -def test_varnames(): - def f(x): - i = 3 # noqa - - class A(object): - def f(self, y): - pass - - class B(object): - def __call__(self, z): - pass - - assert varnames(f) == (("x",), ()) - assert varnames(A().f) == (("y",), ()) - assert varnames(B()) == (("z",), ()) - - -def test_varnames_default(): - def f(x, y=3): - pass - - assert varnames(f) == (("x",), ("y",)) - - -def test_varnames_class(): - class C(object): - def __init__(self, x): - pass - - class D(object): - pass - - class E(object): - def __init__(self, x): - pass - - class F(object): - pass - - assert varnames(C) == (("x",), ()) - assert varnames(D) == ((), ()) - assert varnames(E) == (("x",), ()) - assert varnames(F) == ((), ()) - - -@pytest.mark.skipif( - sys.version_info < (3,), reason="Keyword only arguments are Python 3 only" -) -def test_varnames_keyword_only(): - # SyntaxError on Python 2, so we exec - ns = {} - exec( - "def f1(x, *, y): pass\n" - "def f2(x, *, y=3): pass\n" - "def f3(x=1, *, y=3): pass\n", - ns, - ) - - assert varnames(ns["f1"]) == (("x",), ()) - assert varnames(ns["f2"]) == (("x",), ()) - assert varnames(ns["f3"]) == ((), ("x",)) - - -def test_formatdef(): - def function1(): - pass - - assert _formatdef(function1) == "function1()" - - def function2(arg1): - pass - - assert _formatdef(function2) == "function2(arg1)" - - def function3(arg1, arg2="qwe"): - pass - - assert _formatdef(function3) == "function3(arg1, arg2='qwe')" - - def function4(arg1, *args, **kwargs): - pass - - assert _formatdef(function4) == "function4(arg1, *args, **kwargs)" +from pluggy.hooks import varnames +from pluggy.manager import _formatdef + +import sys +import pytest + + +def test_varnames(): + def f(x): + i = 3 # noqa + + class A(object): + def f(self, y): + pass + + class B(object): + def __call__(self, z): + pass + + assert varnames(f) == (("x",), ()) + assert varnames(A().f) == (("y",), ()) + assert varnames(B()) == (("z",), ()) + + +def test_varnames_default(): + def f(x, y=3): + pass + + assert varnames(f) == (("x",), ("y",)) + + +def test_varnames_class(): + class C(object): + def __init__(self, x): + pass + + class D(object): + pass + + class E(object): + def __init__(self, x): + pass + + class F(object): + pass + + assert varnames(C) == (("x",), ()) + assert varnames(D) == ((), ()) + assert varnames(E) == (("x",), ()) + assert varnames(F) == ((), ()) + + +@pytest.mark.skipif( + sys.version_info < (3,), reason="Keyword only arguments are Python 3 only" +) +def test_varnames_keyword_only(): + # SyntaxError on Python 2, so we exec + ns = {} + exec( + "def f1(x, *, y): pass\n" + "def f2(x, *, y=3): pass\n" + "def f3(x=1, *, y=3): pass\n", + ns, + ) + + assert varnames(ns["f1"]) == (("x",), ()) + assert varnames(ns["f2"]) == (("x",), ()) + assert varnames(ns["f3"]) == ((), ("x",)) + + +def test_formatdef(): + def function1(): + pass + + assert _formatdef(function1) == "function1()" + + def function2(arg1): + pass + + assert _formatdef(function2) == "function2(arg1)" + + def function3(arg1, arg2="qwe"): + pass + + assert _formatdef(function3) == "function3(arg1, arg2='qwe')" + + def function4(arg1, *args, **kwargs): + pass + + assert _formatdef(function4) == "function4(arg1, *args, **kwargs)" diff --git a/contrib/python/pluggy/py2/tests/test_hookcaller.py b/contrib/python/pluggy/py2/tests/test_hookcaller.py index 5664f2bbf6..87ab929f2f 100644 --- a/contrib/python/pluggy/py2/tests/test_hookcaller.py +++ b/contrib/python/pluggy/py2/tests/test_hookcaller.py @@ -1,215 +1,215 @@ -import pytest - -from pluggy import HookimplMarker, HookspecMarker -from pluggy.hooks import HookImpl - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -@pytest.fixture -def hc(pm): - class Hooks(object): - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - return pm.hook.he_method1 - - -@pytest.fixture -def addmeth(hc): - def addmeth(tryfirst=False, trylast=False, hookwrapper=False): - def wrap(func): - hookimpl(tryfirst=tryfirst, trylast=trylast, hookwrapper=hookwrapper)(func) - hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl)) - return func - - return wrap - - return addmeth - - -def funcs(hookmethods): - return [hookmethod.function for hookmethod in hookmethods] - - -def test_adding_nonwrappers(hc, addmeth): - @addmeth() - def he_method1(): - pass - - @addmeth() - def he_method2(): - pass - - @addmeth() - def he_method3(): - pass - - assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3] - - -def test_adding_nonwrappers_trylast(hc, addmeth): - @addmeth() - def he_method1_middle(): - pass - - @addmeth(trylast=True) - def he_method1(): - pass - - @addmeth() - def he_method1_b(): - pass - - assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] - - -def test_adding_nonwrappers_trylast3(hc, addmeth): - @addmeth() - def he_method1_a(): - pass - - @addmeth(trylast=True) - def he_method1_b(): - pass - - @addmeth() - def he_method1_c(): - pass - - @addmeth(trylast=True) - def he_method1_d(): - pass - - assert funcs(hc._nonwrappers) == [ - he_method1_d, - he_method1_b, - he_method1_a, - he_method1_c, - ] - - -def test_adding_nonwrappers_trylast2(hc, addmeth): - @addmeth() - def he_method1_middle(): - pass - - @addmeth() - def he_method1_b(): - pass - - @addmeth(trylast=True) - def he_method1(): - pass - - assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] - - -def test_adding_nonwrappers_tryfirst(hc, addmeth): - @addmeth(tryfirst=True) - def he_method1(): - pass - - @addmeth() - def he_method1_middle(): - pass - - @addmeth() - def he_method1_b(): - pass - - assert funcs(hc._nonwrappers) == [he_method1_middle, he_method1_b, he_method1] - - -def test_adding_wrappers_ordering(hc, addmeth): - @addmeth(hookwrapper=True) - def he_method1(): - pass - - @addmeth() - def he_method1_middle(): - pass - - @addmeth(hookwrapper=True) - def he_method3(): - pass - - assert funcs(hc._nonwrappers) == [he_method1_middle] - assert funcs(hc._wrappers) == [he_method1, he_method3] - - -def test_adding_wrappers_ordering_tryfirst(hc, addmeth): - @addmeth(hookwrapper=True, tryfirst=True) - def he_method1(): - pass - - @addmeth(hookwrapper=True) - def he_method2(): - pass - - assert hc._nonwrappers == [] - assert funcs(hc._wrappers) == [he_method2, he_method1] - - -def test_hookspec(pm): - class HookSpec(object): - @hookspec() - def he_myhook1(arg1): - pass - - @hookspec(firstresult=True) - def he_myhook2(arg1): - pass - - @hookspec(firstresult=False) - def he_myhook3(arg1): - pass - - pm.add_hookspecs(HookSpec) - assert not pm.hook.he_myhook1.spec.opts["firstresult"] - assert pm.hook.he_myhook2.spec.opts["firstresult"] - assert not pm.hook.he_myhook3.spec.opts["firstresult"] - - -@pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"]) -@pytest.mark.parametrize("val", [True, False]) -def test_hookimpl(name, val): - @hookimpl(**{name: val}) - def he_myhook1(arg1): - pass - - if val: - assert he_myhook1.example_impl.get(name) - else: - assert not hasattr(he_myhook1, name) - - -def test_hookrelay_registry(pm): - """Verify hook caller instances are registered by name onto the relay - and can be likewise unregistered.""" - - class Api(object): - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - hook = pm.hook - assert hasattr(hook, "hello") - assert repr(hook.hello).find("hello") != -1 - - class Plugin(object): - @hookimpl - def hello(self, arg): - return arg + 1 - - plugin = Plugin() - pm.register(plugin) - out = hook.hello(arg=3) - assert out == [4] - assert not hasattr(hook, "world") - pm.unregister(plugin) - assert hook.hello(arg=3) == [] +import pytest + +from pluggy import HookimplMarker, HookspecMarker +from pluggy.hooks import HookImpl + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +@pytest.fixture +def hc(pm): + class Hooks(object): + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + return pm.hook.he_method1 + + +@pytest.fixture +def addmeth(hc): + def addmeth(tryfirst=False, trylast=False, hookwrapper=False): + def wrap(func): + hookimpl(tryfirst=tryfirst, trylast=trylast, hookwrapper=hookwrapper)(func) + hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl)) + return func + + return wrap + + return addmeth + + +def funcs(hookmethods): + return [hookmethod.function for hookmethod in hookmethods] + + +def test_adding_nonwrappers(hc, addmeth): + @addmeth() + def he_method1(): + pass + + @addmeth() + def he_method2(): + pass + + @addmeth() + def he_method3(): + pass + + assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3] + + +def test_adding_nonwrappers_trylast(hc, addmeth): + @addmeth() + def he_method1_middle(): + pass + + @addmeth(trylast=True) + def he_method1(): + pass + + @addmeth() + def he_method1_b(): + pass + + assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] + + +def test_adding_nonwrappers_trylast3(hc, addmeth): + @addmeth() + def he_method1_a(): + pass + + @addmeth(trylast=True) + def he_method1_b(): + pass + + @addmeth() + def he_method1_c(): + pass + + @addmeth(trylast=True) + def he_method1_d(): + pass + + assert funcs(hc._nonwrappers) == [ + he_method1_d, + he_method1_b, + he_method1_a, + he_method1_c, + ] + + +def test_adding_nonwrappers_trylast2(hc, addmeth): + @addmeth() + def he_method1_middle(): + pass + + @addmeth() + def he_method1_b(): + pass + + @addmeth(trylast=True) + def he_method1(): + pass + + assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] + + +def test_adding_nonwrappers_tryfirst(hc, addmeth): + @addmeth(tryfirst=True) + def he_method1(): + pass + + @addmeth() + def he_method1_middle(): + pass + + @addmeth() + def he_method1_b(): + pass + + assert funcs(hc._nonwrappers) == [he_method1_middle, he_method1_b, he_method1] + + +def test_adding_wrappers_ordering(hc, addmeth): + @addmeth(hookwrapper=True) + def he_method1(): + pass + + @addmeth() + def he_method1_middle(): + pass + + @addmeth(hookwrapper=True) + def he_method3(): + pass + + assert funcs(hc._nonwrappers) == [he_method1_middle] + assert funcs(hc._wrappers) == [he_method1, he_method3] + + +def test_adding_wrappers_ordering_tryfirst(hc, addmeth): + @addmeth(hookwrapper=True, tryfirst=True) + def he_method1(): + pass + + @addmeth(hookwrapper=True) + def he_method2(): + pass + + assert hc._nonwrappers == [] + assert funcs(hc._wrappers) == [he_method2, he_method1] + + +def test_hookspec(pm): + class HookSpec(object): + @hookspec() + def he_myhook1(arg1): + pass + + @hookspec(firstresult=True) + def he_myhook2(arg1): + pass + + @hookspec(firstresult=False) + def he_myhook3(arg1): + pass + + pm.add_hookspecs(HookSpec) + assert not pm.hook.he_myhook1.spec.opts["firstresult"] + assert pm.hook.he_myhook2.spec.opts["firstresult"] + assert not pm.hook.he_myhook3.spec.opts["firstresult"] + + +@pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"]) +@pytest.mark.parametrize("val", [True, False]) +def test_hookimpl(name, val): + @hookimpl(**{name: val}) + def he_myhook1(arg1): + pass + + if val: + assert he_myhook1.example_impl.get(name) + else: + assert not hasattr(he_myhook1, name) + + +def test_hookrelay_registry(pm): + """Verify hook caller instances are registered by name onto the relay + and can be likewise unregistered.""" + + class Api(object): + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + hook = pm.hook + assert hasattr(hook, "hello") + assert repr(hook.hello).find("hello") != -1 + + class Plugin(object): + @hookimpl + def hello(self, arg): + return arg + 1 + + plugin = Plugin() + pm.register(plugin) + out = hook.hello(arg=3) + assert out == [4] + assert not hasattr(hook, "world") + pm.unregister(plugin) + assert hook.hello(arg=3) == [] diff --git a/contrib/python/pluggy/py2/tests/test_invocations.py b/contrib/python/pluggy/py2/tests/test_invocations.py index a6ec63cfc7..95d0be5bda 100644 --- a/contrib/python/pluggy/py2/tests/test_invocations.py +++ b/contrib/python/pluggy/py2/tests/test_invocations.py @@ -1,216 +1,216 @@ -import pytest -from pluggy import PluginValidationError, HookimplMarker, HookspecMarker - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_argmismatch(pm): - class Api(object): - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin(object): - @hookimpl - def hello(self, argwrong): - pass - - with pytest.raises(PluginValidationError) as exc: - pm.register(Plugin()) - - assert "argwrong" in str(exc.value) - - -def test_only_kwargs(pm): - class Api(object): - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - with pytest.raises(TypeError) as exc: - pm.hook.hello(3) - - comprehensible = "hook calling supports only keyword arguments" - assert comprehensible in str(exc.value) - - -def test_opt_in_args(pm): - """Verfiy that two hookimpls with mutex args can serve - under the same spec. - """ - - class Api(object): - @hookspec - def hello(self, arg1, arg2, common_arg): - "api hook 1" - - class Plugin1(object): - @hookimpl - def hello(self, arg1, common_arg): - return arg1 + common_arg - - class Plugin2(object): - @hookimpl - def hello(self, arg2, common_arg): - return arg2 + common_arg - - pm.add_hookspecs(Api) - pm.register(Plugin1()) - pm.register(Plugin2()) - - results = pm.hook.hello(arg1=1, arg2=2, common_arg=0) - assert results == [2, 1] - - -def test_call_order(pm): - class Api(object): - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1(object): - @hookimpl - def hello(self, arg): - return 1 - - class Plugin2(object): - @hookimpl - def hello(self, arg): - return 2 - - class Plugin3(object): - @hookimpl - def hello(self, arg): - return 3 - - class Plugin4(object): - @hookimpl(hookwrapper=True) - def hello(self, arg): - assert arg == 0 - outcome = yield - assert outcome.get_result() == [3, 2, 1] - - pm.register(Plugin1()) - pm.register(Plugin2()) - pm.register(Plugin3()) - pm.register(Plugin4()) # hookwrapper should get same list result - res = pm.hook.hello(arg=0) - assert res == [3, 2, 1] - - -def test_firstresult_definition(pm): - class Api(object): - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1(object): - @hookimpl - def hello(self, arg): - return arg + 1 - - class Plugin2(object): - @hookimpl - def hello(self, arg): - return arg - 1 - - class Plugin3(object): - @hookimpl - def hello(self, arg): - return None - - class Plugin4(object): - @hookimpl(hookwrapper=True) - def hello(self, arg): - assert arg == 3 - outcome = yield - assert outcome.get_result() == 2 - - pm.register(Plugin1()) # discarded - not the last registered plugin - pm.register(Plugin2()) # used as result - pm.register(Plugin3()) # None result is ignored - pm.register(Plugin4()) # hookwrapper should get same non-list result - res = pm.hook.hello(arg=3) - assert res == 2 - - -def test_firstresult_force_result(pm): - """Verify forcing a result in a wrapper. - """ - - class Api(object): - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1(object): - @hookimpl - def hello(self, arg): - return arg + 1 - - class Plugin2(object): - @hookimpl(hookwrapper=True) - def hello(self, arg): - assert arg == 3 - outcome = yield - assert outcome.get_result() == 4 - outcome.force_result(0) - - class Plugin3(object): - @hookimpl - def hello(self, arg): - return None - - pm.register(Plugin1()) - pm.register(Plugin2()) # wrapper - pm.register(Plugin3()) # ignored since returns None - res = pm.hook.hello(arg=3) - assert res == 0 # this result is forced and not a list - - -def test_firstresult_returns_none(pm): - """If None results are returned by underlying implementations ensure - the multi-call loop returns a None value. - """ - - class Api(object): - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1(object): - @hookimpl - def hello(self, arg): - return None - - pm.register(Plugin1()) - res = pm.hook.hello(arg=3) - assert res is None - - -def test_firstresult_no_plugin(pm): - """If no implementations/plugins have been registered for a firstresult - hook the multi-call loop should return a None value. - """ - - class Api(object): - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - res = pm.hook.hello(arg=3) - assert res is None +import pytest +from pluggy import PluginValidationError, HookimplMarker, HookspecMarker + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_argmismatch(pm): + class Api(object): + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin(object): + @hookimpl + def hello(self, argwrong): + pass + + with pytest.raises(PluginValidationError) as exc: + pm.register(Plugin()) + + assert "argwrong" in str(exc.value) + + +def test_only_kwargs(pm): + class Api(object): + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + with pytest.raises(TypeError) as exc: + pm.hook.hello(3) + + comprehensible = "hook calling supports only keyword arguments" + assert comprehensible in str(exc.value) + + +def test_opt_in_args(pm): + """Verfiy that two hookimpls with mutex args can serve + under the same spec. + """ + + class Api(object): + @hookspec + def hello(self, arg1, arg2, common_arg): + "api hook 1" + + class Plugin1(object): + @hookimpl + def hello(self, arg1, common_arg): + return arg1 + common_arg + + class Plugin2(object): + @hookimpl + def hello(self, arg2, common_arg): + return arg2 + common_arg + + pm.add_hookspecs(Api) + pm.register(Plugin1()) + pm.register(Plugin2()) + + results = pm.hook.hello(arg1=1, arg2=2, common_arg=0) + assert results == [2, 1] + + +def test_call_order(pm): + class Api(object): + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1(object): + @hookimpl + def hello(self, arg): + return 1 + + class Plugin2(object): + @hookimpl + def hello(self, arg): + return 2 + + class Plugin3(object): + @hookimpl + def hello(self, arg): + return 3 + + class Plugin4(object): + @hookimpl(hookwrapper=True) + def hello(self, arg): + assert arg == 0 + outcome = yield + assert outcome.get_result() == [3, 2, 1] + + pm.register(Plugin1()) + pm.register(Plugin2()) + pm.register(Plugin3()) + pm.register(Plugin4()) # hookwrapper should get same list result + res = pm.hook.hello(arg=0) + assert res == [3, 2, 1] + + +def test_firstresult_definition(pm): + class Api(object): + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1(object): + @hookimpl + def hello(self, arg): + return arg + 1 + + class Plugin2(object): + @hookimpl + def hello(self, arg): + return arg - 1 + + class Plugin3(object): + @hookimpl + def hello(self, arg): + return None + + class Plugin4(object): + @hookimpl(hookwrapper=True) + def hello(self, arg): + assert arg == 3 + outcome = yield + assert outcome.get_result() == 2 + + pm.register(Plugin1()) # discarded - not the last registered plugin + pm.register(Plugin2()) # used as result + pm.register(Plugin3()) # None result is ignored + pm.register(Plugin4()) # hookwrapper should get same non-list result + res = pm.hook.hello(arg=3) + assert res == 2 + + +def test_firstresult_force_result(pm): + """Verify forcing a result in a wrapper. + """ + + class Api(object): + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1(object): + @hookimpl + def hello(self, arg): + return arg + 1 + + class Plugin2(object): + @hookimpl(hookwrapper=True) + def hello(self, arg): + assert arg == 3 + outcome = yield + assert outcome.get_result() == 4 + outcome.force_result(0) + + class Plugin3(object): + @hookimpl + def hello(self, arg): + return None + + pm.register(Plugin1()) + pm.register(Plugin2()) # wrapper + pm.register(Plugin3()) # ignored since returns None + res = pm.hook.hello(arg=3) + assert res == 0 # this result is forced and not a list + + +def test_firstresult_returns_none(pm): + """If None results are returned by underlying implementations ensure + the multi-call loop returns a None value. + """ + + class Api(object): + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1(object): + @hookimpl + def hello(self, arg): + return None + + pm.register(Plugin1()) + res = pm.hook.hello(arg=3) + assert res is None + + +def test_firstresult_no_plugin(pm): + """If no implementations/plugins have been registered for a firstresult + hook the multi-call loop should return a None value. + """ + + class Api(object): + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + res = pm.hook.hello(arg=3) + assert res is None diff --git a/contrib/python/pluggy/py2/tests/test_multicall.py b/contrib/python/pluggy/py2/tests/test_multicall.py index eaf534a016..7a6f0a87ec 100644 --- a/contrib/python/pluggy/py2/tests/test_multicall.py +++ b/contrib/python/pluggy/py2/tests/test_multicall.py @@ -1,186 +1,186 @@ -import pytest -from pluggy import HookCallError, HookspecMarker, HookimplMarker -from pluggy.hooks import HookImpl -from pluggy.callers import _multicall, _legacymulticall - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def MC(methods, kwargs, firstresult=False): - caller = _multicall - hookfuncs = [] - for method in methods: - f = HookImpl(None, "<temp>", method, method.example_impl) - hookfuncs.append(f) - if "__multicall__" in f.argnames: - caller = _legacymulticall - return caller(hookfuncs, kwargs, firstresult=firstresult) - - -def test_call_passing(): - class P1(object): - @hookimpl - def m(self, __multicall__, x): - assert len(__multicall__.results) == 1 - assert not __multicall__.hook_impls - return 17 - - class P2(object): - @hookimpl - def m(self, __multicall__, x): - assert __multicall__.results == [] - assert __multicall__.hook_impls - return 23 - - p1 = P1() - p2 = P2() - reslist = MC([p1.m, p2.m], {"x": 23}) - assert len(reslist) == 2 - # ensure reversed order - assert reslist == [23, 17] - - -def test_keyword_args(): - @hookimpl - def f(x): - return x + 1 - - class A(object): - @hookimpl - def f(self, x, y): - return x + y - - reslist = MC([f, A().f], dict(x=23, y=24)) - assert reslist == [24 + 23, 24] - - -def test_keyword_args_with_defaultargs(): - @hookimpl - def f(x, z=1): - return x + z - - reslist = MC([f], dict(x=23, y=24)) - assert reslist == [24] - - -def test_tags_call_error(): - @hookimpl - def f(x): - return x - - with pytest.raises(HookCallError): - MC([f], {}) - - -def test_call_subexecute(): - @hookimpl - def m(__multicall__): - subresult = __multicall__.execute() - return subresult + 1 - - @hookimpl - def n(): - return 1 - - res = MC([n, m], {}, firstresult=True) - assert res == 2 - - -def test_call_none_is_no_result(): - @hookimpl - def m1(): - return 1 - - @hookimpl - def m2(): - return None - - res = MC([m1, m2], {}, firstresult=True) - assert res == 1 - res = MC([m1, m2], {}, {}) - assert res == [1] - - -def test_hookwrapper(): - out = [] - - @hookimpl(hookwrapper=True) - def m1(): - out.append("m1 init") - yield None - out.append("m1 finish") - - @hookimpl - def m2(): - out.append("m2") - return 2 - - res = MC([m2, m1], {}) - assert res == [2] - assert out == ["m1 init", "m2", "m1 finish"] - out[:] = [] - res = MC([m2, m1], {}, firstresult=True) - assert res == 2 - assert out == ["m1 init", "m2", "m1 finish"] - - -def test_hookwrapper_order(): - out = [] - - @hookimpl(hookwrapper=True) - def m1(): - out.append("m1 init") - yield 1 - out.append("m1 finish") - - @hookimpl(hookwrapper=True) - def m2(): - out.append("m2 init") - yield 2 - out.append("m2 finish") - - res = MC([m2, m1], {}) - assert res == [] - assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"] - - -def test_hookwrapper_not_yield(): - @hookimpl(hookwrapper=True) - def m1(): - pass - - with pytest.raises(TypeError): - MC([m1], {}) - - -def test_hookwrapper_too_many_yield(): - @hookimpl(hookwrapper=True) - def m1(): - yield 1 - yield 2 - - with pytest.raises(RuntimeError) as ex: - MC([m1], {}) - assert "m1" in str(ex.value) - assert (__file__ + ":") in str(ex.value) - - -@pytest.mark.parametrize("exc", [ValueError, SystemExit]) -def test_hookwrapper_exception(exc): - out = [] - - @hookimpl(hookwrapper=True) - def m1(): - out.append("m1 init") - yield None - out.append("m1 finish") - - @hookimpl - def m2(): - raise exc - - with pytest.raises(exc): - MC([m2, m1], {}) - assert out == ["m1 init", "m1 finish"] +import pytest +from pluggy import HookCallError, HookspecMarker, HookimplMarker +from pluggy.hooks import HookImpl +from pluggy.callers import _multicall, _legacymulticall + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def MC(methods, kwargs, firstresult=False): + caller = _multicall + hookfuncs = [] + for method in methods: + f = HookImpl(None, "<temp>", method, method.example_impl) + hookfuncs.append(f) + if "__multicall__" in f.argnames: + caller = _legacymulticall + return caller(hookfuncs, kwargs, firstresult=firstresult) + + +def test_call_passing(): + class P1(object): + @hookimpl + def m(self, __multicall__, x): + assert len(__multicall__.results) == 1 + assert not __multicall__.hook_impls + return 17 + + class P2(object): + @hookimpl + def m(self, __multicall__, x): + assert __multicall__.results == [] + assert __multicall__.hook_impls + return 23 + + p1 = P1() + p2 = P2() + reslist = MC([p1.m, p2.m], {"x": 23}) + assert len(reslist) == 2 + # ensure reversed order + assert reslist == [23, 17] + + +def test_keyword_args(): + @hookimpl + def f(x): + return x + 1 + + class A(object): + @hookimpl + def f(self, x, y): + return x + y + + reslist = MC([f, A().f], dict(x=23, y=24)) + assert reslist == [24 + 23, 24] + + +def test_keyword_args_with_defaultargs(): + @hookimpl + def f(x, z=1): + return x + z + + reslist = MC([f], dict(x=23, y=24)) + assert reslist == [24] + + +def test_tags_call_error(): + @hookimpl + def f(x): + return x + + with pytest.raises(HookCallError): + MC([f], {}) + + +def test_call_subexecute(): + @hookimpl + def m(__multicall__): + subresult = __multicall__.execute() + return subresult + 1 + + @hookimpl + def n(): + return 1 + + res = MC([n, m], {}, firstresult=True) + assert res == 2 + + +def test_call_none_is_no_result(): + @hookimpl + def m1(): + return 1 + + @hookimpl + def m2(): + return None + + res = MC([m1, m2], {}, firstresult=True) + assert res == 1 + res = MC([m1, m2], {}, {}) + assert res == [1] + + +def test_hookwrapper(): + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + yield None + out.append("m1 finish") + + @hookimpl + def m2(): + out.append("m2") + return 2 + + res = MC([m2, m1], {}) + assert res == [2] + assert out == ["m1 init", "m2", "m1 finish"] + out[:] = [] + res = MC([m2, m1], {}, firstresult=True) + assert res == 2 + assert out == ["m1 init", "m2", "m1 finish"] + + +def test_hookwrapper_order(): + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + yield 1 + out.append("m1 finish") + + @hookimpl(hookwrapper=True) + def m2(): + out.append("m2 init") + yield 2 + out.append("m2 finish") + + res = MC([m2, m1], {}) + assert res == [] + assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"] + + +def test_hookwrapper_not_yield(): + @hookimpl(hookwrapper=True) + def m1(): + pass + + with pytest.raises(TypeError): + MC([m1], {}) + + +def test_hookwrapper_too_many_yield(): + @hookimpl(hookwrapper=True) + def m1(): + yield 1 + yield 2 + + with pytest.raises(RuntimeError) as ex: + MC([m1], {}) + assert "m1" in str(ex.value) + assert (__file__ + ":") in str(ex.value) + + +@pytest.mark.parametrize("exc", [ValueError, SystemExit]) +def test_hookwrapper_exception(exc): + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + yield None + out.append("m1 finish") + + @hookimpl + def m2(): + raise exc + + with pytest.raises(exc): + MC([m2, m1], {}) + assert out == ["m1 init", "m1 finish"] diff --git a/contrib/python/pluggy/py2/tests/test_pluginmanager.py b/contrib/python/pluggy/py2/tests/test_pluginmanager.py index 67261aaa7b..09d83ab9d4 100644 --- a/contrib/python/pluggy/py2/tests/test_pluginmanager.py +++ b/contrib/python/pluggy/py2/tests/test_pluginmanager.py @@ -1,600 +1,600 @@ -""" -``PluginManager`` unit and public API testing. -""" -import pytest -import types - -from pluggy import ( - PluginManager, - PluginValidationError, - HookCallError, - HookimplMarker, - HookspecMarker, -) -from pluggy.manager import importlib_metadata - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_plugin_double_register(pm): - """Registering the same plugin more then once isn't allowed""" - pm.register(42, name="abc") - with pytest.raises(ValueError): - pm.register(42, name="abc") - with pytest.raises(ValueError): - pm.register(42, name="def") - - -def test_pm(pm): - """Basic registration with objects""" - - class A(object): - pass - - a1, a2 = A(), A() - pm.register(a1) - assert pm.is_registered(a1) - pm.register(a2, "hello") - assert pm.is_registered(a2) - out = pm.get_plugins() - assert a1 in out - assert a2 in out - assert pm.get_plugin("hello") == a2 - assert pm.unregister(a1) == a1 - assert not pm.is_registered(a1) - - out = pm.list_name_plugin() - assert len(out) == 1 - assert out == [("hello", a2)] - - -def test_has_plugin(pm): - class A(object): - pass - - a1 = A() - pm.register(a1, "hello") - assert pm.is_registered(a1) - assert pm.has_plugin("hello") - - -def test_register_dynamic_attr(he_pm): - class A(object): - def __getattr__(self, name): - if name[0] != "_": - return 42 - raise AttributeError() - - a = A() - he_pm.register(a) - assert not he_pm.get_hookcallers(a) - - -def test_pm_name(pm): - class A(object): - pass - - a1 = A() - name = pm.register(a1, name="hello") - assert name == "hello" - pm.unregister(a1) - assert pm.get_plugin(a1) is None - assert not pm.is_registered(a1) - assert not pm.get_plugins() - name2 = pm.register(a1, name="hello") - assert name2 == name - pm.unregister(name="hello") - assert pm.get_plugin(a1) is None - assert not pm.is_registered(a1) - assert not pm.get_plugins() - - -def test_set_blocked(pm): - class A(object): - pass - - a1 = A() - name = pm.register(a1) - assert pm.is_registered(a1) - assert not pm.is_blocked(name) - pm.set_blocked(name) - assert pm.is_blocked(name) - assert not pm.is_registered(a1) - - pm.set_blocked("somename") - assert pm.is_blocked("somename") - assert not pm.register(A(), "somename") - pm.unregister(name="somename") - assert pm.is_blocked("somename") - - -def test_register_mismatch_method(he_pm): - class hello(object): - @hookimpl - def he_method_notexists(self): - pass - - plugin = hello() - - he_pm.register(plugin) - with pytest.raises(PluginValidationError) as excinfo: - he_pm.check_pending() - assert excinfo.value.plugin is plugin - - -def test_register_mismatch_arg(he_pm): - class hello(object): - @hookimpl - def he_method1(self, qlwkje): - pass - - plugin = hello() - - with pytest.raises(PluginValidationError) as excinfo: - he_pm.register(plugin) - assert excinfo.value.plugin is plugin - - -def test_register(pm): - class MyPlugin(object): - pass - - my = MyPlugin() - pm.register(my) - assert my in pm.get_plugins() - my2 = MyPlugin() - pm.register(my2) - assert set([my, my2]).issubset(pm.get_plugins()) - - assert pm.is_registered(my) - assert pm.is_registered(my2) - pm.unregister(my) - assert not pm.is_registered(my) - assert my not in pm.get_plugins() - - -def test_register_unknown_hooks(pm): - class Plugin1(object): - @hookimpl - def he_method1(self, arg): - return arg + 1 - - pname = pm.register(Plugin1()) - - class Hooks(object): - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - # assert not pm._unverified_hooks - assert pm.hook.he_method1(arg=1) == [2] - assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1 - - -def test_register_historic(pm): - class Hooks(object): - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) - out = [] - - class Plugin(object): - @hookimpl - def he_method1(self, arg): - out.append(arg) - - pm.register(Plugin()) - assert out == [1] - - class Plugin2(object): - @hookimpl - def he_method1(self, arg): - out.append(arg * 10) - - pm.register(Plugin2()) - assert out == [1, 10] - pm.hook.he_method1.call_historic(kwargs=dict(arg=12)) - assert out == [1, 10, 120, 12] - - -@pytest.mark.parametrize("result_callback", [True, False]) -def test_with_result_memorized(pm, result_callback): - """Verify that ``_HookCaller._maybe_apply_history()` - correctly applies the ``result_callback`` function, when provided, - to the result from calling each newly registered hook. - """ - out = [] - if result_callback: - - def callback(res): - out.append(res) - - else: - callback = None - - class Hooks(object): - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - class Plugin1(object): - @hookimpl - def he_method1(self, arg): - return arg * 10 - - pm.register(Plugin1()) - - he_method1 = pm.hook.he_method1 - he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1)) - - class Plugin2(object): - @hookimpl - def he_method1(self, arg): - return arg * 10 - - pm.register(Plugin2()) - if result_callback: - assert out == [10, 10] - else: - assert out == [] - - -def test_with_callbacks_immediately_executed(pm): - class Hooks(object): - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - class Plugin1(object): - @hookimpl - def he_method1(self, arg): - return arg * 10 - - class Plugin2(object): - @hookimpl - def he_method1(self, arg): - return arg * 20 - - class Plugin3(object): - @hookimpl - def he_method1(self, arg): - return arg * 30 - - out = [] - pm.register(Plugin1()) - pm.register(Plugin2()) - - he_method1 = pm.hook.he_method1 - he_method1.call_historic(lambda res: out.append(res), dict(arg=1)) - assert out == [20, 10] - pm.register(Plugin3()) - assert out == [20, 10, 30] - - -def test_register_historic_incompat_hookwrapper(pm): - class Hooks(object): - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - out = [] - - class Plugin(object): - @hookimpl(hookwrapper=True) - def he_method1(self, arg): - out.append(arg) - - with pytest.raises(PluginValidationError): - pm.register(Plugin()) - - -def test_call_extra(pm): - class Hooks(object): - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - def he_method1(arg): - return arg * 10 - - out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) - assert out == [10] - - -def test_call_with_too_few_args(pm): - class Hooks(object): - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - class Plugin1(object): - @hookimpl - def he_method1(self, arg): - 0 / 0 - - pm.register(Plugin1()) - with pytest.raises(HookCallError): - with pytest.warns(UserWarning): - pm.hook.he_method1() - - -def test_subset_hook_caller(pm): - class Hooks(object): - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - out = [] - - class Plugin1(object): - @hookimpl - def he_method1(self, arg): - out.append(arg) - - class Plugin2(object): - @hookimpl - def he_method1(self, arg): - out.append(arg * 10) - - class PluginNo(object): - pass - - plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() - pm.register(plugin1) - pm.register(plugin2) - pm.register(plugin3) - pm.hook.he_method1(arg=1) - assert out == [10, 1] - out[:] = [] - - hc = pm.subset_hook_caller("he_method1", [plugin1]) - hc(arg=2) - assert out == [20] - out[:] = [] - - hc = pm.subset_hook_caller("he_method1", [plugin2]) - hc(arg=2) - assert out == [2] - out[:] = [] - - pm.unregister(plugin1) - hc(arg=2) - assert out == [] - out[:] = [] - - pm.hook.he_method1(arg=1) - assert out == [10] - - -def test_get_hookimpls(pm): - class Hooks(object): - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - assert pm.hook.he_method1.get_hookimpls() == [] - - class Plugin1(object): - @hookimpl - def he_method1(self, arg): - pass - - class Plugin2(object): - @hookimpl - def he_method1(self, arg): - pass - - class PluginNo(object): - pass - - plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() - pm.register(plugin1) - pm.register(plugin2) - pm.register(plugin3) - - hookimpls = pm.hook.he_method1.get_hookimpls() - hook_plugins = [item.plugin for item in hookimpls] - assert hook_plugins == [plugin1, plugin2] - - -def test_add_hookspecs_nohooks(pm): - with pytest.raises(ValueError): - pm.add_hookspecs(10) - - -def test_reject_prefixed_module(pm): - """Verify that a module type attribute that contains the project - prefix in its name (in this case `'example_*'` isn't collected - when registering a module which imports it. - """ - pm._implprefix = "example" - conftest = types.ModuleType("conftest") - src = """ -def example_hook(): - pass -""" - exec(src, conftest.__dict__) - conftest.example_blah = types.ModuleType("example_blah") - with pytest.deprecated_call(): - name = pm.register(conftest) - assert name == "conftest" - assert getattr(pm.hook, "example_blah", None) is None - assert getattr( - pm.hook, "example_hook", None - ) # conftest.example_hook should be collected - with pytest.deprecated_call(): - assert pm.parse_hookimpl_opts(conftest, "example_blah") is None - assert pm.parse_hookimpl_opts(conftest, "example_hook") == {} - - -def test_load_setuptools_instantiation(monkeypatch, pm): - class EntryPoint(object): - name = "myname" - group = "hello" - value = "myname:foo" - - def load(self): - class PseudoPlugin(object): - x = 42 - - return PseudoPlugin() - - class Distribution(object): - entry_points = (EntryPoint(),) - - dist = Distribution() - - def my_distributions(): - return (dist,) - - monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) - num = pm.load_setuptools_entrypoints("hello") - assert num == 1 - plugin = pm.get_plugin("myname") - assert plugin.x == 42 - ret = pm.list_plugin_distinfo() - # poor man's `assert ret == [(plugin, mock.ANY)]` - assert len(ret) == 1 - assert len(ret[0]) == 2 - assert ret[0][0] == plugin - assert ret[0][1]._dist == dist - num = pm.load_setuptools_entrypoints("hello") - assert num == 0 # no plugin loaded by this call - - -def test_add_tracefuncs(he_pm): - out = [] - - class api1(object): - @hookimpl - def he_method1(self): - out.append("he_method1-api1") - - class api2(object): - @hookimpl - def he_method1(self): - out.append("he_method1-api2") - - he_pm.register(api1()) - he_pm.register(api2()) - - def before(hook_name, hook_impls, kwargs): - out.append((hook_name, list(hook_impls), kwargs)) - - def after(outcome, hook_name, hook_impls, kwargs): - out.append((outcome, hook_name, list(hook_impls), kwargs)) - - undo = he_pm.add_hookcall_monitoring(before, after) - - he_pm.hook.he_method1(arg=1) - assert len(out) == 4 - assert out[0][0] == "he_method1" - assert len(out[0][1]) == 2 - assert isinstance(out[0][2], dict) - assert out[1] == "he_method1-api2" - assert out[2] == "he_method1-api1" - assert len(out[3]) == 4 - assert out[3][1] == out[0][0] - - undo() - he_pm.hook.he_method1(arg=1) - assert len(out) == 4 + 2 - - -def test_hook_tracing(he_pm): - saveindent = [] - - class api1(object): - @hookimpl - def he_method1(self): - saveindent.append(he_pm.trace.root.indent) - - class api2(object): - @hookimpl - def he_method1(self): - saveindent.append(he_pm.trace.root.indent) - raise ValueError() - - he_pm.register(api1()) - out = [] - he_pm.trace.root.setwriter(out.append) - undo = he_pm.enable_tracing() - try: - indent = he_pm.trace.root.indent - he_pm.hook.he_method1(arg=1) - assert indent == he_pm.trace.root.indent - assert len(out) == 2 - assert "he_method1" in out[0] - assert "finish" in out[1] - - out[:] = [] - he_pm.register(api2()) - - with pytest.raises(ValueError): - he_pm.hook.he_method1(arg=1) - assert he_pm.trace.root.indent == indent - assert saveindent[0] > indent - finally: - undo() - - -def test_implprefix_warning(recwarn): - PluginManager(hookspec.project_name, "hello_") - w = recwarn.pop(DeprecationWarning) - assert "test_pluginmanager.py" in w.filename - - -@pytest.mark.parametrize("include_hookspec", [True, False]) -def test_prefix_hookimpl(include_hookspec): - with pytest.deprecated_call(): - pm = PluginManager(hookspec.project_name, "hello_") - - if include_hookspec: - - class HookSpec(object): - @hookspec - def hello_myhook(self, arg1): - """ add to arg1 """ - - pm.add_hookspecs(HookSpec) - - class Plugin(object): - def hello_myhook(self, arg1): - return arg1 + 1 - - with pytest.deprecated_call(): - pm.register(Plugin()) - pm.register(Plugin()) - results = pm.hook.hello_myhook(arg1=17) - assert results == [18, 18] - - -def test_prefix_hookimpl_dontmatch_module(): - with pytest.deprecated_call(): - pm = PluginManager(hookspec.project_name, "hello_") - - class BadPlugin(object): - hello_module = __import__("email") - - pm.register(BadPlugin()) - pm.check_pending() +""" +``PluginManager`` unit and public API testing. +""" +import pytest +import types + +from pluggy import ( + PluginManager, + PluginValidationError, + HookCallError, + HookimplMarker, + HookspecMarker, +) +from pluggy.manager import importlib_metadata + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_plugin_double_register(pm): + """Registering the same plugin more then once isn't allowed""" + pm.register(42, name="abc") + with pytest.raises(ValueError): + pm.register(42, name="abc") + with pytest.raises(ValueError): + pm.register(42, name="def") + + +def test_pm(pm): + """Basic registration with objects""" + + class A(object): + pass + + a1, a2 = A(), A() + pm.register(a1) + assert pm.is_registered(a1) + pm.register(a2, "hello") + assert pm.is_registered(a2) + out = pm.get_plugins() + assert a1 in out + assert a2 in out + assert pm.get_plugin("hello") == a2 + assert pm.unregister(a1) == a1 + assert not pm.is_registered(a1) + + out = pm.list_name_plugin() + assert len(out) == 1 + assert out == [("hello", a2)] + + +def test_has_plugin(pm): + class A(object): + pass + + a1 = A() + pm.register(a1, "hello") + assert pm.is_registered(a1) + assert pm.has_plugin("hello") + + +def test_register_dynamic_attr(he_pm): + class A(object): + def __getattr__(self, name): + if name[0] != "_": + return 42 + raise AttributeError() + + a = A() + he_pm.register(a) + assert not he_pm.get_hookcallers(a) + + +def test_pm_name(pm): + class A(object): + pass + + a1 = A() + name = pm.register(a1, name="hello") + assert name == "hello" + pm.unregister(a1) + assert pm.get_plugin(a1) is None + assert not pm.is_registered(a1) + assert not pm.get_plugins() + name2 = pm.register(a1, name="hello") + assert name2 == name + pm.unregister(name="hello") + assert pm.get_plugin(a1) is None + assert not pm.is_registered(a1) + assert not pm.get_plugins() + + +def test_set_blocked(pm): + class A(object): + pass + + a1 = A() + name = pm.register(a1) + assert pm.is_registered(a1) + assert not pm.is_blocked(name) + pm.set_blocked(name) + assert pm.is_blocked(name) + assert not pm.is_registered(a1) + + pm.set_blocked("somename") + assert pm.is_blocked("somename") + assert not pm.register(A(), "somename") + pm.unregister(name="somename") + assert pm.is_blocked("somename") + + +def test_register_mismatch_method(he_pm): + class hello(object): + @hookimpl + def he_method_notexists(self): + pass + + plugin = hello() + + he_pm.register(plugin) + with pytest.raises(PluginValidationError) as excinfo: + he_pm.check_pending() + assert excinfo.value.plugin is plugin + + +def test_register_mismatch_arg(he_pm): + class hello(object): + @hookimpl + def he_method1(self, qlwkje): + pass + + plugin = hello() + + with pytest.raises(PluginValidationError) as excinfo: + he_pm.register(plugin) + assert excinfo.value.plugin is plugin + + +def test_register(pm): + class MyPlugin(object): + pass + + my = MyPlugin() + pm.register(my) + assert my in pm.get_plugins() + my2 = MyPlugin() + pm.register(my2) + assert set([my, my2]).issubset(pm.get_plugins()) + + assert pm.is_registered(my) + assert pm.is_registered(my2) + pm.unregister(my) + assert not pm.is_registered(my) + assert my not in pm.get_plugins() + + +def test_register_unknown_hooks(pm): + class Plugin1(object): + @hookimpl + def he_method1(self, arg): + return arg + 1 + + pname = pm.register(Plugin1()) + + class Hooks(object): + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + # assert not pm._unverified_hooks + assert pm.hook.he_method1(arg=1) == [2] + assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1 + + +def test_register_historic(pm): + class Hooks(object): + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) + out = [] + + class Plugin(object): + @hookimpl + def he_method1(self, arg): + out.append(arg) + + pm.register(Plugin()) + assert out == [1] + + class Plugin2(object): + @hookimpl + def he_method1(self, arg): + out.append(arg * 10) + + pm.register(Plugin2()) + assert out == [1, 10] + pm.hook.he_method1.call_historic(kwargs=dict(arg=12)) + assert out == [1, 10, 120, 12] + + +@pytest.mark.parametrize("result_callback", [True, False]) +def test_with_result_memorized(pm, result_callback): + """Verify that ``_HookCaller._maybe_apply_history()` + correctly applies the ``result_callback`` function, when provided, + to the result from calling each newly registered hook. + """ + out = [] + if result_callback: + + def callback(res): + out.append(res) + + else: + callback = None + + class Hooks(object): + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin1(object): + @hookimpl + def he_method1(self, arg): + return arg * 10 + + pm.register(Plugin1()) + + he_method1 = pm.hook.he_method1 + he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1)) + + class Plugin2(object): + @hookimpl + def he_method1(self, arg): + return arg * 10 + + pm.register(Plugin2()) + if result_callback: + assert out == [10, 10] + else: + assert out == [] + + +def test_with_callbacks_immediately_executed(pm): + class Hooks(object): + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin1(object): + @hookimpl + def he_method1(self, arg): + return arg * 10 + + class Plugin2(object): + @hookimpl + def he_method1(self, arg): + return arg * 20 + + class Plugin3(object): + @hookimpl + def he_method1(self, arg): + return arg * 30 + + out = [] + pm.register(Plugin1()) + pm.register(Plugin2()) + + he_method1 = pm.hook.he_method1 + he_method1.call_historic(lambda res: out.append(res), dict(arg=1)) + assert out == [20, 10] + pm.register(Plugin3()) + assert out == [20, 10, 30] + + +def test_register_historic_incompat_hookwrapper(pm): + class Hooks(object): + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + out = [] + + class Plugin(object): + @hookimpl(hookwrapper=True) + def he_method1(self, arg): + out.append(arg) + + with pytest.raises(PluginValidationError): + pm.register(Plugin()) + + +def test_call_extra(pm): + class Hooks(object): + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + def he_method1(arg): + return arg * 10 + + out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) + assert out == [10] + + +def test_call_with_too_few_args(pm): + class Hooks(object): + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin1(object): + @hookimpl + def he_method1(self, arg): + 0 / 0 + + pm.register(Plugin1()) + with pytest.raises(HookCallError): + with pytest.warns(UserWarning): + pm.hook.he_method1() + + +def test_subset_hook_caller(pm): + class Hooks(object): + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + out = [] + + class Plugin1(object): + @hookimpl + def he_method1(self, arg): + out.append(arg) + + class Plugin2(object): + @hookimpl + def he_method1(self, arg): + out.append(arg * 10) + + class PluginNo(object): + pass + + plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() + pm.register(plugin1) + pm.register(plugin2) + pm.register(plugin3) + pm.hook.he_method1(arg=1) + assert out == [10, 1] + out[:] = [] + + hc = pm.subset_hook_caller("he_method1", [plugin1]) + hc(arg=2) + assert out == [20] + out[:] = [] + + hc = pm.subset_hook_caller("he_method1", [plugin2]) + hc(arg=2) + assert out == [2] + out[:] = [] + + pm.unregister(plugin1) + hc(arg=2) + assert out == [] + out[:] = [] + + pm.hook.he_method1(arg=1) + assert out == [10] + + +def test_get_hookimpls(pm): + class Hooks(object): + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + assert pm.hook.he_method1.get_hookimpls() == [] + + class Plugin1(object): + @hookimpl + def he_method1(self, arg): + pass + + class Plugin2(object): + @hookimpl + def he_method1(self, arg): + pass + + class PluginNo(object): + pass + + plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() + pm.register(plugin1) + pm.register(plugin2) + pm.register(plugin3) + + hookimpls = pm.hook.he_method1.get_hookimpls() + hook_plugins = [item.plugin for item in hookimpls] + assert hook_plugins == [plugin1, plugin2] + + +def test_add_hookspecs_nohooks(pm): + with pytest.raises(ValueError): + pm.add_hookspecs(10) + + +def test_reject_prefixed_module(pm): + """Verify that a module type attribute that contains the project + prefix in its name (in this case `'example_*'` isn't collected + when registering a module which imports it. + """ + pm._implprefix = "example" + conftest = types.ModuleType("conftest") + src = """ +def example_hook(): + pass +""" + exec(src, conftest.__dict__) + conftest.example_blah = types.ModuleType("example_blah") + with pytest.deprecated_call(): + name = pm.register(conftest) + assert name == "conftest" + assert getattr(pm.hook, "example_blah", None) is None + assert getattr( + pm.hook, "example_hook", None + ) # conftest.example_hook should be collected + with pytest.deprecated_call(): + assert pm.parse_hookimpl_opts(conftest, "example_blah") is None + assert pm.parse_hookimpl_opts(conftest, "example_hook") == {} + + +def test_load_setuptools_instantiation(monkeypatch, pm): + class EntryPoint(object): + name = "myname" + group = "hello" + value = "myname:foo" + + def load(self): + class PseudoPlugin(object): + x = 42 + + return PseudoPlugin() + + class Distribution(object): + entry_points = (EntryPoint(),) + + dist = Distribution() + + def my_distributions(): + return (dist,) + + monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) + num = pm.load_setuptools_entrypoints("hello") + assert num == 1 + plugin = pm.get_plugin("myname") + assert plugin.x == 42 + ret = pm.list_plugin_distinfo() + # poor man's `assert ret == [(plugin, mock.ANY)]` + assert len(ret) == 1 + assert len(ret[0]) == 2 + assert ret[0][0] == plugin + assert ret[0][1]._dist == dist + num = pm.load_setuptools_entrypoints("hello") + assert num == 0 # no plugin loaded by this call + + +def test_add_tracefuncs(he_pm): + out = [] + + class api1(object): + @hookimpl + def he_method1(self): + out.append("he_method1-api1") + + class api2(object): + @hookimpl + def he_method1(self): + out.append("he_method1-api2") + + he_pm.register(api1()) + he_pm.register(api2()) + + def before(hook_name, hook_impls, kwargs): + out.append((hook_name, list(hook_impls), kwargs)) + + def after(outcome, hook_name, hook_impls, kwargs): + out.append((outcome, hook_name, list(hook_impls), kwargs)) + + undo = he_pm.add_hookcall_monitoring(before, after) + + he_pm.hook.he_method1(arg=1) + assert len(out) == 4 + assert out[0][0] == "he_method1" + assert len(out[0][1]) == 2 + assert isinstance(out[0][2], dict) + assert out[1] == "he_method1-api2" + assert out[2] == "he_method1-api1" + assert len(out[3]) == 4 + assert out[3][1] == out[0][0] + + undo() + he_pm.hook.he_method1(arg=1) + assert len(out) == 4 + 2 + + +def test_hook_tracing(he_pm): + saveindent = [] + + class api1(object): + @hookimpl + def he_method1(self): + saveindent.append(he_pm.trace.root.indent) + + class api2(object): + @hookimpl + def he_method1(self): + saveindent.append(he_pm.trace.root.indent) + raise ValueError() + + he_pm.register(api1()) + out = [] + he_pm.trace.root.setwriter(out.append) + undo = he_pm.enable_tracing() + try: + indent = he_pm.trace.root.indent + he_pm.hook.he_method1(arg=1) + assert indent == he_pm.trace.root.indent + assert len(out) == 2 + assert "he_method1" in out[0] + assert "finish" in out[1] + + out[:] = [] + he_pm.register(api2()) + + with pytest.raises(ValueError): + he_pm.hook.he_method1(arg=1) + assert he_pm.trace.root.indent == indent + assert saveindent[0] > indent + finally: + undo() + + +def test_implprefix_warning(recwarn): + PluginManager(hookspec.project_name, "hello_") + w = recwarn.pop(DeprecationWarning) + assert "test_pluginmanager.py" in w.filename + + +@pytest.mark.parametrize("include_hookspec", [True, False]) +def test_prefix_hookimpl(include_hookspec): + with pytest.deprecated_call(): + pm = PluginManager(hookspec.project_name, "hello_") + + if include_hookspec: + + class HookSpec(object): + @hookspec + def hello_myhook(self, arg1): + """ add to arg1 """ + + pm.add_hookspecs(HookSpec) + + class Plugin(object): + def hello_myhook(self, arg1): + return arg1 + 1 + + with pytest.deprecated_call(): + pm.register(Plugin()) + pm.register(Plugin()) + results = pm.hook.hello_myhook(arg1=17) + assert results == [18, 18] + + +def test_prefix_hookimpl_dontmatch_module(): + with pytest.deprecated_call(): + pm = PluginManager(hookspec.project_name, "hello_") + + class BadPlugin(object): + hello_module = __import__("email") + + pm.register(BadPlugin()) + pm.check_pending() diff --git a/contrib/python/pluggy/py2/tests/test_tracer.py b/contrib/python/pluggy/py2/tests/test_tracer.py index 992ec67914..7915f0ee75 100644 --- a/contrib/python/pluggy/py2/tests/test_tracer.py +++ b/contrib/python/pluggy/py2/tests/test_tracer.py @@ -1,78 +1,78 @@ -from pluggy._tracing import TagTracer - -import pytest - - -@pytest.fixture -def rootlogger(): - return TagTracer() - - -def test_simple(rootlogger): - log = rootlogger.get("pytest") - log("hello") - out = [] - rootlogger.setwriter(out.append) - log("world") - assert len(out) == 1 - assert out[0] == "world [pytest]\n" - sublog = log.get("collection") - sublog("hello") - assert out[1] == "hello [pytest:collection]\n" - - -def test_indent(rootlogger): - log = rootlogger.get("1") - out = [] - log.root.setwriter(lambda arg: out.append(arg)) - log("hello") - log.root.indent += 1 - log("line1") - log("line2") - log.root.indent += 1 - log("line3") - log("line4") - log.root.indent -= 1 - log("line5") - log.root.indent -= 1 - log("last") - assert len(out) == 7 - names = [x[: x.rfind(" [")] for x in out] - assert names == [ - "hello", - " line1", - " line2", - " line3", - " line4", - " line5", - "last", - ] - - -def test_readable_output_dictargs(rootlogger): - - out = rootlogger._format_message(["test"], [1]) - assert out == "1 [test]\n" - - out2 = rootlogger._format_message(["test"], ["test", {"a": 1}]) - assert out2 == "test [test]\n a: 1\n" - - -def test_setprocessor(rootlogger): - log = rootlogger.get("1") - log2 = log.get("2") - assert log2.tags == tuple("12") - out = [] - rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args)) - log("not seen") - log2("seen") - assert len(out) == 1 - tags, args = out[0] - assert "1" in tags - assert "2" in tags - assert args == ("seen",) - l2 = [] - rootlogger.setprocessor("1:2", lambda *args: l2.append(args)) - log2("seen") - tags, args = l2[0] - assert args == ("seen",) +from pluggy._tracing import TagTracer + +import pytest + + +@pytest.fixture +def rootlogger(): + return TagTracer() + + +def test_simple(rootlogger): + log = rootlogger.get("pytest") + log("hello") + out = [] + rootlogger.setwriter(out.append) + log("world") + assert len(out) == 1 + assert out[0] == "world [pytest]\n" + sublog = log.get("collection") + sublog("hello") + assert out[1] == "hello [pytest:collection]\n" + + +def test_indent(rootlogger): + log = rootlogger.get("1") + out = [] + log.root.setwriter(lambda arg: out.append(arg)) + log("hello") + log.root.indent += 1 + log("line1") + log("line2") + log.root.indent += 1 + log("line3") + log("line4") + log.root.indent -= 1 + log("line5") + log.root.indent -= 1 + log("last") + assert len(out) == 7 + names = [x[: x.rfind(" [")] for x in out] + assert names == [ + "hello", + " line1", + " line2", + " line3", + " line4", + " line5", + "last", + ] + + +def test_readable_output_dictargs(rootlogger): + + out = rootlogger._format_message(["test"], [1]) + assert out == "1 [test]\n" + + out2 = rootlogger._format_message(["test"], ["test", {"a": 1}]) + assert out2 == "test [test]\n a: 1\n" + + +def test_setprocessor(rootlogger): + log = rootlogger.get("1") + log2 = log.get("2") + assert log2.tags == tuple("12") + out = [] + rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args)) + log("not seen") + log2("seen") + assert len(out) == 1 + tags, args = out[0] + assert "1" in tags + assert "2" in tags + assert args == ("seen",) + l2 = [] + rootlogger.setprocessor("1:2", lambda *args: l2.append(args)) + log2("seen") + tags, args = l2[0] + assert args == ("seen",) diff --git a/contrib/python/pluggy/py2/tests/ya.make b/contrib/python/pluggy/py2/tests/ya.make index a0bd221149..2091a66adf 100644 --- a/contrib/python/pluggy/py2/tests/ya.make +++ b/contrib/python/pluggy/py2/tests/ya.make @@ -1,23 +1,23 @@ -PY2TEST() - -OWNER(g:python-contrib) - -PEERDIR ( - contrib/python/pluggy -) - -TEST_SRCS( - conftest.py - test_deprecations.py - test_details.py - test_helpers.py - test_hookcaller.py - test_invocations.py - test_multicall.py - test_pluginmanager.py - test_tracer.py -) - -NO_LINT() - -END() +PY2TEST() + +OWNER(g:python-contrib) + +PEERDIR ( + contrib/python/pluggy +) + +TEST_SRCS( + conftest.py + test_deprecations.py + test_details.py + test_helpers.py + test_hookcaller.py + test_invocations.py + test_multicall.py + test_pluginmanager.py + test_tracer.py +) + +NO_LINT() + +END() diff --git a/contrib/python/pluggy/py2/ya.make b/contrib/python/pluggy/py2/ya.make index 40ae96f0bf..9e48d90b96 100644 --- a/contrib/python/pluggy/py2/ya.make +++ b/contrib/python/pluggy/py2/ya.make @@ -1,15 +1,15 @@ -PY2_LIBRARY() +PY2_LIBRARY() OWNER(g:python-contrib) -VERSION(0.13.1) - -LICENSE(MIT) - -PEERDIR( - contrib/python/importlib-metadata -) +VERSION(0.13.1) +LICENSE(MIT) + +PEERDIR( + contrib/python/importlib-metadata +) + NO_LINT() PY_SRCS( @@ -22,14 +22,14 @@ PY_SRCS( pluggy/manager.py ) -RESOURCE_FILES( - PREFIX contrib/python/pluggy/py2/ - .dist-info/METADATA - .dist-info/top_level.txt -) - +RESOURCE_FILES( + PREFIX contrib/python/pluggy/py2/ + .dist-info/METADATA + .dist-info/top_level.txt +) + END() - -RECURSE_FOR_TESTS( - tests -) + +RECURSE_FOR_TESTS( + tests +) diff --git a/contrib/python/pluggy/py3/.dist-info/METADATA b/contrib/python/pluggy/py3/.dist-info/METADATA index 976c06cddb..dcbd076ac1 100644 --- a/contrib/python/pluggy/py3/.dist-info/METADATA +++ b/contrib/python/pluggy/py3/.dist-info/METADATA @@ -1,143 +1,143 @@ -Metadata-Version: 2.1 -Name: pluggy -Version: 1.0.0 -Summary: plugin and hook calling mechanisms for python -Home-page: https://github.com/pytest-dev/pluggy -Author: Holger Krekel -Author-email: holger@merlinux.eu -License: MIT -Platform: unix -Platform: linux -Platform: osx -Platform: win32 -Classifier: Development Status :: 6 - Mature -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: POSIX -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Topic :: Software Development :: Testing -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Utilities -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Requires-Python: >=3.6 -Description-Content-Type: text/x-rst -License-File: LICENSE -Requires-Dist: importlib-metadata (>=0.12) ; python_version < "3.8" -Provides-Extra: dev -Requires-Dist: pre-commit ; extra == 'dev' -Requires-Dist: tox ; extra == 'dev' -Provides-Extra: testing -Requires-Dist: pytest ; extra == 'testing' -Requires-Dist: pytest-benchmark ; extra == 'testing' - -==================================================== -pluggy - A minimalist production ready plugin system -==================================================== - -|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov| - -This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. - -Please `read the docs`_ to learn more! - -A definitive example -==================== -.. code-block:: 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) - - -Running this directly gets us:: - - $ python docs/examples/toy-example.py - inside Plugin_2.myhook() - inside Plugin_1.myhook() - [-1, 3] - - -.. badges - -.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |github-actions| image:: https://github.com/pytest-dev/pluggy/workflows/main/badge.svg - :target: https://github.com/pytest-dev/pluggy/actions - -.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg - :target: https://anaconda.org/conda-forge/pytest - -.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg - :alt: Join the chat at https://gitter.im/pytest-dev/pluggy - :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black - -.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg - :target: https://codecov.io/gh/pytest-dev/pluggy - :alt: Code coverage Status - -.. links -.. _pytest: - http://pytest.org -.. _tox: - https://tox.readthedocs.org -.. _devpi: - http://doc.devpi.net -.. _read the docs: - https://pluggy.readthedocs.io/en/latest/ - - +Metadata-Version: 2.1 +Name: pluggy +Version: 1.0.0 +Summary: plugin and hook calling mechanisms for python +Home-page: https://github.com/pytest-dev/pluggy +Author: Holger Krekel +Author-email: holger@merlinux.eu +License: MIT +Platform: unix +Platform: linux +Platform: osx +Platform: win32 +Classifier: Development Status :: 6 - Mature +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: importlib-metadata (>=0.12) ; python_version < "3.8" +Provides-Extra: dev +Requires-Dist: pre-commit ; extra == 'dev' +Requires-Dist: tox ; extra == 'dev' +Provides-Extra: testing +Requires-Dist: pytest ; extra == 'testing' +Requires-Dist: pytest-benchmark ; extra == 'testing' + +==================================================== +pluggy - A minimalist production ready plugin system +==================================================== + +|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov| + +This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. + +Please `read the docs`_ to learn more! + +A definitive example +==================== +.. code-block:: 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) + + +Running this directly gets us:: + + $ python docs/examples/toy-example.py + inside Plugin_2.myhook() + inside Plugin_1.myhook() + [-1, 3] + + +.. badges + +.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |github-actions| image:: https://github.com/pytest-dev/pluggy/workflows/main/badge.svg + :target: https://github.com/pytest-dev/pluggy/actions + +.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg + :target: https://anaconda.org/conda-forge/pytest + +.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg + :alt: Join the chat at https://gitter.im/pytest-dev/pluggy + :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + +.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pluggy + :alt: Code coverage Status + +.. links +.. _pytest: + http://pytest.org +.. _tox: + https://tox.readthedocs.org +.. _devpi: + http://doc.devpi.net +.. _read the docs: + https://pluggy.readthedocs.io/en/latest/ + + diff --git a/contrib/python/pluggy/py3/.dist-info/top_level.txt b/contrib/python/pluggy/py3/.dist-info/top_level.txt index 11bdb5c1f5..b78af1723b 100644 --- a/contrib/python/pluggy/py3/.dist-info/top_level.txt +++ b/contrib/python/pluggy/py3/.dist-info/top_level.txt @@ -1 +1 @@ -pluggy +pluggy diff --git a/contrib/python/pluggy/py3/LICENSE b/contrib/python/pluggy/py3/LICENSE index 85f4dd63d2..58f08de42e 100644 --- a/contrib/python/pluggy/py3/LICENSE +++ b/contrib/python/pluggy/py3/LICENSE @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/contrib/python/pluggy/py3/README.rst b/contrib/python/pluggy/py3/README.rst index 3496617e1e..7040442bf8 100644 --- a/contrib/python/pluggy/py3/README.rst +++ b/contrib/python/pluggy/py3/README.rst @@ -1,101 +1,101 @@ -==================================================== -pluggy - A minimalist production ready plugin system -==================================================== - -|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov| - -This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. - -Please `read the docs`_ to learn more! - -A definitive example -==================== -.. code-block:: 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) - - -Running this directly gets us:: - - $ python docs/examples/toy-example.py - inside Plugin_2.myhook() - inside Plugin_1.myhook() - [-1, 3] - - -.. badges - -.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg - :target: https://pypi.org/pypi/pluggy - -.. |github-actions| image:: https://github.com/pytest-dev/pluggy/workflows/main/badge.svg - :target: https://github.com/pytest-dev/pluggy/actions - -.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg - :target: https://anaconda.org/conda-forge/pytest - -.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg - :alt: Join the chat at https://gitter.im/pytest-dev/pluggy - :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black - -.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg - :target: https://codecov.io/gh/pytest-dev/pluggy - :alt: Code coverage Status - -.. links -.. _pytest: - http://pytest.org -.. _tox: - https://tox.readthedocs.org -.. _devpi: - http://doc.devpi.net -.. _read the docs: - https://pluggy.readthedocs.io/en/latest/ +==================================================== +pluggy - A minimalist production ready plugin system +==================================================== + +|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov| + +This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. + +Please `read the docs`_ to learn more! + +A definitive example +==================== +.. code-block:: 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) + + +Running this directly gets us:: + + $ python docs/examples/toy-example.py + inside Plugin_2.myhook() + inside Plugin_1.myhook() + [-1, 3] + + +.. badges + +.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg + :target: https://pypi.org/pypi/pluggy + +.. |github-actions| image:: https://github.com/pytest-dev/pluggy/workflows/main/badge.svg + :target: https://github.com/pytest-dev/pluggy/actions + +.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg + :target: https://anaconda.org/conda-forge/pytest + +.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg + :alt: Join the chat at https://gitter.im/pytest-dev/pluggy + :target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + +.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pluggy + :alt: Code coverage Status + +.. links +.. _pytest: + http://pytest.org +.. _tox: + https://tox.readthedocs.org +.. _devpi: + http://doc.devpi.net +.. _read the docs: + https://pluggy.readthedocs.io/en/latest/ diff --git a/contrib/python/pluggy/py3/pluggy/__init__.py b/contrib/python/pluggy/py3/pluggy/__init__.py index 979028f759..a874cc0965 100644 --- a/contrib/python/pluggy/py3/pluggy/__init__.py +++ b/contrib/python/pluggy/py3/pluggy/__init__.py @@ -1,18 +1,18 @@ -try: - from ._version import version as __version__ -except ImportError: - # broken installation, we don't even try - # unknown only works because we do poor mans version compare - __version__ = "unknown" - -__all__ = [ - "PluginManager", - "PluginValidationError", - "HookCallError", - "HookspecMarker", - "HookimplMarker", -] - -from ._manager import PluginManager, PluginValidationError -from ._callers import HookCallError -from ._hooks import HookspecMarker, HookimplMarker +try: + from ._version import version as __version__ +except ImportError: + # broken installation, we don't even try + # unknown only works because we do poor mans version compare + __version__ = "unknown" + +__all__ = [ + "PluginManager", + "PluginValidationError", + "HookCallError", + "HookspecMarker", + "HookimplMarker", +] + +from ._manager import PluginManager, PluginValidationError +from ._callers import HookCallError +from ._hooks import HookspecMarker, HookimplMarker diff --git a/contrib/python/pluggy/py3/pluggy/_callers.py b/contrib/python/pluggy/py3/pluggy/_callers.py index 7a16f3bdd4..b64b85a23b 100644 --- a/contrib/python/pluggy/py3/pluggy/_callers.py +++ b/contrib/python/pluggy/py3/pluggy/_callers.py @@ -1,60 +1,60 @@ -""" -Call loop machinery -""" -import sys - -from ._result import HookCallError, _Result, _raise_wrapfail - - -def _multicall(hook_name, hook_impls, caller_kwargs, firstresult): - """Execute a call into multiple python functions/methods and return the - result(s). - - ``caller_kwargs`` comes from _HookCaller.__call__(). - """ - __tracebackhide__ = True - results = [] - excinfo = None - try: # run impl and wrapper setup functions in a loop - teardowns = [] - try: - for hook_impl in reversed(hook_impls): - try: - args = [caller_kwargs[argname] for argname in hook_impl.argnames] - except KeyError: - for argname in hook_impl.argnames: - if argname not in caller_kwargs: - raise HookCallError( - f"hook call must provide argument {argname!r}" - ) - - if hook_impl.hookwrapper: - try: - gen = hook_impl.function(*args) - next(gen) # first yield - teardowns.append(gen) - except StopIteration: - _raise_wrapfail(gen, "did not yield") - else: - res = hook_impl.function(*args) - if res is not None: - results.append(res) - if firstresult: # halt further impl calls - break - except BaseException: - excinfo = sys.exc_info() - finally: - if firstresult: # first result hooks return a single value - outcome = _Result(results[0] if results else None, excinfo) - else: - outcome = _Result(results, excinfo) - - # run all wrapper post-yield blocks - for gen in reversed(teardowns): - try: - gen.send(outcome) - _raise_wrapfail(gen, "has second yield") - except StopIteration: - pass - - return outcome.get_result() +""" +Call loop machinery +""" +import sys + +from ._result import HookCallError, _Result, _raise_wrapfail + + +def _multicall(hook_name, hook_impls, caller_kwargs, firstresult): + """Execute a call into multiple python functions/methods and return the + result(s). + + ``caller_kwargs`` comes from _HookCaller.__call__(). + """ + __tracebackhide__ = True + results = [] + excinfo = None + try: # run impl and wrapper setup functions in a loop + teardowns = [] + try: + for hook_impl in reversed(hook_impls): + try: + args = [caller_kwargs[argname] for argname in hook_impl.argnames] + except KeyError: + for argname in hook_impl.argnames: + if argname not in caller_kwargs: + raise HookCallError( + f"hook call must provide argument {argname!r}" + ) + + if hook_impl.hookwrapper: + try: + gen = hook_impl.function(*args) + next(gen) # first yield + teardowns.append(gen) + except StopIteration: + _raise_wrapfail(gen, "did not yield") + else: + res = hook_impl.function(*args) + if res is not None: + results.append(res) + if firstresult: # halt further impl calls + break + except BaseException: + excinfo = sys.exc_info() + finally: + if firstresult: # first result hooks return a single value + outcome = _Result(results[0] if results else None, excinfo) + else: + outcome = _Result(results, excinfo) + + # run all wrapper post-yield blocks + for gen in reversed(teardowns): + try: + gen.send(outcome) + _raise_wrapfail(gen, "has second yield") + except StopIteration: + pass + + return outcome.get_result() diff --git a/contrib/python/pluggy/py3/pluggy/_hooks.py b/contrib/python/pluggy/py3/pluggy/_hooks.py index 1e5fbb7595..a057b0aae2 100644 --- a/contrib/python/pluggy/py3/pluggy/_hooks.py +++ b/contrib/python/pluggy/py3/pluggy/_hooks.py @@ -1,325 +1,325 @@ -""" -Internal hook annotation, representation and calling machinery. -""" -import inspect -import sys -import warnings - - -class HookspecMarker: - """Decorator helper class for marking functions as hook specifications. - - You can instantiate it with a project_name to get a decorator. - Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions - if the :py:class:`.PluginManager` uses the same project_name. - """ - - def __init__(self, project_name): - self.project_name = project_name - - def __call__( - self, function=None, firstresult=False, historic=False, warn_on_impl=None - ): - """if passed a function, directly sets attributes on the function - which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`. - If passed no function, returns a decorator which can be applied to a function - later using the attributes supplied. - - If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered - hook implementation functions) will stop at I<=N when the I'th function - returns a non-``None`` result. - - If ``historic`` is ``True`` calls to a hook will be memorized and replayed - on later registered plugins. - - """ - - def setattr_hookspec_opts(func): - if historic and firstresult: - raise ValueError("cannot have a historic firstresult hook") - setattr( - func, - self.project_name + "_spec", - dict( - firstresult=firstresult, - historic=historic, - warn_on_impl=warn_on_impl, - ), - ) - return func - - if function is not None: - return setattr_hookspec_opts(function) - else: - return setattr_hookspec_opts - - -class HookimplMarker: - """Decorator helper class for marking functions as hook implementations. - - You can instantiate with a ``project_name`` to get a decorator. - Calling :py:meth:`.PluginManager.register` later will discover all marked functions - if the :py:class:`.PluginManager` uses the same project_name. - """ - - def __init__(self, project_name): - self.project_name = project_name - - def __call__( - self, - function=None, - hookwrapper=False, - optionalhook=False, - tryfirst=False, - trylast=False, - specname=None, - ): - - """if passed a function, directly sets attributes on the function - which will make it discoverable to :py:meth:`.PluginManager.register`. - If passed no function, returns a decorator which can be applied to a - function later using the attributes supplied. - - If ``optionalhook`` is ``True`` a missing matching hook specification will not result - in an error (by default it is an error if no matching spec is found). - - If ``tryfirst`` is ``True`` this hook implementation will run as early as possible - in the chain of N hook implementations for a specification. - - If ``trylast`` is ``True`` this hook implementation will run as late as possible - in the chain of N hook implementations. - - If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly - one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper - function is run. The code after the ``yield`` is run after all non-hookwrapper - function have run. The ``yield`` receives a :py:class:`.callers._Result` object - representing the exception or result outcome of the inner calls (including other - hookwrapper calls). - - If ``specname`` is provided, it will be used instead of the function name when - matching this hook implementation to a hook specification during registration. - - """ - - def setattr_hookimpl_opts(func): - setattr( - func, - self.project_name + "_impl", - dict( - hookwrapper=hookwrapper, - optionalhook=optionalhook, - tryfirst=tryfirst, - trylast=trylast, - specname=specname, - ), - ) - return func - - if function is None: - return setattr_hookimpl_opts - else: - return setattr_hookimpl_opts(function) - - -def normalize_hookimpl_opts(opts): - opts.setdefault("tryfirst", False) - opts.setdefault("trylast", False) - opts.setdefault("hookwrapper", False) - opts.setdefault("optionalhook", False) - opts.setdefault("specname", None) - - -_PYPY = hasattr(sys, "pypy_version_info") - - -def varnames(func): - """Return tuple of positional and keywrord argument names for a function, - method, class or callable. - - In case of a class, its ``__init__`` method is considered. - For methods the ``self`` parameter is not included. - """ - if inspect.isclass(func): - try: - func = func.__init__ - except AttributeError: - return (), () - elif not inspect.isroutine(func): # callable object? - try: - func = getattr(func, "__call__", func) - except Exception: - return (), () - - try: # func MUST be a function or method here or we won't parse any args - spec = inspect.getfullargspec(func) - except TypeError: - return (), () - - args, defaults = tuple(spec.args), spec.defaults - if defaults: - index = -len(defaults) - args, kwargs = args[:index], tuple(args[index:]) - else: - kwargs = () - - # strip any implicit instance arg - # pypy3 uses "obj" instead of "self" for default dunder methods - implicit_names = ("self",) if not _PYPY else ("self", "obj") - if args: - if inspect.ismethod(func) or ( - "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names - ): - args = args[1:] - - return args, kwargs - - -class _HookRelay: - """hook holder object for performing 1:N hook calls where N is the number - of registered plugins. - - """ - - -class _HookCaller: - def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None): - self.name = name - self._wrappers = [] - self._nonwrappers = [] - self._hookexec = hook_execute - self._call_history = None - self.spec = None - if specmodule_or_class is not None: - assert spec_opts is not None - self.set_specification(specmodule_or_class, spec_opts) - - def has_spec(self): - return self.spec is not None - - def set_specification(self, specmodule_or_class, spec_opts): - assert not self.has_spec() - self.spec = HookSpec(specmodule_or_class, self.name, spec_opts) - if spec_opts.get("historic"): - self._call_history = [] - - def is_historic(self): - return self._call_history is not None - - def _remove_plugin(self, plugin): - def remove(wrappers): - for i, method in enumerate(wrappers): - if method.plugin == plugin: - del wrappers[i] - return True - - if remove(self._wrappers) is None: - if remove(self._nonwrappers) is None: - raise ValueError(f"plugin {plugin!r} not found") - - def get_hookimpls(self): - # Order is important for _hookexec - return self._nonwrappers + self._wrappers - - def _add_hookimpl(self, hookimpl): - """Add an implementation to the callback chain.""" - if hookimpl.hookwrapper: - methods = self._wrappers - else: - methods = self._nonwrappers - - if hookimpl.trylast: - methods.insert(0, hookimpl) - elif hookimpl.tryfirst: - methods.append(hookimpl) - else: - # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and methods[i].tryfirst: - i -= 1 - methods.insert(i + 1, hookimpl) - - def __repr__(self): - return f"<_HookCaller {self.name!r}>" - - def __call__(self, *args, **kwargs): - if args: - raise TypeError("hook calling supports only keyword arguments") - assert not self.is_historic() - - # This is written to avoid expensive operations when not needed. - if self.spec: - for argname in self.spec.argnames: - if argname not in kwargs: - notincall = tuple(set(self.spec.argnames) - kwargs.keys()) - warnings.warn( - "Argument(s) {} which are declared in the hookspec " - "can not be found in this hook call".format(notincall), - stacklevel=2, - ) - break - - firstresult = self.spec.opts.get("firstresult") - else: - firstresult = False - - return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult) - - def call_historic(self, result_callback=None, kwargs=None): - """Call the hook with given ``kwargs`` for all registered plugins and - for all plugins which will be registered afterwards. - - If ``result_callback`` is not ``None`` it will be called for for each - non-``None`` result obtained from a hook implementation. - """ - self._call_history.append((kwargs or {}, result_callback)) - # Historizing hooks don't return results. - # Remember firstresult isn't compatible with historic. - res = self._hookexec(self.name, self.get_hookimpls(), kwargs, False) - if result_callback is None: - return - for x in res or []: - result_callback(x) - - def call_extra(self, methods, kwargs): - """Call the hook with some additional temporarily participating - methods using the specified ``kwargs`` as call parameters.""" - old = list(self._nonwrappers), list(self._wrappers) - for method in methods: - opts = dict(hookwrapper=False, trylast=False, tryfirst=False) - hookimpl = HookImpl(None, "<temp>", method, opts) - self._add_hookimpl(hookimpl) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old - - def _maybe_apply_history(self, method): - """Apply call history to a new hookimpl if it is marked as historic.""" - if self.is_historic(): - for kwargs, result_callback in self._call_history: - res = self._hookexec(self.name, [method], kwargs, False) - if res and result_callback is not None: - result_callback(res[0]) - - -class HookImpl: - def __init__(self, plugin, plugin_name, function, hook_impl_opts): - self.function = function - self.argnames, self.kwargnames = varnames(self.function) - self.plugin = plugin - self.opts = hook_impl_opts - self.plugin_name = plugin_name - self.__dict__.update(hook_impl_opts) - - def __repr__(self): - return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>" - - -class HookSpec: - def __init__(self, namespace, name, opts): - self.namespace = namespace - self.function = function = getattr(namespace, name) - self.name = name - self.argnames, self.kwargnames = varnames(function) - self.opts = opts - self.warn_on_impl = opts.get("warn_on_impl") +""" +Internal hook annotation, representation and calling machinery. +""" +import inspect +import sys +import warnings + + +class HookspecMarker: + """Decorator helper class for marking functions as hook specifications. + + You can instantiate it with a project_name to get a decorator. + Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions + if the :py:class:`.PluginManager` uses the same project_name. + """ + + def __init__(self, project_name): + self.project_name = project_name + + def __call__( + self, function=None, firstresult=False, historic=False, warn_on_impl=None + ): + """if passed a function, directly sets attributes on the function + which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`. + If passed no function, returns a decorator which can be applied to a function + later using the attributes supplied. + + If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered + hook implementation functions) will stop at I<=N when the I'th function + returns a non-``None`` result. + + If ``historic`` is ``True`` calls to a hook will be memorized and replayed + on later registered plugins. + + """ + + def setattr_hookspec_opts(func): + if historic and firstresult: + raise ValueError("cannot have a historic firstresult hook") + setattr( + func, + self.project_name + "_spec", + dict( + firstresult=firstresult, + historic=historic, + warn_on_impl=warn_on_impl, + ), + ) + return func + + if function is not None: + return setattr_hookspec_opts(function) + else: + return setattr_hookspec_opts + + +class HookimplMarker: + """Decorator helper class for marking functions as hook implementations. + + You can instantiate with a ``project_name`` to get a decorator. + Calling :py:meth:`.PluginManager.register` later will discover all marked functions + if the :py:class:`.PluginManager` uses the same project_name. + """ + + def __init__(self, project_name): + self.project_name = project_name + + def __call__( + self, + function=None, + hookwrapper=False, + optionalhook=False, + tryfirst=False, + trylast=False, + specname=None, + ): + + """if passed a function, directly sets attributes on the function + which will make it discoverable to :py:meth:`.PluginManager.register`. + If passed no function, returns a decorator which can be applied to a + function later using the attributes supplied. + + If ``optionalhook`` is ``True`` a missing matching hook specification will not result + in an error (by default it is an error if no matching spec is found). + + If ``tryfirst`` is ``True`` this hook implementation will run as early as possible + in the chain of N hook implementations for a specification. + + If ``trylast`` is ``True`` this hook implementation will run as late as possible + in the chain of N hook implementations. + + If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly + one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper + function is run. The code after the ``yield`` is run after all non-hookwrapper + function have run. The ``yield`` receives a :py:class:`.callers._Result` object + representing the exception or result outcome of the inner calls (including other + hookwrapper calls). + + If ``specname`` is provided, it will be used instead of the function name when + matching this hook implementation to a hook specification during registration. + + """ + + def setattr_hookimpl_opts(func): + setattr( + func, + self.project_name + "_impl", + dict( + hookwrapper=hookwrapper, + optionalhook=optionalhook, + tryfirst=tryfirst, + trylast=trylast, + specname=specname, + ), + ) + return func + + if function is None: + return setattr_hookimpl_opts + else: + return setattr_hookimpl_opts(function) + + +def normalize_hookimpl_opts(opts): + opts.setdefault("tryfirst", False) + opts.setdefault("trylast", False) + opts.setdefault("hookwrapper", False) + opts.setdefault("optionalhook", False) + opts.setdefault("specname", None) + + +_PYPY = hasattr(sys, "pypy_version_info") + + +def varnames(func): + """Return tuple of positional and keywrord argument names for a function, + method, class or callable. + + In case of a class, its ``__init__`` method is considered. + For methods the ``self`` parameter is not included. + """ + if inspect.isclass(func): + try: + func = func.__init__ + except AttributeError: + return (), () + elif not inspect.isroutine(func): # callable object? + try: + func = getattr(func, "__call__", func) + except Exception: + return (), () + + try: # func MUST be a function or method here or we won't parse any args + spec = inspect.getfullargspec(func) + except TypeError: + return (), () + + args, defaults = tuple(spec.args), spec.defaults + if defaults: + index = -len(defaults) + args, kwargs = args[:index], tuple(args[index:]) + else: + kwargs = () + + # strip any implicit instance arg + # pypy3 uses "obj" instead of "self" for default dunder methods + implicit_names = ("self",) if not _PYPY else ("self", "obj") + if args: + if inspect.ismethod(func) or ( + "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names + ): + args = args[1:] + + return args, kwargs + + +class _HookRelay: + """hook holder object for performing 1:N hook calls where N is the number + of registered plugins. + + """ + + +class _HookCaller: + def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None): + self.name = name + self._wrappers = [] + self._nonwrappers = [] + self._hookexec = hook_execute + self._call_history = None + self.spec = None + if specmodule_or_class is not None: + assert spec_opts is not None + self.set_specification(specmodule_or_class, spec_opts) + + def has_spec(self): + return self.spec is not None + + def set_specification(self, specmodule_or_class, spec_opts): + assert not self.has_spec() + self.spec = HookSpec(specmodule_or_class, self.name, spec_opts) + if spec_opts.get("historic"): + self._call_history = [] + + def is_historic(self): + return self._call_history is not None + + def _remove_plugin(self, plugin): + def remove(wrappers): + for i, method in enumerate(wrappers): + if method.plugin == plugin: + del wrappers[i] + return True + + if remove(self._wrappers) is None: + if remove(self._nonwrappers) is None: + raise ValueError(f"plugin {plugin!r} not found") + + def get_hookimpls(self): + # Order is important for _hookexec + return self._nonwrappers + self._wrappers + + def _add_hookimpl(self, hookimpl): + """Add an implementation to the callback chain.""" + if hookimpl.hookwrapper: + methods = self._wrappers + else: + methods = self._nonwrappers + + if hookimpl.trylast: + methods.insert(0, hookimpl) + elif hookimpl.tryfirst: + methods.append(hookimpl) + else: + # find last non-tryfirst method + i = len(methods) - 1 + while i >= 0 and methods[i].tryfirst: + i -= 1 + methods.insert(i + 1, hookimpl) + + def __repr__(self): + return f"<_HookCaller {self.name!r}>" + + def __call__(self, *args, **kwargs): + if args: + raise TypeError("hook calling supports only keyword arguments") + assert not self.is_historic() + + # This is written to avoid expensive operations when not needed. + if self.spec: + for argname in self.spec.argnames: + if argname not in kwargs: + notincall = tuple(set(self.spec.argnames) - kwargs.keys()) + warnings.warn( + "Argument(s) {} which are declared in the hookspec " + "can not be found in this hook call".format(notincall), + stacklevel=2, + ) + break + + firstresult = self.spec.opts.get("firstresult") + else: + firstresult = False + + return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult) + + def call_historic(self, result_callback=None, kwargs=None): + """Call the hook with given ``kwargs`` for all registered plugins and + for all plugins which will be registered afterwards. + + If ``result_callback`` is not ``None`` it will be called for for each + non-``None`` result obtained from a hook implementation. + """ + self._call_history.append((kwargs or {}, result_callback)) + # Historizing hooks don't return results. + # Remember firstresult isn't compatible with historic. + res = self._hookexec(self.name, self.get_hookimpls(), kwargs, False) + if result_callback is None: + return + for x in res or []: + result_callback(x) + + def call_extra(self, methods, kwargs): + """Call the hook with some additional temporarily participating + methods using the specified ``kwargs`` as call parameters.""" + old = list(self._nonwrappers), list(self._wrappers) + for method in methods: + opts = dict(hookwrapper=False, trylast=False, tryfirst=False) + hookimpl = HookImpl(None, "<temp>", method, opts) + self._add_hookimpl(hookimpl) + try: + return self(**kwargs) + finally: + self._nonwrappers, self._wrappers = old + + def _maybe_apply_history(self, method): + """Apply call history to a new hookimpl if it is marked as historic.""" + if self.is_historic(): + for kwargs, result_callback in self._call_history: + res = self._hookexec(self.name, [method], kwargs, False) + if res and result_callback is not None: + result_callback(res[0]) + + +class HookImpl: + def __init__(self, plugin, plugin_name, function, hook_impl_opts): + self.function = function + self.argnames, self.kwargnames = varnames(self.function) + self.plugin = plugin + self.opts = hook_impl_opts + self.plugin_name = plugin_name + self.__dict__.update(hook_impl_opts) + + def __repr__(self): + return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>" + + +class HookSpec: + def __init__(self, namespace, name, opts): + self.namespace = namespace + self.function = function = getattr(namespace, name) + self.name = name + self.argnames, self.kwargnames = varnames(function) + self.opts = opts + self.warn_on_impl = opts.get("warn_on_impl") diff --git a/contrib/python/pluggy/py3/pluggy/_manager.py b/contrib/python/pluggy/py3/pluggy/_manager.py index 65f4e50842..1d5cefcadc 100644 --- a/contrib/python/pluggy/py3/pluggy/_manager.py +++ b/contrib/python/pluggy/py3/pluggy/_manager.py @@ -1,373 +1,373 @@ -import inspect -import sys -import warnings - -from . import _tracing -from ._callers import _Result, _multicall -from ._hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts - -if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata -else: - import importlib_metadata - - -def _warn_for_function(warning, function): - warnings.warn_explicit( - warning, - type(warning), - lineno=function.__code__.co_firstlineno, - filename=function.__code__.co_filename, - ) - - -class PluginValidationError(Exception): - """plugin failed validation. - - :param object plugin: the plugin which failed validation, - may be a module or an arbitrary object. - """ - - def __init__(self, plugin, message): - self.plugin = plugin - super(Exception, self).__init__(message) - - -class DistFacade: - """Emulate a pkg_resources Distribution""" - - def __init__(self, dist): - self._dist = dist - - @property - def project_name(self): - return self.metadata["name"] - - def __getattr__(self, attr, default=None): - return getattr(self._dist, attr, default) - - def __dir__(self): - return sorted(dir(self._dist) + ["_dist", "project_name"]) - - -class PluginManager: - """Core :py:class:`.PluginManager` class which manages registration - of plugin objects and 1:N hook calling. - - You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class) - <.PluginManager.add_hookspecs>`. - You can register plugin objects (which contain hooks) by calling - :py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager` - is initialized with a prefix that is searched for in the names of the dict - of registered plugin objects. - - For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing` - which will subsequently send debug information to the trace helper. - """ - - def __init__(self, project_name): - self.project_name = project_name - self._name2plugin = {} - self._plugin2hookcallers = {} - self._plugin_distinfo = [] - self.trace = _tracing.TagTracer().get("pluginmanage") - self.hook = _HookRelay() - self._inner_hookexec = _multicall - - def _hookexec(self, hook_name, methods, kwargs, firstresult): - # called from all hookcaller instances. - # enable_tracing will set its own wrapping function at self._inner_hookexec - return self._inner_hookexec(hook_name, methods, kwargs, firstresult) - - def register(self, plugin, name=None): - """Register a plugin and return its canonical name or ``None`` if the name - is blocked from registering. Raise a :py:class:`ValueError` if the plugin - is already registered.""" - plugin_name = name or self.get_canonical_name(plugin) - - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: - if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration - raise ValueError( - "Plugin already registered: %s=%s\n%s" - % (plugin_name, plugin, self._name2plugin) - ) - - # XXX if an error happens we should make sure no state has been - # changed at point of return - self._name2plugin[plugin_name] = plugin - - # register matching hook implementations of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] - for name in dir(plugin): - hookimpl_opts = self.parse_hookimpl_opts(plugin, name) - if hookimpl_opts is not None: - normalize_hookimpl_opts(hookimpl_opts) - method = getattr(plugin, name) - hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts) - name = hookimpl_opts.get("specname") or name - hook = getattr(self.hook, name, None) - if hook is None: - hook = _HookCaller(name, self._hookexec) - setattr(self.hook, name, hook) - elif hook.has_spec(): - self._verify_hook(hook, hookimpl) - hook._maybe_apply_history(hookimpl) - hook._add_hookimpl(hookimpl) - hookcallers.append(hook) - return plugin_name - - def parse_hookimpl_opts(self, plugin, name): - method = getattr(plugin, name) - if not inspect.isroutine(method): - return - try: - res = getattr(method, self.project_name + "_impl", None) - except Exception: - res = {} - if res is not None and not isinstance(res, dict): - # false positive - res = None - return res - - def unregister(self, plugin=None, name=None): - """unregister a plugin object and all its contained hook implementations - from internal data structures.""" - if name is None: - assert plugin is not None, "one of name or plugin needs to be specified" - name = self.get_name(plugin) - - if plugin is None: - plugin = self.get_plugin(name) - - # if self._name2plugin[name] == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2plugin[name] - - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - - return plugin - - def set_blocked(self, name): - """block registrations of the given name, unregister if already registered.""" - self.unregister(name=name) - self._name2plugin[name] = None - - def is_blocked(self, name): - """return ``True`` if the given plugin name is blocked.""" - return name in self._name2plugin and self._name2plugin[name] is None - - def add_hookspecs(self, module_or_class): - """add new hook specifications defined in the given ``module_or_class``. - Functions are recognized if they have been decorated accordingly.""" - names = [] - for name in dir(module_or_class): - spec_opts = self.parse_hookspec_opts(module_or_class, name) - if spec_opts is not None: - hc = getattr(self.hook, name, None) - if hc is None: - hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts) - setattr(self.hook, name, hc) - else: - # plugins registered this hook without knowing the spec - hc.set_specification(module_or_class, spec_opts) - for hookfunction in hc.get_hookimpls(): - self._verify_hook(hc, hookfunction) - names.append(name) - - if not names: - raise ValueError( - f"did not find any {self.project_name!r} hooks in {module_or_class!r}" - ) - - def parse_hookspec_opts(self, module_or_class, name): - method = getattr(module_or_class, name) - return getattr(method, self.project_name + "_spec", None) - - def get_plugins(self): - """return the set of registered plugins.""" - return set(self._plugin2hookcallers) - - def is_registered(self, plugin): - """Return ``True`` if the plugin is already registered.""" - return plugin in self._plugin2hookcallers - - def get_canonical_name(self, plugin): - """Return canonical name for a plugin object. Note that a plugin - may be registered under a different name which was specified - by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`. - To obtain the name of an registered plugin use :py:meth:`get_name(plugin) - <.PluginManager.get_name>` instead.""" - return getattr(plugin, "__name__", None) or str(id(plugin)) - - def get_plugin(self, name): - """Return a plugin or ``None`` for the given name.""" - return self._name2plugin.get(name) - - def has_plugin(self, name): - """Return ``True`` if a plugin with the given name is registered.""" - return self.get_plugin(name) is not None - - def get_name(self, plugin): - """Return name for registered plugin or ``None`` if not registered.""" - for name, val in self._name2plugin.items(): - if plugin == val: - return name - - def _verify_hook(self, hook, hookimpl): - if hook.is_historic() and hookimpl.hookwrapper: - raise PluginValidationError( - hookimpl.plugin, - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" - % (hookimpl.plugin_name, hook.name), - ) - - if hook.spec.warn_on_impl: - _warn_for_function(hook.spec.warn_on_impl, hookimpl.function) - - # positional arg checking - notinspec = set(hookimpl.argnames) - set(hook.spec.argnames) - if notinspec: - raise PluginValidationError( - hookimpl.plugin, - "Plugin %r for hook %r\nhookimpl definition: %s\n" - "Argument(s) %s are declared in the hookimpl but " - "can not be found in the hookspec" - % ( - hookimpl.plugin_name, - hook.name, - _formatdef(hookimpl.function), - notinspec, - ), - ) - - if hookimpl.hookwrapper and not inspect.isgeneratorfunction(hookimpl.function): - raise PluginValidationError( - hookimpl.plugin, - "Plugin %r for hook %r\nhookimpl definition: %s\n" - "Declared as hookwrapper=True but function is not a generator function" - % (hookimpl.plugin_name, hook.name, _formatdef(hookimpl.function)), - ) - - def check_pending(self): - """Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`.""" - for name in self.hook.__dict__: - if name[0] != "_": - hook = getattr(self.hook, name) - if not hook.has_spec(): - for hookimpl in hook.get_hookimpls(): - if not hookimpl.optionalhook: - raise PluginValidationError( - hookimpl.plugin, - "unknown hook %r in plugin %r" - % (name, hookimpl.plugin), - ) - - def load_setuptools_entrypoints(self, group, name=None): - """Load modules from querying the specified setuptools ``group``. - - :param str group: entry point group to load plugins - :param str name: if given, loads only plugins with the given ``name``. - :rtype: int - :return: return the number of loaded plugins by this call. - """ - count = 0 - for dist in list(importlib_metadata.distributions()): - for ep in dist.entry_points: - if ( - ep.group != group - or (name is not None and ep.name != name) - # already registered - or self.get_plugin(ep.name) - or self.is_blocked(ep.name) - ): - continue - plugin = ep.load() - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((plugin, DistFacade(dist))) - count += 1 - return count - - def list_plugin_distinfo(self): - """return list of distinfo/plugin tuples for all setuptools registered - plugins.""" - return list(self._plugin_distinfo) - - def list_name_plugin(self): - """return list of name/plugin pairs.""" - return list(self._name2plugin.items()) - - def get_hookcallers(self, plugin): - """get all hook callers for the specified plugin.""" - return self._plugin2hookcallers.get(plugin) - - def add_hookcall_monitoring(self, before, after): - """add before/after tracing functions for all hooks - and return an undo function which, when called, - will remove the added tracers. - - ``before(hook_name, hook_impls, kwargs)`` will be called ahead - of all hook calls and receive a hookcaller instance, a list - of HookImpl instances and the keyword arguments for the hook call. - - ``after(outcome, hook_name, hook_impls, kwargs)`` receives the - same arguments as ``before`` but also a :py:class:`pluggy._callers._Result` object - which represents the result of the overall hook call. - """ - oldcall = self._inner_hookexec - - def traced_hookexec(hook_name, hook_impls, kwargs, firstresult): - before(hook_name, hook_impls, kwargs) - outcome = _Result.from_call( - lambda: oldcall(hook_name, hook_impls, kwargs, firstresult) - ) - after(outcome, hook_name, hook_impls, kwargs) - return outcome.get_result() - - self._inner_hookexec = traced_hookexec - - def undo(): - self._inner_hookexec = oldcall - - return undo - - def enable_tracing(self): - """enable tracing of hook calls and return an undo function.""" - hooktrace = self.trace.root.get("hook") - - def before(hook_name, methods, kwargs): - hooktrace.root.indent += 1 - hooktrace(hook_name, kwargs) - - def after(outcome, hook_name, methods, kwargs): - if outcome.excinfo is None: - hooktrace("finish", hook_name, "-->", outcome.get_result()) - hooktrace.root.indent -= 1 - - return self.add_hookcall_monitoring(before, after) - - def subset_hook_caller(self, name, remove_plugins): - """Return a new :py:class:`._hooks._HookCaller` instance for the named method - which manages calls to all registered plugins except the - ones from remove_plugins.""" - orig = getattr(self.hook, name) - plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)] - if plugins_to_remove: - hc = _HookCaller( - orig.name, orig._hookexec, orig.spec.namespace, orig.spec.opts - ) - for hookimpl in orig.get_hookimpls(): - plugin = hookimpl.plugin - if plugin not in plugins_to_remove: - hc._add_hookimpl(hookimpl) - # we also keep track of this hook caller so it - # gets properly removed on plugin unregistration - self._plugin2hookcallers.setdefault(plugin, []).append(hc) - return hc - return orig - - -def _formatdef(func): - return f"{func.__name__}{inspect.signature(func)}" +import inspect +import sys +import warnings + +from . import _tracing +from ._callers import _Result, _multicall +from ._hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts + +if sys.version_info >= (3, 8): + from importlib import metadata as importlib_metadata +else: + import importlib_metadata + + +def _warn_for_function(warning, function): + warnings.warn_explicit( + warning, + type(warning), + lineno=function.__code__.co_firstlineno, + filename=function.__code__.co_filename, + ) + + +class PluginValidationError(Exception): + """plugin failed validation. + + :param object plugin: the plugin which failed validation, + may be a module or an arbitrary object. + """ + + def __init__(self, plugin, message): + self.plugin = plugin + super(Exception, self).__init__(message) + + +class DistFacade: + """Emulate a pkg_resources Distribution""" + + def __init__(self, dist): + self._dist = dist + + @property + def project_name(self): + return self.metadata["name"] + + def __getattr__(self, attr, default=None): + return getattr(self._dist, attr, default) + + def __dir__(self): + return sorted(dir(self._dist) + ["_dist", "project_name"]) + + +class PluginManager: + """Core :py:class:`.PluginManager` class which manages registration + of plugin objects and 1:N hook calling. + + You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class) + <.PluginManager.add_hookspecs>`. + You can register plugin objects (which contain hooks) by calling + :py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager` + is initialized with a prefix that is searched for in the names of the dict + of registered plugin objects. + + For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing` + which will subsequently send debug information to the trace helper. + """ + + def __init__(self, project_name): + self.project_name = project_name + self._name2plugin = {} + self._plugin2hookcallers = {} + self._plugin_distinfo = [] + self.trace = _tracing.TagTracer().get("pluginmanage") + self.hook = _HookRelay() + self._inner_hookexec = _multicall + + def _hookexec(self, hook_name, methods, kwargs, firstresult): + # called from all hookcaller instances. + # enable_tracing will set its own wrapping function at self._inner_hookexec + return self._inner_hookexec(hook_name, methods, kwargs, firstresult) + + def register(self, plugin, name=None): + """Register a plugin and return its canonical name or ``None`` if the name + is blocked from registering. Raise a :py:class:`ValueError` if the plugin + is already registered.""" + plugin_name = name or self.get_canonical_name(plugin) + + if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: + if self._name2plugin.get(plugin_name, -1) is None: + return # blocked plugin, return None to indicate no registration + raise ValueError( + "Plugin already registered: %s=%s\n%s" + % (plugin_name, plugin, self._name2plugin) + ) + + # XXX if an error happens we should make sure no state has been + # changed at point of return + self._name2plugin[plugin_name] = plugin + + # register matching hook implementations of the plugin + self._plugin2hookcallers[plugin] = hookcallers = [] + for name in dir(plugin): + hookimpl_opts = self.parse_hookimpl_opts(plugin, name) + if hookimpl_opts is not None: + normalize_hookimpl_opts(hookimpl_opts) + method = getattr(plugin, name) + hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts) + name = hookimpl_opts.get("specname") or name + hook = getattr(self.hook, name, None) + if hook is None: + hook = _HookCaller(name, self._hookexec) + setattr(self.hook, name, hook) + elif hook.has_spec(): + self._verify_hook(hook, hookimpl) + hook._maybe_apply_history(hookimpl) + hook._add_hookimpl(hookimpl) + hookcallers.append(hook) + return plugin_name + + def parse_hookimpl_opts(self, plugin, name): + method = getattr(plugin, name) + if not inspect.isroutine(method): + return + try: + res = getattr(method, self.project_name + "_impl", None) + except Exception: + res = {} + if res is not None and not isinstance(res, dict): + # false positive + res = None + return res + + def unregister(self, plugin=None, name=None): + """unregister a plugin object and all its contained hook implementations + from internal data structures.""" + if name is None: + assert plugin is not None, "one of name or plugin needs to be specified" + name = self.get_name(plugin) + + if plugin is None: + plugin = self.get_plugin(name) + + # if self._name2plugin[name] == None registration was blocked: ignore + if self._name2plugin.get(name): + del self._name2plugin[name] + + for hookcaller in self._plugin2hookcallers.pop(plugin, []): + hookcaller._remove_plugin(plugin) + + return plugin + + def set_blocked(self, name): + """block registrations of the given name, unregister if already registered.""" + self.unregister(name=name) + self._name2plugin[name] = None + + def is_blocked(self, name): + """return ``True`` if the given plugin name is blocked.""" + return name in self._name2plugin and self._name2plugin[name] is None + + def add_hookspecs(self, module_or_class): + """add new hook specifications defined in the given ``module_or_class``. + Functions are recognized if they have been decorated accordingly.""" + names = [] + for name in dir(module_or_class): + spec_opts = self.parse_hookspec_opts(module_or_class, name) + if spec_opts is not None: + hc = getattr(self.hook, name, None) + if hc is None: + hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts) + setattr(self.hook, name, hc) + else: + # plugins registered this hook without knowing the spec + hc.set_specification(module_or_class, spec_opts) + for hookfunction in hc.get_hookimpls(): + self._verify_hook(hc, hookfunction) + names.append(name) + + if not names: + raise ValueError( + f"did not find any {self.project_name!r} hooks in {module_or_class!r}" + ) + + def parse_hookspec_opts(self, module_or_class, name): + method = getattr(module_or_class, name) + return getattr(method, self.project_name + "_spec", None) + + def get_plugins(self): + """return the set of registered plugins.""" + return set(self._plugin2hookcallers) + + def is_registered(self, plugin): + """Return ``True`` if the plugin is already registered.""" + return plugin in self._plugin2hookcallers + + def get_canonical_name(self, plugin): + """Return canonical name for a plugin object. Note that a plugin + may be registered under a different name which was specified + by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`. + To obtain the name of an registered plugin use :py:meth:`get_name(plugin) + <.PluginManager.get_name>` instead.""" + return getattr(plugin, "__name__", None) or str(id(plugin)) + + def get_plugin(self, name): + """Return a plugin or ``None`` for the given name.""" + return self._name2plugin.get(name) + + def has_plugin(self, name): + """Return ``True`` if a plugin with the given name is registered.""" + return self.get_plugin(name) is not None + + def get_name(self, plugin): + """Return name for registered plugin or ``None`` if not registered.""" + for name, val in self._name2plugin.items(): + if plugin == val: + return name + + def _verify_hook(self, hook, hookimpl): + if hook.is_historic() and hookimpl.hookwrapper: + raise PluginValidationError( + hookimpl.plugin, + "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" + % (hookimpl.plugin_name, hook.name), + ) + + if hook.spec.warn_on_impl: + _warn_for_function(hook.spec.warn_on_impl, hookimpl.function) + + # positional arg checking + notinspec = set(hookimpl.argnames) - set(hook.spec.argnames) + if notinspec: + raise PluginValidationError( + hookimpl.plugin, + "Plugin %r for hook %r\nhookimpl definition: %s\n" + "Argument(s) %s are declared in the hookimpl but " + "can not be found in the hookspec" + % ( + hookimpl.plugin_name, + hook.name, + _formatdef(hookimpl.function), + notinspec, + ), + ) + + if hookimpl.hookwrapper and not inspect.isgeneratorfunction(hookimpl.function): + raise PluginValidationError( + hookimpl.plugin, + "Plugin %r for hook %r\nhookimpl definition: %s\n" + "Declared as hookwrapper=True but function is not a generator function" + % (hookimpl.plugin_name, hook.name, _formatdef(hookimpl.function)), + ) + + def check_pending(self): + """Verify that all hooks which have not been verified against + a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`.""" + for name in self.hook.__dict__: + if name[0] != "_": + hook = getattr(self.hook, name) + if not hook.has_spec(): + for hookimpl in hook.get_hookimpls(): + if not hookimpl.optionalhook: + raise PluginValidationError( + hookimpl.plugin, + "unknown hook %r in plugin %r" + % (name, hookimpl.plugin), + ) + + def load_setuptools_entrypoints(self, group, name=None): + """Load modules from querying the specified setuptools ``group``. + + :param str group: entry point group to load plugins + :param str name: if given, loads only plugins with the given ``name``. + :rtype: int + :return: return the number of loaded plugins by this call. + """ + count = 0 + for dist in list(importlib_metadata.distributions()): + for ep in dist.entry_points: + if ( + ep.group != group + or (name is not None and ep.name != name) + # already registered + or self.get_plugin(ep.name) + or self.is_blocked(ep.name) + ): + continue + plugin = ep.load() + self.register(plugin, name=ep.name) + self._plugin_distinfo.append((plugin, DistFacade(dist))) + count += 1 + return count + + def list_plugin_distinfo(self): + """return list of distinfo/plugin tuples for all setuptools registered + plugins.""" + return list(self._plugin_distinfo) + + def list_name_plugin(self): + """return list of name/plugin pairs.""" + return list(self._name2plugin.items()) + + def get_hookcallers(self, plugin): + """get all hook callers for the specified plugin.""" + return self._plugin2hookcallers.get(plugin) + + def add_hookcall_monitoring(self, before, after): + """add before/after tracing functions for all hooks + and return an undo function which, when called, + will remove the added tracers. + + ``before(hook_name, hook_impls, kwargs)`` will be called ahead + of all hook calls and receive a hookcaller instance, a list + of HookImpl instances and the keyword arguments for the hook call. + + ``after(outcome, hook_name, hook_impls, kwargs)`` receives the + same arguments as ``before`` but also a :py:class:`pluggy._callers._Result` object + which represents the result of the overall hook call. + """ + oldcall = self._inner_hookexec + + def traced_hookexec(hook_name, hook_impls, kwargs, firstresult): + before(hook_name, hook_impls, kwargs) + outcome = _Result.from_call( + lambda: oldcall(hook_name, hook_impls, kwargs, firstresult) + ) + after(outcome, hook_name, hook_impls, kwargs) + return outcome.get_result() + + self._inner_hookexec = traced_hookexec + + def undo(): + self._inner_hookexec = oldcall + + return undo + + def enable_tracing(self): + """enable tracing of hook calls and return an undo function.""" + hooktrace = self.trace.root.get("hook") + + def before(hook_name, methods, kwargs): + hooktrace.root.indent += 1 + hooktrace(hook_name, kwargs) + + def after(outcome, hook_name, methods, kwargs): + if outcome.excinfo is None: + hooktrace("finish", hook_name, "-->", outcome.get_result()) + hooktrace.root.indent -= 1 + + return self.add_hookcall_monitoring(before, after) + + def subset_hook_caller(self, name, remove_plugins): + """Return a new :py:class:`._hooks._HookCaller` instance for the named method + which manages calls to all registered plugins except the + ones from remove_plugins.""" + orig = getattr(self.hook, name) + plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)] + if plugins_to_remove: + hc = _HookCaller( + orig.name, orig._hookexec, orig.spec.namespace, orig.spec.opts + ) + for hookimpl in orig.get_hookimpls(): + plugin = hookimpl.plugin + if plugin not in plugins_to_remove: + hc._add_hookimpl(hookimpl) + # we also keep track of this hook caller so it + # gets properly removed on plugin unregistration + self._plugin2hookcallers.setdefault(plugin, []).append(hc) + return hc + return orig + + +def _formatdef(func): + return f"{func.__name__}{inspect.signature(func)}" diff --git a/contrib/python/pluggy/py3/pluggy/_result.py b/contrib/python/pluggy/py3/pluggy/_result.py index 4c1f7f1f3c..b8a8520772 100644 --- a/contrib/python/pluggy/py3/pluggy/_result.py +++ b/contrib/python/pluggy/py3/pluggy/_result.py @@ -1,60 +1,60 @@ -""" -Hook wrapper "result" utilities. -""" -import sys - - -def _raise_wrapfail(wrap_controller, msg): - co = wrap_controller.gi_code - raise RuntimeError( - "wrap_controller at %r %s:%d %s" - % (co.co_name, co.co_filename, co.co_firstlineno, msg) - ) - - -class HookCallError(Exception): - """Hook was called wrongly.""" - - -class _Result: - def __init__(self, result, excinfo): - self._result = result - self._excinfo = excinfo - - @property - def excinfo(self): - return self._excinfo - - @classmethod - def from_call(cls, func): - __tracebackhide__ = True - result = excinfo = None - try: - result = func() - except BaseException: - excinfo = sys.exc_info() - - return cls(result, excinfo) - - def force_result(self, result): - """Force the result(s) to ``result``. - - If the hook was marked as a ``firstresult`` a single value should - be set otherwise set a (modified) list of results. Any exceptions - found during invocation will be deleted. - """ - self._result = result - self._excinfo = None - - def get_result(self): - """Get the result(s) for this hook call. - - If the hook was marked as a ``firstresult`` only a single value - will be returned otherwise a list of results. - """ - __tracebackhide__ = True - if self._excinfo is None: - return self._result - else: - ex = self._excinfo - raise ex[1].with_traceback(ex[2]) +""" +Hook wrapper "result" utilities. +""" +import sys + + +def _raise_wrapfail(wrap_controller, msg): + co = wrap_controller.gi_code + raise RuntimeError( + "wrap_controller at %r %s:%d %s" + % (co.co_name, co.co_filename, co.co_firstlineno, msg) + ) + + +class HookCallError(Exception): + """Hook was called wrongly.""" + + +class _Result: + def __init__(self, result, excinfo): + self._result = result + self._excinfo = excinfo + + @property + def excinfo(self): + return self._excinfo + + @classmethod + def from_call(cls, func): + __tracebackhide__ = True + result = excinfo = None + try: + result = func() + except BaseException: + excinfo = sys.exc_info() + + return cls(result, excinfo) + + def force_result(self, result): + """Force the result(s) to ``result``. + + If the hook was marked as a ``firstresult`` a single value should + be set otherwise set a (modified) list of results. Any exceptions + found during invocation will be deleted. + """ + self._result = result + self._excinfo = None + + def get_result(self): + """Get the result(s) for this hook call. + + If the hook was marked as a ``firstresult`` only a single value + will be returned otherwise a list of results. + """ + __tracebackhide__ = True + if self._excinfo is None: + return self._result + else: + ex = self._excinfo + raise ex[1].with_traceback(ex[2]) diff --git a/contrib/python/pluggy/py3/pluggy/_tracing.py b/contrib/python/pluggy/py3/pluggy/_tracing.py index 82c016271e..0e0d3d7175 100644 --- a/contrib/python/pluggy/py3/pluggy/_tracing.py +++ b/contrib/python/pluggy/py3/pluggy/_tracing.py @@ -1,62 +1,62 @@ -""" -Tracing utils -""" - - -class TagTracer: - def __init__(self): - self._tags2proc = {} - self._writer = None - self.indent = 0 - - def get(self, name): - return TagTracerSub(self, (name,)) - - def _format_message(self, tags, args): - if isinstance(args[-1], dict): - extra = args[-1] - args = args[:-1] - else: - extra = {} - - content = " ".join(map(str, args)) - indent = " " * self.indent - - lines = ["{}{} [{}]\n".format(indent, content, ":".join(tags))] - - for name, value in extra.items(): - lines.append(f"{indent} {name}: {value}\n") - - return "".join(lines) - - def _processmessage(self, tags, args): - if self._writer is not None and args: - self._writer(self._format_message(tags, args)) - try: - processor = self._tags2proc[tags] - except KeyError: - pass - else: - processor(tags, args) - - def setwriter(self, writer): - self._writer = writer - - def setprocessor(self, tags, processor): - if isinstance(tags, str): - tags = tuple(tags.split(":")) - else: - assert isinstance(tags, tuple) - self._tags2proc[tags] = processor - - -class TagTracerSub: - def __init__(self, root, tags): - self.root = root - self.tags = tags - - def __call__(self, *args): - self.root._processmessage(self.tags, args) - - def get(self, name): - return self.__class__(self.root, self.tags + (name,)) +""" +Tracing utils +""" + + +class TagTracer: + def __init__(self): + self._tags2proc = {} + self._writer = None + self.indent = 0 + + def get(self, name): + return TagTracerSub(self, (name,)) + + def _format_message(self, tags, args): + if isinstance(args[-1], dict): + extra = args[-1] + args = args[:-1] + else: + extra = {} + + content = " ".join(map(str, args)) + indent = " " * self.indent + + lines = ["{}{} [{}]\n".format(indent, content, ":".join(tags))] + + for name, value in extra.items(): + lines.append(f"{indent} {name}: {value}\n") + + return "".join(lines) + + def _processmessage(self, tags, args): + if self._writer is not None and args: + self._writer(self._format_message(tags, args)) + try: + processor = self._tags2proc[tags] + except KeyError: + pass + else: + processor(tags, args) + + def setwriter(self, writer): + self._writer = writer + + def setprocessor(self, tags, processor): + if isinstance(tags, str): + tags = tuple(tags.split(":")) + else: + assert isinstance(tags, tuple) + self._tags2proc[tags] = processor + + +class TagTracerSub: + def __init__(self, root, tags): + self.root = root + self.tags = tags + + def __call__(self, *args): + self.root._processmessage(self.tags, args) + + def get(self, name): + return self.__class__(self.root, self.tags + (name,)) diff --git a/contrib/python/pluggy/py3/pluggy/_version.py b/contrib/python/pluggy/py3/pluggy/_version.py index 7f0e082d0d..89929710b2 100644 --- a/contrib/python/pluggy/py3/pluggy/_version.py +++ b/contrib/python/pluggy/py3/pluggy/_version.py @@ -1,5 +1,5 @@ -# coding: utf-8 -# file generated by setuptools_scm -# don't change, don't track in version control -version = '1.0.0' -version_tuple = (1, 0, 0) +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '1.0.0' +version_tuple = (1, 0, 0) diff --git a/contrib/python/pluggy/py3/tests/benchmark.py b/contrib/python/pluggy/py3/tests/benchmark.py index b0d4b9536a..ed7a18720d 100644 --- a/contrib/python/pluggy/py3/tests/benchmark.py +++ b/contrib/python/pluggy/py3/tests/benchmark.py @@ -1,102 +1,102 @@ -""" -Benchmarking and performance tests. -""" -import pytest -from pluggy import HookspecMarker, HookimplMarker, PluginManager -from pluggy._hooks import HookImpl -from pluggy._callers import _multicall - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -@hookimpl -def hook(arg1, arg2, arg3): - return arg1, arg2, arg3 - - -@hookimpl(hookwrapper=True) -def wrapper(arg1, arg2, arg3): - yield - - -@pytest.fixture(params=[10, 100], ids="hooks={}".format) -def hooks(request): - return [hook for i in range(request.param)] - - -@pytest.fixture(params=[10, 100], ids="wrappers={}".format) -def wrappers(request): - return [wrapper for i in range(request.param)] - - -def test_hook_and_wrappers_speed(benchmark, hooks, wrappers): - def setup(): - hook_name = "foo" - hook_impls = [] - for method in hooks + wrappers: - f = HookImpl(None, "<temp>", method, method.example_impl) - hook_impls.append(f) - caller_kwargs = {"arg1": 1, "arg2": 2, "arg3": 3} - firstresult = False - return (hook_name, hook_impls, caller_kwargs, firstresult), {} - - benchmark.pedantic(_multicall, setup=setup) - - -@pytest.mark.parametrize( - ("plugins, wrappers, nesting"), - [ - (1, 1, 0), - (1, 1, 1), - (1, 1, 5), - (1, 5, 1), - (1, 5, 5), - (5, 1, 1), - (5, 1, 5), - (5, 5, 1), - (5, 5, 5), - (20, 20, 0), - (100, 100, 0), - ], -) -def test_call_hook(benchmark, plugins, wrappers, nesting): - pm = PluginManager("example") - - class HookSpec: - @hookspec - def fun(self, hooks, nesting: int): - yield - - class Plugin: - def __init__(self, num): - self.num = num - - def __repr__(self): - return f"<Plugin {self.num}>" - - @hookimpl - def fun(self, hooks, nesting: int): - if nesting: - hooks.fun(hooks=hooks, nesting=nesting - 1) - - class PluginWrap: - def __init__(self, num): - self.num = num - - def __repr__(self): - return f"<PluginWrap {self.num}>" - - @hookimpl(hookwrapper=True) - def fun(self): - yield - - pm.add_hookspecs(HookSpec) - - for i in range(plugins): - pm.register(Plugin(i), name=f"plug_{i}") - for i in range(wrappers): - pm.register(PluginWrap(i), name=f"wrap_plug_{i}") - - benchmark(pm.hook.fun, hooks=pm.hook, nesting=nesting) +""" +Benchmarking and performance tests. +""" +import pytest +from pluggy import HookspecMarker, HookimplMarker, PluginManager +from pluggy._hooks import HookImpl +from pluggy._callers import _multicall + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +@hookimpl +def hook(arg1, arg2, arg3): + return arg1, arg2, arg3 + + +@hookimpl(hookwrapper=True) +def wrapper(arg1, arg2, arg3): + yield + + +@pytest.fixture(params=[10, 100], ids="hooks={}".format) +def hooks(request): + return [hook for i in range(request.param)] + + +@pytest.fixture(params=[10, 100], ids="wrappers={}".format) +def wrappers(request): + return [wrapper for i in range(request.param)] + + +def test_hook_and_wrappers_speed(benchmark, hooks, wrappers): + def setup(): + hook_name = "foo" + hook_impls = [] + for method in hooks + wrappers: + f = HookImpl(None, "<temp>", method, method.example_impl) + hook_impls.append(f) + caller_kwargs = {"arg1": 1, "arg2": 2, "arg3": 3} + firstresult = False + return (hook_name, hook_impls, caller_kwargs, firstresult), {} + + benchmark.pedantic(_multicall, setup=setup) + + +@pytest.mark.parametrize( + ("plugins, wrappers, nesting"), + [ + (1, 1, 0), + (1, 1, 1), + (1, 1, 5), + (1, 5, 1), + (1, 5, 5), + (5, 1, 1), + (5, 1, 5), + (5, 5, 1), + (5, 5, 5), + (20, 20, 0), + (100, 100, 0), + ], +) +def test_call_hook(benchmark, plugins, wrappers, nesting): + pm = PluginManager("example") + + class HookSpec: + @hookspec + def fun(self, hooks, nesting: int): + yield + + class Plugin: + def __init__(self, num): + self.num = num + + def __repr__(self): + return f"<Plugin {self.num}>" + + @hookimpl + def fun(self, hooks, nesting: int): + if nesting: + hooks.fun(hooks=hooks, nesting=nesting - 1) + + class PluginWrap: + def __init__(self, num): + self.num = num + + def __repr__(self): + return f"<PluginWrap {self.num}>" + + @hookimpl(hookwrapper=True) + def fun(self): + yield + + pm.add_hookspecs(HookSpec) + + for i in range(plugins): + pm.register(Plugin(i), name=f"plug_{i}") + for i in range(wrappers): + pm.register(PluginWrap(i), name=f"wrap_plug_{i}") + + benchmark(pm.hook.fun, hooks=pm.hook, nesting=nesting) diff --git a/contrib/python/pluggy/py3/tests/conftest.py b/contrib/python/pluggy/py3/tests/conftest.py index 1fd4ecd5bd..e4cbe346e3 100644 --- a/contrib/python/pluggy/py3/tests/conftest.py +++ b/contrib/python/pluggy/py3/tests/conftest.py @@ -1,26 +1,26 @@ -import pytest - - -@pytest.fixture( - params=[lambda spec: spec, lambda spec: spec()], - ids=["spec-is-class", "spec-is-instance"], -) -def he_pm(request, pm): - from pluggy import HookspecMarker - - hookspec = HookspecMarker("example") - - class Hooks: - @hookspec - def he_method1(self, arg): - return arg + 1 - - pm.add_hookspecs(request.param(Hooks)) - return pm - - -@pytest.fixture -def pm(): - from pluggy import PluginManager - - return PluginManager("example") +import pytest + + +@pytest.fixture( + params=[lambda spec: spec, lambda spec: spec()], + ids=["spec-is-class", "spec-is-instance"], +) +def he_pm(request, pm): + from pluggy import HookspecMarker + + hookspec = HookspecMarker("example") + + class Hooks: + @hookspec + def he_method1(self, arg): + return arg + 1 + + pm.add_hookspecs(request.param(Hooks)) + return pm + + +@pytest.fixture +def pm(): + from pluggy import PluginManager + + return PluginManager("example") diff --git a/contrib/python/pluggy/py3/tests/test_details.py b/contrib/python/pluggy/py3/tests/test_details.py index 0ceb3b3eb1..a4d4d777a2 100644 --- a/contrib/python/pluggy/py3/tests/test_details.py +++ b/contrib/python/pluggy/py3/tests/test_details.py @@ -1,135 +1,135 @@ -import warnings -import pytest -from pluggy import PluginManager, HookimplMarker, HookspecMarker - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_parse_hookimpl_override(): - class MyPluginManager(PluginManager): - def parse_hookimpl_opts(self, module_or_class, name): - opts = PluginManager.parse_hookimpl_opts(self, module_or_class, name) - if opts is None: - if name.startswith("x1"): - opts = {} - return opts - - class Plugin: - def x1meth(self): - pass - - @hookimpl(hookwrapper=True, tryfirst=True) - def x1meth2(self): - yield # pragma: no cover - - class Spec: - @hookspec - def x1meth(self): - pass - - @hookspec - def x1meth2(self): - pass - - pm = MyPluginManager(hookspec.project_name) - pm.register(Plugin()) - pm.add_hookspecs(Spec) - assert not pm.hook.x1meth._nonwrappers[0].hookwrapper - assert not pm.hook.x1meth._nonwrappers[0].tryfirst - assert not pm.hook.x1meth._nonwrappers[0].trylast - assert not pm.hook.x1meth._nonwrappers[0].optionalhook - - assert pm.hook.x1meth2._wrappers[0].tryfirst - assert pm.hook.x1meth2._wrappers[0].hookwrapper - - -def test_warn_when_deprecated_specified(recwarn): - warning = DeprecationWarning("foo is deprecated") - - class Spec: - @hookspec(warn_on_impl=warning) - def foo(self): - pass - - class Plugin: - @hookimpl - def foo(self): - pass - - pm = PluginManager(hookspec.project_name) - pm.add_hookspecs(Spec) - - with pytest.warns(DeprecationWarning) as records: - pm.register(Plugin()) - (record,) = records - assert record.message is warning - assert record.filename == Plugin.foo.__code__.co_filename - assert record.lineno == Plugin.foo.__code__.co_firstlineno - - -def test_plugin_getattr_raises_errors(): - """Pluggy must be able to handle plugins which raise weird exceptions - when getattr() gets called (#11). - """ - - class DontTouchMe: - def __getattr__(self, x): - raise Exception("cant touch me") - - class Module: - pass - - module = Module() - module.x = DontTouchMe() - - pm = PluginManager(hookspec.project_name) - # register() would raise an error - pm.register(module, "donttouch") - assert pm.get_plugin("donttouch") is module - - -def test_warning_on_call_vs_hookspec_arg_mismatch(): - """Verify that is a hook is called with less arguments then defined in the - spec that a warning is emitted. - """ - - class Spec: - @hookspec - def myhook(self, arg1, arg2): - pass - - class Plugin: - @hookimpl - def myhook(self, arg1): - pass - - pm = PluginManager(hookspec.project_name) - pm.register(Plugin()) - pm.add_hookspecs(Spec()) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - - # calling should trigger a warning - pm.hook.myhook(arg1=1) - - assert len(warns) == 1 - warning = warns[-1] - assert issubclass(warning.category, Warning) - assert "Argument(s) ('arg2',)" in str(warning.message) - - -def test_repr(): - class Plugin: - @hookimpl - def myhook(self): - raise NotImplementedError() - - pm = PluginManager(hookspec.project_name) - - plugin = Plugin() - pname = pm.register(plugin) - assert repr(pm.hook.myhook._nonwrappers[0]) == ( - f"<HookImpl plugin_name={pname!r}, plugin={plugin!r}>" - ) +import warnings +import pytest +from pluggy import PluginManager, HookimplMarker, HookspecMarker + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_parse_hookimpl_override(): + class MyPluginManager(PluginManager): + def parse_hookimpl_opts(self, module_or_class, name): + opts = PluginManager.parse_hookimpl_opts(self, module_or_class, name) + if opts is None: + if name.startswith("x1"): + opts = {} + return opts + + class Plugin: + def x1meth(self): + pass + + @hookimpl(hookwrapper=True, tryfirst=True) + def x1meth2(self): + yield # pragma: no cover + + class Spec: + @hookspec + def x1meth(self): + pass + + @hookspec + def x1meth2(self): + pass + + pm = MyPluginManager(hookspec.project_name) + pm.register(Plugin()) + pm.add_hookspecs(Spec) + assert not pm.hook.x1meth._nonwrappers[0].hookwrapper + assert not pm.hook.x1meth._nonwrappers[0].tryfirst + assert not pm.hook.x1meth._nonwrappers[0].trylast + assert not pm.hook.x1meth._nonwrappers[0].optionalhook + + assert pm.hook.x1meth2._wrappers[0].tryfirst + assert pm.hook.x1meth2._wrappers[0].hookwrapper + + +def test_warn_when_deprecated_specified(recwarn): + warning = DeprecationWarning("foo is deprecated") + + class Spec: + @hookspec(warn_on_impl=warning) + def foo(self): + pass + + class Plugin: + @hookimpl + def foo(self): + pass + + pm = PluginManager(hookspec.project_name) + pm.add_hookspecs(Spec) + + with pytest.warns(DeprecationWarning) as records: + pm.register(Plugin()) + (record,) = records + assert record.message is warning + assert record.filename == Plugin.foo.__code__.co_filename + assert record.lineno == Plugin.foo.__code__.co_firstlineno + + +def test_plugin_getattr_raises_errors(): + """Pluggy must be able to handle plugins which raise weird exceptions + when getattr() gets called (#11). + """ + + class DontTouchMe: + def __getattr__(self, x): + raise Exception("cant touch me") + + class Module: + pass + + module = Module() + module.x = DontTouchMe() + + pm = PluginManager(hookspec.project_name) + # register() would raise an error + pm.register(module, "donttouch") + assert pm.get_plugin("donttouch") is module + + +def test_warning_on_call_vs_hookspec_arg_mismatch(): + """Verify that is a hook is called with less arguments then defined in the + spec that a warning is emitted. + """ + + class Spec: + @hookspec + def myhook(self, arg1, arg2): + pass + + class Plugin: + @hookimpl + def myhook(self, arg1): + pass + + pm = PluginManager(hookspec.project_name) + pm.register(Plugin()) + pm.add_hookspecs(Spec()) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + + # calling should trigger a warning + pm.hook.myhook(arg1=1) + + assert len(warns) == 1 + warning = warns[-1] + assert issubclass(warning.category, Warning) + assert "Argument(s) ('arg2',)" in str(warning.message) + + +def test_repr(): + class Plugin: + @hookimpl + def myhook(self): + raise NotImplementedError() + + pm = PluginManager(hookspec.project_name) + + plugin = Plugin() + pname = pm.register(plugin) + assert repr(pm.hook.myhook._nonwrappers[0]) == ( + f"<HookImpl plugin_name={pname!r}, plugin={plugin!r}>" + ) diff --git a/contrib/python/pluggy/py3/tests/test_helpers.py b/contrib/python/pluggy/py3/tests/test_helpers.py index 465858c499..d5972ac543 100644 --- a/contrib/python/pluggy/py3/tests/test_helpers.py +++ b/contrib/python/pluggy/py3/tests/test_helpers.py @@ -1,84 +1,84 @@ -from pluggy._hooks import varnames -from pluggy._manager import _formatdef - - -def test_varnames(): - def f(x): - i = 3 # noqa - - class A: - def f(self, y): - pass - - class B: - def __call__(self, z): - pass - - assert varnames(f) == (("x",), ()) - assert varnames(A().f) == (("y",), ()) - assert varnames(B()) == (("z",), ()) - - -def test_varnames_default(): - def f(x, y=3): - pass - - assert varnames(f) == (("x",), ("y",)) - - -def test_varnames_class(): - class C: - def __init__(self, x): - pass - - class D: - pass - - class E: - def __init__(self, x): - pass - - class F: - pass - - assert varnames(C) == (("x",), ()) - assert varnames(D) == ((), ()) - assert varnames(E) == (("x",), ()) - assert varnames(F) == ((), ()) - - -def test_varnames_keyword_only(): - def f1(x, *, y): - pass - - def f2(x, *, y=3): - pass - - def f3(x=1, *, y=3): - pass - - assert varnames(f1) == (("x",), ()) - assert varnames(f2) == (("x",), ()) - assert varnames(f3) == ((), ("x",)) - - -def test_formatdef(): - def function1(): - pass - - assert _formatdef(function1) == "function1()" - - def function2(arg1): - pass - - assert _formatdef(function2) == "function2(arg1)" - - def function3(arg1, arg2="qwe"): - pass - - assert _formatdef(function3) == "function3(arg1, arg2='qwe')" - - def function4(arg1, *args, **kwargs): - pass - - assert _formatdef(function4) == "function4(arg1, *args, **kwargs)" +from pluggy._hooks import varnames +from pluggy._manager import _formatdef + + +def test_varnames(): + def f(x): + i = 3 # noqa + + class A: + def f(self, y): + pass + + class B: + def __call__(self, z): + pass + + assert varnames(f) == (("x",), ()) + assert varnames(A().f) == (("y",), ()) + assert varnames(B()) == (("z",), ()) + + +def test_varnames_default(): + def f(x, y=3): + pass + + assert varnames(f) == (("x",), ("y",)) + + +def test_varnames_class(): + class C: + def __init__(self, x): + pass + + class D: + pass + + class E: + def __init__(self, x): + pass + + class F: + pass + + assert varnames(C) == (("x",), ()) + assert varnames(D) == ((), ()) + assert varnames(E) == (("x",), ()) + assert varnames(F) == ((), ()) + + +def test_varnames_keyword_only(): + def f1(x, *, y): + pass + + def f2(x, *, y=3): + pass + + def f3(x=1, *, y=3): + pass + + assert varnames(f1) == (("x",), ()) + assert varnames(f2) == (("x",), ()) + assert varnames(f3) == ((), ("x",)) + + +def test_formatdef(): + def function1(): + pass + + assert _formatdef(function1) == "function1()" + + def function2(arg1): + pass + + assert _formatdef(function2) == "function2(arg1)" + + def function3(arg1, arg2="qwe"): + pass + + assert _formatdef(function3) == "function3(arg1, arg2='qwe')" + + def function4(arg1, *args, **kwargs): + pass + + assert _formatdef(function4) == "function4(arg1, *args, **kwargs)" diff --git a/contrib/python/pluggy/py3/tests/test_hookcaller.py b/contrib/python/pluggy/py3/tests/test_hookcaller.py index 9eeaef8666..7120a1c721 100644 --- a/contrib/python/pluggy/py3/tests/test_hookcaller.py +++ b/contrib/python/pluggy/py3/tests/test_hookcaller.py @@ -1,272 +1,272 @@ -import pytest - -from pluggy import HookimplMarker, HookspecMarker, PluginValidationError -from pluggy._hooks import HookImpl - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -@pytest.fixture -def hc(pm): - class Hooks: - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - return pm.hook.he_method1 - - -@pytest.fixture -def addmeth(hc): - def addmeth(tryfirst=False, trylast=False, hookwrapper=False): - def wrap(func): - hookimpl(tryfirst=tryfirst, trylast=trylast, hookwrapper=hookwrapper)(func) - hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl)) - return func - - return wrap - - return addmeth - - -def funcs(hookmethods): - return [hookmethod.function for hookmethod in hookmethods] - - -def test_adding_nonwrappers(hc, addmeth): - @addmeth() - def he_method1(): - pass - - @addmeth() - def he_method2(): - pass - - @addmeth() - def he_method3(): - pass - - assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3] - - -def test_adding_nonwrappers_trylast(hc, addmeth): - @addmeth() - def he_method1_middle(): - pass - - @addmeth(trylast=True) - def he_method1(): - pass - - @addmeth() - def he_method1_b(): - pass - - assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] - - -def test_adding_nonwrappers_trylast3(hc, addmeth): - @addmeth() - def he_method1_a(): - pass - - @addmeth(trylast=True) - def he_method1_b(): - pass - - @addmeth() - def he_method1_c(): - pass - - @addmeth(trylast=True) - def he_method1_d(): - pass - - assert funcs(hc._nonwrappers) == [ - he_method1_d, - he_method1_b, - he_method1_a, - he_method1_c, - ] - - -def test_adding_nonwrappers_trylast2(hc, addmeth): - @addmeth() - def he_method1_middle(): - pass - - @addmeth() - def he_method1_b(): - pass - - @addmeth(trylast=True) - def he_method1(): - pass - - assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] - - -def test_adding_nonwrappers_tryfirst(hc, addmeth): - @addmeth(tryfirst=True) - def he_method1(): - pass - - @addmeth() - def he_method1_middle(): - pass - - @addmeth() - def he_method1_b(): - pass - - assert funcs(hc._nonwrappers) == [he_method1_middle, he_method1_b, he_method1] - - -def test_adding_wrappers_ordering(hc, addmeth): - @addmeth(hookwrapper=True) - def he_method1(): - pass - - @addmeth() - def he_method1_middle(): - pass - - @addmeth(hookwrapper=True) - def he_method3(): - pass - - assert funcs(hc._nonwrappers) == [he_method1_middle] - assert funcs(hc._wrappers) == [he_method1, he_method3] - - -def test_adding_wrappers_ordering_tryfirst(hc, addmeth): - @addmeth(hookwrapper=True, tryfirst=True) - def he_method1(): - pass - - @addmeth(hookwrapper=True) - def he_method2(): - pass - - assert hc._nonwrappers == [] - assert funcs(hc._wrappers) == [he_method2, he_method1] - - -def test_hookspec(pm): - class HookSpec: - @hookspec() - def he_myhook1(arg1): - pass - - @hookspec(firstresult=True) - def he_myhook2(arg1): - pass - - @hookspec(firstresult=False) - def he_myhook3(arg1): - pass - - pm.add_hookspecs(HookSpec) - assert not pm.hook.he_myhook1.spec.opts["firstresult"] - assert pm.hook.he_myhook2.spec.opts["firstresult"] - assert not pm.hook.he_myhook3.spec.opts["firstresult"] - - -@pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"]) -@pytest.mark.parametrize("val", [True, False]) -def test_hookimpl(name, val): - @hookimpl(**{name: val}) - def he_myhook1(arg1): - pass - - if val: - assert he_myhook1.example_impl.get(name) - else: - assert not hasattr(he_myhook1, name) - - -def test_hookrelay_registry(pm): - """Verify hook caller instances are registered by name onto the relay - and can be likewise unregistered.""" - - class Api: - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - hook = pm.hook - assert hasattr(hook, "hello") - assert repr(hook.hello).find("hello") != -1 - - class Plugin: - @hookimpl - def hello(self, arg): - return arg + 1 - - plugin = Plugin() - pm.register(plugin) - out = hook.hello(arg=3) - assert out == [4] - assert not hasattr(hook, "world") - pm.unregister(plugin) - assert hook.hello(arg=3) == [] - - -def test_hookrelay_registration_by_specname(pm): - """Verify hook caller instances may also be registered by specifying a - specname option to the hookimpl""" - - class Api: - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - hook = pm.hook - assert hasattr(hook, "hello") - assert len(pm.hook.hello.get_hookimpls()) == 0 - - class Plugin: - @hookimpl(specname="hello") - def foo(self, arg): - return arg + 1 - - plugin = Plugin() - pm.register(plugin) - out = hook.hello(arg=3) - assert out == [4] - - -def test_hookrelay_registration_by_specname_raises(pm): - """Verify using specname still raises the types of errors during registration as it - would have without using specname.""" - - class Api: - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - # make sure a bad signature still raises an error when using specname - class Plugin: - @hookimpl(specname="hello") - def foo(self, arg, too, many, args): - return arg + 1 - - with pytest.raises(PluginValidationError): - pm.register(Plugin()) - - # make sure check_pending still fails if specname doesn't have a - # corresponding spec. EVEN if the function name matches one. - class Plugin2: - @hookimpl(specname="bar") - def hello(self, arg): - return arg + 1 - - pm.register(Plugin2()) - with pytest.raises(PluginValidationError): - pm.check_pending() +import pytest + +from pluggy import HookimplMarker, HookspecMarker, PluginValidationError +from pluggy._hooks import HookImpl + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +@pytest.fixture +def hc(pm): + class Hooks: + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + return pm.hook.he_method1 + + +@pytest.fixture +def addmeth(hc): + def addmeth(tryfirst=False, trylast=False, hookwrapper=False): + def wrap(func): + hookimpl(tryfirst=tryfirst, trylast=trylast, hookwrapper=hookwrapper)(func) + hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl)) + return func + + return wrap + + return addmeth + + +def funcs(hookmethods): + return [hookmethod.function for hookmethod in hookmethods] + + +def test_adding_nonwrappers(hc, addmeth): + @addmeth() + def he_method1(): + pass + + @addmeth() + def he_method2(): + pass + + @addmeth() + def he_method3(): + pass + + assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3] + + +def test_adding_nonwrappers_trylast(hc, addmeth): + @addmeth() + def he_method1_middle(): + pass + + @addmeth(trylast=True) + def he_method1(): + pass + + @addmeth() + def he_method1_b(): + pass + + assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] + + +def test_adding_nonwrappers_trylast3(hc, addmeth): + @addmeth() + def he_method1_a(): + pass + + @addmeth(trylast=True) + def he_method1_b(): + pass + + @addmeth() + def he_method1_c(): + pass + + @addmeth(trylast=True) + def he_method1_d(): + pass + + assert funcs(hc._nonwrappers) == [ + he_method1_d, + he_method1_b, + he_method1_a, + he_method1_c, + ] + + +def test_adding_nonwrappers_trylast2(hc, addmeth): + @addmeth() + def he_method1_middle(): + pass + + @addmeth() + def he_method1_b(): + pass + + @addmeth(trylast=True) + def he_method1(): + pass + + assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b] + + +def test_adding_nonwrappers_tryfirst(hc, addmeth): + @addmeth(tryfirst=True) + def he_method1(): + pass + + @addmeth() + def he_method1_middle(): + pass + + @addmeth() + def he_method1_b(): + pass + + assert funcs(hc._nonwrappers) == [he_method1_middle, he_method1_b, he_method1] + + +def test_adding_wrappers_ordering(hc, addmeth): + @addmeth(hookwrapper=True) + def he_method1(): + pass + + @addmeth() + def he_method1_middle(): + pass + + @addmeth(hookwrapper=True) + def he_method3(): + pass + + assert funcs(hc._nonwrappers) == [he_method1_middle] + assert funcs(hc._wrappers) == [he_method1, he_method3] + + +def test_adding_wrappers_ordering_tryfirst(hc, addmeth): + @addmeth(hookwrapper=True, tryfirst=True) + def he_method1(): + pass + + @addmeth(hookwrapper=True) + def he_method2(): + pass + + assert hc._nonwrappers == [] + assert funcs(hc._wrappers) == [he_method2, he_method1] + + +def test_hookspec(pm): + class HookSpec: + @hookspec() + def he_myhook1(arg1): + pass + + @hookspec(firstresult=True) + def he_myhook2(arg1): + pass + + @hookspec(firstresult=False) + def he_myhook3(arg1): + pass + + pm.add_hookspecs(HookSpec) + assert not pm.hook.he_myhook1.spec.opts["firstresult"] + assert pm.hook.he_myhook2.spec.opts["firstresult"] + assert not pm.hook.he_myhook3.spec.opts["firstresult"] + + +@pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"]) +@pytest.mark.parametrize("val", [True, False]) +def test_hookimpl(name, val): + @hookimpl(**{name: val}) + def he_myhook1(arg1): + pass + + if val: + assert he_myhook1.example_impl.get(name) + else: + assert not hasattr(he_myhook1, name) + + +def test_hookrelay_registry(pm): + """Verify hook caller instances are registered by name onto the relay + and can be likewise unregistered.""" + + class Api: + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + hook = pm.hook + assert hasattr(hook, "hello") + assert repr(hook.hello).find("hello") != -1 + + class Plugin: + @hookimpl + def hello(self, arg): + return arg + 1 + + plugin = Plugin() + pm.register(plugin) + out = hook.hello(arg=3) + assert out == [4] + assert not hasattr(hook, "world") + pm.unregister(plugin) + assert hook.hello(arg=3) == [] + + +def test_hookrelay_registration_by_specname(pm): + """Verify hook caller instances may also be registered by specifying a + specname option to the hookimpl""" + + class Api: + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + hook = pm.hook + assert hasattr(hook, "hello") + assert len(pm.hook.hello.get_hookimpls()) == 0 + + class Plugin: + @hookimpl(specname="hello") + def foo(self, arg): + return arg + 1 + + plugin = Plugin() + pm.register(plugin) + out = hook.hello(arg=3) + assert out == [4] + + +def test_hookrelay_registration_by_specname_raises(pm): + """Verify using specname still raises the types of errors during registration as it + would have without using specname.""" + + class Api: + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + # make sure a bad signature still raises an error when using specname + class Plugin: + @hookimpl(specname="hello") + def foo(self, arg, too, many, args): + return arg + 1 + + with pytest.raises(PluginValidationError): + pm.register(Plugin()) + + # make sure check_pending still fails if specname doesn't have a + # corresponding spec. EVEN if the function name matches one. + class Plugin2: + @hookimpl(specname="bar") + def hello(self, arg): + return arg + 1 + + pm.register(Plugin2()) + with pytest.raises(PluginValidationError): + pm.check_pending() diff --git a/contrib/python/pluggy/py3/tests/test_invocations.py b/contrib/python/pluggy/py3/tests/test_invocations.py index 323b9b21e8..e254849adc 100644 --- a/contrib/python/pluggy/py3/tests/test_invocations.py +++ b/contrib/python/pluggy/py3/tests/test_invocations.py @@ -1,215 +1,215 @@ -import pytest -from pluggy import PluginValidationError, HookimplMarker, HookspecMarker - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_argmismatch(pm): - class Api: - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin: - @hookimpl - def hello(self, argwrong): - pass - - with pytest.raises(PluginValidationError) as exc: - pm.register(Plugin()) - - assert "argwrong" in str(exc.value) - - -def test_only_kwargs(pm): - class Api: - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - with pytest.raises(TypeError) as exc: - pm.hook.hello(3) - - comprehensible = "hook calling supports only keyword arguments" - assert comprehensible in str(exc.value) - - -def test_opt_in_args(pm): - """Verfiy that two hookimpls with mutex args can serve - under the same spec. - """ - - class Api: - @hookspec - def hello(self, arg1, arg2, common_arg): - "api hook 1" - - class Plugin1: - @hookimpl - def hello(self, arg1, common_arg): - return arg1 + common_arg - - class Plugin2: - @hookimpl - def hello(self, arg2, common_arg): - return arg2 + common_arg - - pm.add_hookspecs(Api) - pm.register(Plugin1()) - pm.register(Plugin2()) - - results = pm.hook.hello(arg1=1, arg2=2, common_arg=0) - assert results == [2, 1] - - -def test_call_order(pm): - class Api: - @hookspec - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1: - @hookimpl - def hello(self, arg): - return 1 - - class Plugin2: - @hookimpl - def hello(self, arg): - return 2 - - class Plugin3: - @hookimpl - def hello(self, arg): - return 3 - - class Plugin4: - @hookimpl(hookwrapper=True) - def hello(self, arg): - assert arg == 0 - outcome = yield - assert outcome.get_result() == [3, 2, 1] - - pm.register(Plugin1()) - pm.register(Plugin2()) - pm.register(Plugin3()) - pm.register(Plugin4()) # hookwrapper should get same list result - res = pm.hook.hello(arg=0) - assert res == [3, 2, 1] - - -def test_firstresult_definition(pm): - class Api: - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1: - @hookimpl - def hello(self, arg): - return arg + 1 - - class Plugin2: - @hookimpl - def hello(self, arg): - return arg - 1 - - class Plugin3: - @hookimpl - def hello(self, arg): - return None - - class Plugin4: - @hookimpl(hookwrapper=True) - def hello(self, arg): - assert arg == 3 - outcome = yield - assert outcome.get_result() == 2 - - pm.register(Plugin1()) # discarded - not the last registered plugin - pm.register(Plugin2()) # used as result - pm.register(Plugin3()) # None result is ignored - pm.register(Plugin4()) # hookwrapper should get same non-list result - res = pm.hook.hello(arg=3) - assert res == 2 - - -def test_firstresult_force_result(pm): - """Verify forcing a result in a wrapper.""" - - class Api: - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1: - @hookimpl - def hello(self, arg): - return arg + 1 - - class Plugin2: - @hookimpl(hookwrapper=True) - def hello(self, arg): - assert arg == 3 - outcome = yield - assert outcome.get_result() == 4 - outcome.force_result(0) - - class Plugin3: - @hookimpl - def hello(self, arg): - return None - - pm.register(Plugin1()) - pm.register(Plugin2()) # wrapper - pm.register(Plugin3()) # ignored since returns None - res = pm.hook.hello(arg=3) - assert res == 0 # this result is forced and not a list - - -def test_firstresult_returns_none(pm): - """If None results are returned by underlying implementations ensure - the multi-call loop returns a None value. - """ - - class Api: - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - - class Plugin1: - @hookimpl - def hello(self, arg): - return None - - pm.register(Plugin1()) - res = pm.hook.hello(arg=3) - assert res is None - - -def test_firstresult_no_plugin(pm): - """If no implementations/plugins have been registered for a firstresult - hook the multi-call loop should return a None value. - """ - - class Api: - @hookspec(firstresult=True) - def hello(self, arg): - "api hook 1" - - pm.add_hookspecs(Api) - res = pm.hook.hello(arg=3) - assert res is None +import pytest +from pluggy import PluginValidationError, HookimplMarker, HookspecMarker + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_argmismatch(pm): + class Api: + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin: + @hookimpl + def hello(self, argwrong): + pass + + with pytest.raises(PluginValidationError) as exc: + pm.register(Plugin()) + + assert "argwrong" in str(exc.value) + + +def test_only_kwargs(pm): + class Api: + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + with pytest.raises(TypeError) as exc: + pm.hook.hello(3) + + comprehensible = "hook calling supports only keyword arguments" + assert comprehensible in str(exc.value) + + +def test_opt_in_args(pm): + """Verfiy that two hookimpls with mutex args can serve + under the same spec. + """ + + class Api: + @hookspec + def hello(self, arg1, arg2, common_arg): + "api hook 1" + + class Plugin1: + @hookimpl + def hello(self, arg1, common_arg): + return arg1 + common_arg + + class Plugin2: + @hookimpl + def hello(self, arg2, common_arg): + return arg2 + common_arg + + pm.add_hookspecs(Api) + pm.register(Plugin1()) + pm.register(Plugin2()) + + results = pm.hook.hello(arg1=1, arg2=2, common_arg=0) + assert results == [2, 1] + + +def test_call_order(pm): + class Api: + @hookspec + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def hello(self, arg): + return 1 + + class Plugin2: + @hookimpl + def hello(self, arg): + return 2 + + class Plugin3: + @hookimpl + def hello(self, arg): + return 3 + + class Plugin4: + @hookimpl(hookwrapper=True) + def hello(self, arg): + assert arg == 0 + outcome = yield + assert outcome.get_result() == [3, 2, 1] + + pm.register(Plugin1()) + pm.register(Plugin2()) + pm.register(Plugin3()) + pm.register(Plugin4()) # hookwrapper should get same list result + res = pm.hook.hello(arg=0) + assert res == [3, 2, 1] + + +def test_firstresult_definition(pm): + class Api: + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def hello(self, arg): + return arg + 1 + + class Plugin2: + @hookimpl + def hello(self, arg): + return arg - 1 + + class Plugin3: + @hookimpl + def hello(self, arg): + return None + + class Plugin4: + @hookimpl(hookwrapper=True) + def hello(self, arg): + assert arg == 3 + outcome = yield + assert outcome.get_result() == 2 + + pm.register(Plugin1()) # discarded - not the last registered plugin + pm.register(Plugin2()) # used as result + pm.register(Plugin3()) # None result is ignored + pm.register(Plugin4()) # hookwrapper should get same non-list result + res = pm.hook.hello(arg=3) + assert res == 2 + + +def test_firstresult_force_result(pm): + """Verify forcing a result in a wrapper.""" + + class Api: + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def hello(self, arg): + return arg + 1 + + class Plugin2: + @hookimpl(hookwrapper=True) + def hello(self, arg): + assert arg == 3 + outcome = yield + assert outcome.get_result() == 4 + outcome.force_result(0) + + class Plugin3: + @hookimpl + def hello(self, arg): + return None + + pm.register(Plugin1()) + pm.register(Plugin2()) # wrapper + pm.register(Plugin3()) # ignored since returns None + res = pm.hook.hello(arg=3) + assert res == 0 # this result is forced and not a list + + +def test_firstresult_returns_none(pm): + """If None results are returned by underlying implementations ensure + the multi-call loop returns a None value. + """ + + class Api: + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + + class Plugin1: + @hookimpl + def hello(self, arg): + return None + + pm.register(Plugin1()) + res = pm.hook.hello(arg=3) + assert res is None + + +def test_firstresult_no_plugin(pm): + """If no implementations/plugins have been registered for a firstresult + hook the multi-call loop should return a None value. + """ + + class Api: + @hookspec(firstresult=True) + def hello(self, arg): + "api hook 1" + + pm.add_hookspecs(Api) + res = pm.hook.hello(arg=3) + assert res is None diff --git a/contrib/python/pluggy/py3/tests/test_multicall.py b/contrib/python/pluggy/py3/tests/test_multicall.py index 8ffb452f69..ca65f32f7a 100644 --- a/contrib/python/pluggy/py3/tests/test_multicall.py +++ b/contrib/python/pluggy/py3/tests/test_multicall.py @@ -1,147 +1,147 @@ -import pytest -from pluggy import HookCallError, HookspecMarker, HookimplMarker -from pluggy._hooks import HookImpl -from pluggy._callers import _multicall - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def MC(methods, kwargs, firstresult=False): - caller = _multicall - hookfuncs = [] - for method in methods: - f = HookImpl(None, "<temp>", method, method.example_impl) - hookfuncs.append(f) - return caller("foo", hookfuncs, kwargs, firstresult) - - -def test_keyword_args(): - @hookimpl - def f(x): - return x + 1 - - class A: - @hookimpl - def f(self, x, y): - return x + y - - reslist = MC([f, A().f], dict(x=23, y=24)) - assert reslist == [24 + 23, 24] - - -def test_keyword_args_with_defaultargs(): - @hookimpl - def f(x, z=1): - return x + z - - reslist = MC([f], dict(x=23, y=24)) - assert reslist == [24] - - -def test_tags_call_error(): - @hookimpl - def f(x): - return x - - with pytest.raises(HookCallError): - MC([f], {}) - - -def test_call_none_is_no_result(): - @hookimpl - def m1(): - return 1 - - @hookimpl - def m2(): - return None - - res = MC([m1, m2], {}, firstresult=True) - assert res == 1 - res = MC([m1, m2], {}, {}) - assert res == [1] - - -def test_hookwrapper(): - out = [] - - @hookimpl(hookwrapper=True) - def m1(): - out.append("m1 init") - yield None - out.append("m1 finish") - - @hookimpl - def m2(): - out.append("m2") - return 2 - - res = MC([m2, m1], {}) - assert res == [2] - assert out == ["m1 init", "m2", "m1 finish"] - out[:] = [] - res = MC([m2, m1], {}, firstresult=True) - assert res == 2 - assert out == ["m1 init", "m2", "m1 finish"] - - -def test_hookwrapper_order(): - out = [] - - @hookimpl(hookwrapper=True) - def m1(): - out.append("m1 init") - yield 1 - out.append("m1 finish") - - @hookimpl(hookwrapper=True) - def m2(): - out.append("m2 init") - yield 2 - out.append("m2 finish") - - res = MC([m2, m1], {}) - assert res == [] - assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"] - - -def test_hookwrapper_not_yield(): - @hookimpl(hookwrapper=True) - def m1(): - pass - - with pytest.raises(TypeError): - MC([m1], {}) - - -def test_hookwrapper_too_many_yield(): - @hookimpl(hookwrapper=True) - def m1(): - yield 1 - yield 2 - - with pytest.raises(RuntimeError) as ex: - MC([m1], {}) - assert "m1" in str(ex.value) - assert (__file__ + ":") in str(ex.value) - - -@pytest.mark.parametrize("exc", [ValueError, SystemExit]) -def test_hookwrapper_exception(exc): - out = [] - - @hookimpl(hookwrapper=True) - def m1(): - out.append("m1 init") - yield None - out.append("m1 finish") - - @hookimpl - def m2(): - raise exc - - with pytest.raises(exc): - MC([m2, m1], {}) - assert out == ["m1 init", "m1 finish"] +import pytest +from pluggy import HookCallError, HookspecMarker, HookimplMarker +from pluggy._hooks import HookImpl +from pluggy._callers import _multicall + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def MC(methods, kwargs, firstresult=False): + caller = _multicall + hookfuncs = [] + for method in methods: + f = HookImpl(None, "<temp>", method, method.example_impl) + hookfuncs.append(f) + return caller("foo", hookfuncs, kwargs, firstresult) + + +def test_keyword_args(): + @hookimpl + def f(x): + return x + 1 + + class A: + @hookimpl + def f(self, x, y): + return x + y + + reslist = MC([f, A().f], dict(x=23, y=24)) + assert reslist == [24 + 23, 24] + + +def test_keyword_args_with_defaultargs(): + @hookimpl + def f(x, z=1): + return x + z + + reslist = MC([f], dict(x=23, y=24)) + assert reslist == [24] + + +def test_tags_call_error(): + @hookimpl + def f(x): + return x + + with pytest.raises(HookCallError): + MC([f], {}) + + +def test_call_none_is_no_result(): + @hookimpl + def m1(): + return 1 + + @hookimpl + def m2(): + return None + + res = MC([m1, m2], {}, firstresult=True) + assert res == 1 + res = MC([m1, m2], {}, {}) + assert res == [1] + + +def test_hookwrapper(): + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + yield None + out.append("m1 finish") + + @hookimpl + def m2(): + out.append("m2") + return 2 + + res = MC([m2, m1], {}) + assert res == [2] + assert out == ["m1 init", "m2", "m1 finish"] + out[:] = [] + res = MC([m2, m1], {}, firstresult=True) + assert res == 2 + assert out == ["m1 init", "m2", "m1 finish"] + + +def test_hookwrapper_order(): + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + yield 1 + out.append("m1 finish") + + @hookimpl(hookwrapper=True) + def m2(): + out.append("m2 init") + yield 2 + out.append("m2 finish") + + res = MC([m2, m1], {}) + assert res == [] + assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"] + + +def test_hookwrapper_not_yield(): + @hookimpl(hookwrapper=True) + def m1(): + pass + + with pytest.raises(TypeError): + MC([m1], {}) + + +def test_hookwrapper_too_many_yield(): + @hookimpl(hookwrapper=True) + def m1(): + yield 1 + yield 2 + + with pytest.raises(RuntimeError) as ex: + MC([m1], {}) + assert "m1" in str(ex.value) + assert (__file__ + ":") in str(ex.value) + + +@pytest.mark.parametrize("exc", [ValueError, SystemExit]) +def test_hookwrapper_exception(exc): + out = [] + + @hookimpl(hookwrapper=True) + def m1(): + out.append("m1 init") + yield None + out.append("m1 finish") + + @hookimpl + def m2(): + raise exc + + with pytest.raises(exc): + MC([m2, m1], {}) + assert out == ["m1 init", "m1 finish"] diff --git a/contrib/python/pluggy/py3/tests/test_pluginmanager.py b/contrib/python/pluggy/py3/tests/test_pluginmanager.py index 304a007a58..56a1e0756c 100644 --- a/contrib/python/pluggy/py3/tests/test_pluginmanager.py +++ b/contrib/python/pluggy/py3/tests/test_pluginmanager.py @@ -1,544 +1,544 @@ -""" -``PluginManager`` unit and public API testing. -""" -import pytest - -from pluggy import ( - PluginValidationError, - HookCallError, - HookimplMarker, - HookspecMarker, -) -from pluggy._manager import importlib_metadata - - -hookspec = HookspecMarker("example") -hookimpl = HookimplMarker("example") - - -def test_plugin_double_register(pm): - """Registering the same plugin more then once isn't allowed""" - pm.register(42, name="abc") - with pytest.raises(ValueError): - pm.register(42, name="abc") - with pytest.raises(ValueError): - pm.register(42, name="def") - - -def test_pm(pm): - """Basic registration with objects""" - - class A: - pass - - a1, a2 = A(), A() - pm.register(a1) - assert pm.is_registered(a1) - pm.register(a2, "hello") - assert pm.is_registered(a2) - out = pm.get_plugins() - assert a1 in out - assert a2 in out - assert pm.get_plugin("hello") == a2 - assert pm.unregister(a1) == a1 - assert not pm.is_registered(a1) - - out = pm.list_name_plugin() - assert len(out) == 1 - assert out == [("hello", a2)] - - -def test_has_plugin(pm): - class A: - pass - - a1 = A() - pm.register(a1, "hello") - assert pm.is_registered(a1) - assert pm.has_plugin("hello") - - -def test_register_dynamic_attr(he_pm): - class A: - def __getattr__(self, name): - if name[0] != "_": - return 42 - raise AttributeError() - - a = A() - he_pm.register(a) - assert not he_pm.get_hookcallers(a) - - -def test_pm_name(pm): - class A: - pass - - a1 = A() - name = pm.register(a1, name="hello") - assert name == "hello" - pm.unregister(a1) - assert pm.get_plugin(a1) is None - assert not pm.is_registered(a1) - assert not pm.get_plugins() - name2 = pm.register(a1, name="hello") - assert name2 == name - pm.unregister(name="hello") - assert pm.get_plugin(a1) is None - assert not pm.is_registered(a1) - assert not pm.get_plugins() - - -def test_set_blocked(pm): - class A: - pass - - a1 = A() - name = pm.register(a1) - assert pm.is_registered(a1) - assert not pm.is_blocked(name) - pm.set_blocked(name) - assert pm.is_blocked(name) - assert not pm.is_registered(a1) - - pm.set_blocked("somename") - assert pm.is_blocked("somename") - assert not pm.register(A(), "somename") - pm.unregister(name="somename") - assert pm.is_blocked("somename") - - -def test_register_mismatch_method(he_pm): - class hello: - @hookimpl - def he_method_notexists(self): - pass - - plugin = hello() - - he_pm.register(plugin) - with pytest.raises(PluginValidationError) as excinfo: - he_pm.check_pending() - assert excinfo.value.plugin is plugin - - -def test_register_mismatch_arg(he_pm): - class hello: - @hookimpl - def he_method1(self, qlwkje): - pass - - plugin = hello() - - with pytest.raises(PluginValidationError) as excinfo: - he_pm.register(plugin) - assert excinfo.value.plugin is plugin - - -def test_register_hookwrapper_not_a_generator_function(he_pm): - class hello: - @hookimpl(hookwrapper=True) - def he_method1(self): - pass # pragma: no cover - - plugin = hello() - - with pytest.raises(PluginValidationError, match="generator function") as excinfo: - he_pm.register(plugin) - assert excinfo.value.plugin is plugin - - -def test_register(pm): - class MyPlugin: - pass - - my = MyPlugin() - pm.register(my) - assert my in pm.get_plugins() - my2 = MyPlugin() - pm.register(my2) - assert {my, my2}.issubset(pm.get_plugins()) - - assert pm.is_registered(my) - assert pm.is_registered(my2) - pm.unregister(my) - assert not pm.is_registered(my) - assert my not in pm.get_plugins() - - -def test_register_unknown_hooks(pm): - class Plugin1: - @hookimpl - def he_method1(self, arg): - return arg + 1 - - pname = pm.register(Plugin1()) - - class Hooks: - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - # assert not pm._unverified_hooks - assert pm.hook.he_method1(arg=1) == [2] - assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1 - - -def test_register_historic(pm): - class Hooks: - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) - out = [] - - class Plugin: - @hookimpl - def he_method1(self, arg): - out.append(arg) - - pm.register(Plugin()) - assert out == [1] - - class Plugin2: - @hookimpl - def he_method1(self, arg): - out.append(arg * 10) - - pm.register(Plugin2()) - assert out == [1, 10] - pm.hook.he_method1.call_historic(kwargs=dict(arg=12)) - assert out == [1, 10, 120, 12] - - -@pytest.mark.parametrize("result_callback", [True, False]) -def test_with_result_memorized(pm, result_callback): - """Verify that ``_HookCaller._maybe_apply_history()` - correctly applies the ``result_callback`` function, when provided, - to the result from calling each newly registered hook. - """ - out = [] - if result_callback: - - def callback(res): - out.append(res) - - else: - callback = None - - class Hooks: - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - class Plugin1: - @hookimpl - def he_method1(self, arg): - return arg * 10 - - pm.register(Plugin1()) - - he_method1 = pm.hook.he_method1 - he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1)) - - class Plugin2: - @hookimpl - def he_method1(self, arg): - return arg * 10 - - pm.register(Plugin2()) - if result_callback: - assert out == [10, 10] - else: - assert out == [] - - -def test_with_callbacks_immediately_executed(pm): - class Hooks: - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - class Plugin1: - @hookimpl - def he_method1(self, arg): - return arg * 10 - - class Plugin2: - @hookimpl - def he_method1(self, arg): - return arg * 20 - - class Plugin3: - @hookimpl - def he_method1(self, arg): - return arg * 30 - - out = [] - pm.register(Plugin1()) - pm.register(Plugin2()) - - he_method1 = pm.hook.he_method1 - he_method1.call_historic(lambda res: out.append(res), dict(arg=1)) - assert out == [20, 10] - pm.register(Plugin3()) - assert out == [20, 10, 30] - - -def test_register_historic_incompat_hookwrapper(pm): - class Hooks: - @hookspec(historic=True) - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - out = [] - - class Plugin: - @hookimpl(hookwrapper=True) - def he_method1(self, arg): - out.append(arg) - - with pytest.raises(PluginValidationError): - pm.register(Plugin()) - - -def test_call_extra(pm): - class Hooks: - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - def he_method1(arg): - return arg * 10 - - out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) - assert out == [10] - - -def test_call_with_too_few_args(pm): - class Hooks: - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - class Plugin1: - @hookimpl - def he_method1(self, arg): - 0 / 0 - - pm.register(Plugin1()) - with pytest.raises(HookCallError): - with pytest.warns(UserWarning): - pm.hook.he_method1() - - -def test_subset_hook_caller(pm): - class Hooks: - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - - out = [] - - class Plugin1: - @hookimpl - def he_method1(self, arg): - out.append(arg) - - class Plugin2: - @hookimpl - def he_method1(self, arg): - out.append(arg * 10) - - class PluginNo: - pass - - plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() - pm.register(plugin1) - pm.register(plugin2) - pm.register(plugin3) - pm.hook.he_method1(arg=1) - assert out == [10, 1] - out[:] = [] - - hc = pm.subset_hook_caller("he_method1", [plugin1]) - hc(arg=2) - assert out == [20] - out[:] = [] - - hc = pm.subset_hook_caller("he_method1", [plugin2]) - hc(arg=2) - assert out == [2] - out[:] = [] - - pm.unregister(plugin1) - hc(arg=2) - assert out == [] - out[:] = [] - - pm.hook.he_method1(arg=1) - assert out == [10] - - -def test_get_hookimpls(pm): - class Hooks: - @hookspec - def he_method1(self, arg): - pass - - pm.add_hookspecs(Hooks) - assert pm.hook.he_method1.get_hookimpls() == [] - - class Plugin1: - @hookimpl - def he_method1(self, arg): - pass - - class Plugin2: - @hookimpl - def he_method1(self, arg): - pass - - class PluginNo: - pass - - plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() - pm.register(plugin1) - pm.register(plugin2) - pm.register(plugin3) - - hookimpls = pm.hook.he_method1.get_hookimpls() - hook_plugins = [item.plugin for item in hookimpls] - assert hook_plugins == [plugin1, plugin2] - - -def test_add_hookspecs_nohooks(pm): - with pytest.raises(ValueError): - pm.add_hookspecs(10) - - -def test_load_setuptools_instantiation(monkeypatch, pm): - class EntryPoint: - name = "myname" - group = "hello" - value = "myname:foo" - - def load(self): - class PseudoPlugin: - x = 42 - - return PseudoPlugin() - - class Distribution: - entry_points = (EntryPoint(),) - - dist = Distribution() - - def my_distributions(): - return (dist,) - - monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) - num = pm.load_setuptools_entrypoints("hello") - assert num == 1 - plugin = pm.get_plugin("myname") - assert plugin.x == 42 - ret = pm.list_plugin_distinfo() - # poor man's `assert ret == [(plugin, mock.ANY)]` - assert len(ret) == 1 - assert len(ret[0]) == 2 - assert ret[0][0] == plugin - assert ret[0][1]._dist == dist - num = pm.load_setuptools_entrypoints("hello") - assert num == 0 # no plugin loaded by this call - - -def test_add_tracefuncs(he_pm): - out = [] - - class api1: - @hookimpl - def he_method1(self): - out.append("he_method1-api1") - - class api2: - @hookimpl - def he_method1(self): - out.append("he_method1-api2") - - he_pm.register(api1()) - he_pm.register(api2()) - - def before(hook_name, hook_impls, kwargs): - out.append((hook_name, list(hook_impls), kwargs)) - - def after(outcome, hook_name, hook_impls, kwargs): - out.append((outcome, hook_name, list(hook_impls), kwargs)) - - undo = he_pm.add_hookcall_monitoring(before, after) - - he_pm.hook.he_method1(arg=1) - assert len(out) == 4 - assert out[0][0] == "he_method1" - assert len(out[0][1]) == 2 - assert isinstance(out[0][2], dict) - assert out[1] == "he_method1-api2" - assert out[2] == "he_method1-api1" - assert len(out[3]) == 4 - assert out[3][1] == out[0][0] - - undo() - he_pm.hook.he_method1(arg=1) - assert len(out) == 4 + 2 - - -def test_hook_tracing(he_pm): - saveindent = [] - - class api1: - @hookimpl - def he_method1(self): - saveindent.append(he_pm.trace.root.indent) - - class api2: - @hookimpl - def he_method1(self): - saveindent.append(he_pm.trace.root.indent) - raise ValueError() - - he_pm.register(api1()) - out = [] - he_pm.trace.root.setwriter(out.append) - undo = he_pm.enable_tracing() - try: - indent = he_pm.trace.root.indent - he_pm.hook.he_method1(arg=1) - assert indent == he_pm.trace.root.indent - assert len(out) == 2 - assert "he_method1" in out[0] - assert "finish" in out[1] - - out[:] = [] - he_pm.register(api2()) - - with pytest.raises(ValueError): - he_pm.hook.he_method1(arg=1) - assert he_pm.trace.root.indent == indent - assert saveindent[0] > indent - finally: - undo() +""" +``PluginManager`` unit and public API testing. +""" +import pytest + +from pluggy import ( + PluginValidationError, + HookCallError, + HookimplMarker, + HookspecMarker, +) +from pluggy._manager import importlib_metadata + + +hookspec = HookspecMarker("example") +hookimpl = HookimplMarker("example") + + +def test_plugin_double_register(pm): + """Registering the same plugin more then once isn't allowed""" + pm.register(42, name="abc") + with pytest.raises(ValueError): + pm.register(42, name="abc") + with pytest.raises(ValueError): + pm.register(42, name="def") + + +def test_pm(pm): + """Basic registration with objects""" + + class A: + pass + + a1, a2 = A(), A() + pm.register(a1) + assert pm.is_registered(a1) + pm.register(a2, "hello") + assert pm.is_registered(a2) + out = pm.get_plugins() + assert a1 in out + assert a2 in out + assert pm.get_plugin("hello") == a2 + assert pm.unregister(a1) == a1 + assert not pm.is_registered(a1) + + out = pm.list_name_plugin() + assert len(out) == 1 + assert out == [("hello", a2)] + + +def test_has_plugin(pm): + class A: + pass + + a1 = A() + pm.register(a1, "hello") + assert pm.is_registered(a1) + assert pm.has_plugin("hello") + + +def test_register_dynamic_attr(he_pm): + class A: + def __getattr__(self, name): + if name[0] != "_": + return 42 + raise AttributeError() + + a = A() + he_pm.register(a) + assert not he_pm.get_hookcallers(a) + + +def test_pm_name(pm): + class A: + pass + + a1 = A() + name = pm.register(a1, name="hello") + assert name == "hello" + pm.unregister(a1) + assert pm.get_plugin(a1) is None + assert not pm.is_registered(a1) + assert not pm.get_plugins() + name2 = pm.register(a1, name="hello") + assert name2 == name + pm.unregister(name="hello") + assert pm.get_plugin(a1) is None + assert not pm.is_registered(a1) + assert not pm.get_plugins() + + +def test_set_blocked(pm): + class A: + pass + + a1 = A() + name = pm.register(a1) + assert pm.is_registered(a1) + assert not pm.is_blocked(name) + pm.set_blocked(name) + assert pm.is_blocked(name) + assert not pm.is_registered(a1) + + pm.set_blocked("somename") + assert pm.is_blocked("somename") + assert not pm.register(A(), "somename") + pm.unregister(name="somename") + assert pm.is_blocked("somename") + + +def test_register_mismatch_method(he_pm): + class hello: + @hookimpl + def he_method_notexists(self): + pass + + plugin = hello() + + he_pm.register(plugin) + with pytest.raises(PluginValidationError) as excinfo: + he_pm.check_pending() + assert excinfo.value.plugin is plugin + + +def test_register_mismatch_arg(he_pm): + class hello: + @hookimpl + def he_method1(self, qlwkje): + pass + + plugin = hello() + + with pytest.raises(PluginValidationError) as excinfo: + he_pm.register(plugin) + assert excinfo.value.plugin is plugin + + +def test_register_hookwrapper_not_a_generator_function(he_pm): + class hello: + @hookimpl(hookwrapper=True) + def he_method1(self): + pass # pragma: no cover + + plugin = hello() + + with pytest.raises(PluginValidationError, match="generator function") as excinfo: + he_pm.register(plugin) + assert excinfo.value.plugin is plugin + + +def test_register(pm): + class MyPlugin: + pass + + my = MyPlugin() + pm.register(my) + assert my in pm.get_plugins() + my2 = MyPlugin() + pm.register(my2) + assert {my, my2}.issubset(pm.get_plugins()) + + assert pm.is_registered(my) + assert pm.is_registered(my2) + pm.unregister(my) + assert not pm.is_registered(my) + assert my not in pm.get_plugins() + + +def test_register_unknown_hooks(pm): + class Plugin1: + @hookimpl + def he_method1(self, arg): + return arg + 1 + + pname = pm.register(Plugin1()) + + class Hooks: + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + # assert not pm._unverified_hooks + assert pm.hook.he_method1(arg=1) == [2] + assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1 + + +def test_register_historic(pm): + class Hooks: + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) + out = [] + + class Plugin: + @hookimpl + def he_method1(self, arg): + out.append(arg) + + pm.register(Plugin()) + assert out == [1] + + class Plugin2: + @hookimpl + def he_method1(self, arg): + out.append(arg * 10) + + pm.register(Plugin2()) + assert out == [1, 10] + pm.hook.he_method1.call_historic(kwargs=dict(arg=12)) + assert out == [1, 10, 120, 12] + + +@pytest.mark.parametrize("result_callback", [True, False]) +def test_with_result_memorized(pm, result_callback): + """Verify that ``_HookCaller._maybe_apply_history()` + correctly applies the ``result_callback`` function, when provided, + to the result from calling each newly registered hook. + """ + out = [] + if result_callback: + + def callback(res): + out.append(res) + + else: + callback = None + + class Hooks: + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin1: + @hookimpl + def he_method1(self, arg): + return arg * 10 + + pm.register(Plugin1()) + + he_method1 = pm.hook.he_method1 + he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1)) + + class Plugin2: + @hookimpl + def he_method1(self, arg): + return arg * 10 + + pm.register(Plugin2()) + if result_callback: + assert out == [10, 10] + else: + assert out == [] + + +def test_with_callbacks_immediately_executed(pm): + class Hooks: + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin1: + @hookimpl + def he_method1(self, arg): + return arg * 10 + + class Plugin2: + @hookimpl + def he_method1(self, arg): + return arg * 20 + + class Plugin3: + @hookimpl + def he_method1(self, arg): + return arg * 30 + + out = [] + pm.register(Plugin1()) + pm.register(Plugin2()) + + he_method1 = pm.hook.he_method1 + he_method1.call_historic(lambda res: out.append(res), dict(arg=1)) + assert out == [20, 10] + pm.register(Plugin3()) + assert out == [20, 10, 30] + + +def test_register_historic_incompat_hookwrapper(pm): + class Hooks: + @hookspec(historic=True) + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + out = [] + + class Plugin: + @hookimpl(hookwrapper=True) + def he_method1(self, arg): + out.append(arg) + + with pytest.raises(PluginValidationError): + pm.register(Plugin()) + + +def test_call_extra(pm): + class Hooks: + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + def he_method1(arg): + return arg * 10 + + out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) + assert out == [10] + + +def test_call_with_too_few_args(pm): + class Hooks: + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + class Plugin1: + @hookimpl + def he_method1(self, arg): + 0 / 0 + + pm.register(Plugin1()) + with pytest.raises(HookCallError): + with pytest.warns(UserWarning): + pm.hook.he_method1() + + +def test_subset_hook_caller(pm): + class Hooks: + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + + out = [] + + class Plugin1: + @hookimpl + def he_method1(self, arg): + out.append(arg) + + class Plugin2: + @hookimpl + def he_method1(self, arg): + out.append(arg * 10) + + class PluginNo: + pass + + plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() + pm.register(plugin1) + pm.register(plugin2) + pm.register(plugin3) + pm.hook.he_method1(arg=1) + assert out == [10, 1] + out[:] = [] + + hc = pm.subset_hook_caller("he_method1", [plugin1]) + hc(arg=2) + assert out == [20] + out[:] = [] + + hc = pm.subset_hook_caller("he_method1", [plugin2]) + hc(arg=2) + assert out == [2] + out[:] = [] + + pm.unregister(plugin1) + hc(arg=2) + assert out == [] + out[:] = [] + + pm.hook.he_method1(arg=1) + assert out == [10] + + +def test_get_hookimpls(pm): + class Hooks: + @hookspec + def he_method1(self, arg): + pass + + pm.add_hookspecs(Hooks) + assert pm.hook.he_method1.get_hookimpls() == [] + + class Plugin1: + @hookimpl + def he_method1(self, arg): + pass + + class Plugin2: + @hookimpl + def he_method1(self, arg): + pass + + class PluginNo: + pass + + plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() + pm.register(plugin1) + pm.register(plugin2) + pm.register(plugin3) + + hookimpls = pm.hook.he_method1.get_hookimpls() + hook_plugins = [item.plugin for item in hookimpls] + assert hook_plugins == [plugin1, plugin2] + + +def test_add_hookspecs_nohooks(pm): + with pytest.raises(ValueError): + pm.add_hookspecs(10) + + +def test_load_setuptools_instantiation(monkeypatch, pm): + class EntryPoint: + name = "myname" + group = "hello" + value = "myname:foo" + + def load(self): + class PseudoPlugin: + x = 42 + + return PseudoPlugin() + + class Distribution: + entry_points = (EntryPoint(),) + + dist = Distribution() + + def my_distributions(): + return (dist,) + + monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) + num = pm.load_setuptools_entrypoints("hello") + assert num == 1 + plugin = pm.get_plugin("myname") + assert plugin.x == 42 + ret = pm.list_plugin_distinfo() + # poor man's `assert ret == [(plugin, mock.ANY)]` + assert len(ret) == 1 + assert len(ret[0]) == 2 + assert ret[0][0] == plugin + assert ret[0][1]._dist == dist + num = pm.load_setuptools_entrypoints("hello") + assert num == 0 # no plugin loaded by this call + + +def test_add_tracefuncs(he_pm): + out = [] + + class api1: + @hookimpl + def he_method1(self): + out.append("he_method1-api1") + + class api2: + @hookimpl + def he_method1(self): + out.append("he_method1-api2") + + he_pm.register(api1()) + he_pm.register(api2()) + + def before(hook_name, hook_impls, kwargs): + out.append((hook_name, list(hook_impls), kwargs)) + + def after(outcome, hook_name, hook_impls, kwargs): + out.append((outcome, hook_name, list(hook_impls), kwargs)) + + undo = he_pm.add_hookcall_monitoring(before, after) + + he_pm.hook.he_method1(arg=1) + assert len(out) == 4 + assert out[0][0] == "he_method1" + assert len(out[0][1]) == 2 + assert isinstance(out[0][2], dict) + assert out[1] == "he_method1-api2" + assert out[2] == "he_method1-api1" + assert len(out[3]) == 4 + assert out[3][1] == out[0][0] + + undo() + he_pm.hook.he_method1(arg=1) + assert len(out) == 4 + 2 + + +def test_hook_tracing(he_pm): + saveindent = [] + + class api1: + @hookimpl + def he_method1(self): + saveindent.append(he_pm.trace.root.indent) + + class api2: + @hookimpl + def he_method1(self): + saveindent.append(he_pm.trace.root.indent) + raise ValueError() + + he_pm.register(api1()) + out = [] + he_pm.trace.root.setwriter(out.append) + undo = he_pm.enable_tracing() + try: + indent = he_pm.trace.root.indent + he_pm.hook.he_method1(arg=1) + assert indent == he_pm.trace.root.indent + assert len(out) == 2 + assert "he_method1" in out[0] + assert "finish" in out[1] + + out[:] = [] + he_pm.register(api2()) + + with pytest.raises(ValueError): + he_pm.hook.he_method1(arg=1) + assert he_pm.trace.root.indent == indent + assert saveindent[0] > indent + finally: + undo() diff --git a/contrib/python/pluggy/py3/tests/test_tracer.py b/contrib/python/pluggy/py3/tests/test_tracer.py index 992ec67914..7915f0ee75 100644 --- a/contrib/python/pluggy/py3/tests/test_tracer.py +++ b/contrib/python/pluggy/py3/tests/test_tracer.py @@ -1,78 +1,78 @@ -from pluggy._tracing import TagTracer - -import pytest - - -@pytest.fixture -def rootlogger(): - return TagTracer() - - -def test_simple(rootlogger): - log = rootlogger.get("pytest") - log("hello") - out = [] - rootlogger.setwriter(out.append) - log("world") - assert len(out) == 1 - assert out[0] == "world [pytest]\n" - sublog = log.get("collection") - sublog("hello") - assert out[1] == "hello [pytest:collection]\n" - - -def test_indent(rootlogger): - log = rootlogger.get("1") - out = [] - log.root.setwriter(lambda arg: out.append(arg)) - log("hello") - log.root.indent += 1 - log("line1") - log("line2") - log.root.indent += 1 - log("line3") - log("line4") - log.root.indent -= 1 - log("line5") - log.root.indent -= 1 - log("last") - assert len(out) == 7 - names = [x[: x.rfind(" [")] for x in out] - assert names == [ - "hello", - " line1", - " line2", - " line3", - " line4", - " line5", - "last", - ] - - -def test_readable_output_dictargs(rootlogger): - - out = rootlogger._format_message(["test"], [1]) - assert out == "1 [test]\n" - - out2 = rootlogger._format_message(["test"], ["test", {"a": 1}]) - assert out2 == "test [test]\n a: 1\n" - - -def test_setprocessor(rootlogger): - log = rootlogger.get("1") - log2 = log.get("2") - assert log2.tags == tuple("12") - out = [] - rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args)) - log("not seen") - log2("seen") - assert len(out) == 1 - tags, args = out[0] - assert "1" in tags - assert "2" in tags - assert args == ("seen",) - l2 = [] - rootlogger.setprocessor("1:2", lambda *args: l2.append(args)) - log2("seen") - tags, args = l2[0] - assert args == ("seen",) +from pluggy._tracing import TagTracer + +import pytest + + +@pytest.fixture +def rootlogger(): + return TagTracer() + + +def test_simple(rootlogger): + log = rootlogger.get("pytest") + log("hello") + out = [] + rootlogger.setwriter(out.append) + log("world") + assert len(out) == 1 + assert out[0] == "world [pytest]\n" + sublog = log.get("collection") + sublog("hello") + assert out[1] == "hello [pytest:collection]\n" + + +def test_indent(rootlogger): + log = rootlogger.get("1") + out = [] + log.root.setwriter(lambda arg: out.append(arg)) + log("hello") + log.root.indent += 1 + log("line1") + log("line2") + log.root.indent += 1 + log("line3") + log("line4") + log.root.indent -= 1 + log("line5") + log.root.indent -= 1 + log("last") + assert len(out) == 7 + names = [x[: x.rfind(" [")] for x in out] + assert names == [ + "hello", + " line1", + " line2", + " line3", + " line4", + " line5", + "last", + ] + + +def test_readable_output_dictargs(rootlogger): + + out = rootlogger._format_message(["test"], [1]) + assert out == "1 [test]\n" + + out2 = rootlogger._format_message(["test"], ["test", {"a": 1}]) + assert out2 == "test [test]\n a: 1\n" + + +def test_setprocessor(rootlogger): + log = rootlogger.get("1") + log2 = log.get("2") + assert log2.tags == tuple("12") + out = [] + rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args)) + log("not seen") + log2("seen") + assert len(out) == 1 + tags, args = out[0] + assert "1" in tags + assert "2" in tags + assert args == ("seen",) + l2 = [] + rootlogger.setprocessor("1:2", lambda *args: l2.append(args)) + log2("seen") + tags, args = l2[0] + assert args == ("seen",) diff --git a/contrib/python/pluggy/py3/tests/ya.make b/contrib/python/pluggy/py3/tests/ya.make index 565cbdd42b..b35f70ca36 100644 --- a/contrib/python/pluggy/py3/tests/ya.make +++ b/contrib/python/pluggy/py3/tests/ya.make @@ -1,22 +1,22 @@ -PY3TEST() - -OWNER(g:python-contrib) - -PEERDIR ( - contrib/python/pluggy -) - -TEST_SRCS( - conftest.py - test_details.py - test_helpers.py - test_hookcaller.py - test_invocations.py - test_multicall.py - test_pluginmanager.py - test_tracer.py -) - -NO_LINT() - -END() +PY3TEST() + +OWNER(g:python-contrib) + +PEERDIR ( + contrib/python/pluggy +) + +TEST_SRCS( + conftest.py + test_details.py + test_helpers.py + test_hookcaller.py + test_invocations.py + test_multicall.py + test_pluginmanager.py + test_tracer.py +) + +NO_LINT() + +END() diff --git a/contrib/python/pluggy/py3/ya.make b/contrib/python/pluggy/py3/ya.make index ba9c269fc8..99b618d436 100644 --- a/contrib/python/pluggy/py3/ya.make +++ b/contrib/python/pluggy/py3/ya.make @@ -1,32 +1,32 @@ -PY3_LIBRARY() - -OWNER(g:python-contrib) - -VERSION(1.0.0) - -LICENSE(MIT) - -NO_LINT() - -PY_SRCS( - TOP_LEVEL - pluggy/__init__.py - pluggy/_callers.py - pluggy/_hooks.py - pluggy/_manager.py - pluggy/_result.py - pluggy/_tracing.py - pluggy/_version.py -) - -RESOURCE_FILES( - PREFIX contrib/python/pluggy/py3/ - .dist-info/METADATA - .dist-info/top_level.txt -) - -END() - -RECURSE_FOR_TESTS( - tests -) +PY3_LIBRARY() + +OWNER(g:python-contrib) + +VERSION(1.0.0) + +LICENSE(MIT) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + pluggy/__init__.py + pluggy/_callers.py + pluggy/_hooks.py + pluggy/_manager.py + pluggy/_result.py + pluggy/_tracing.py + pluggy/_version.py +) + +RESOURCE_FILES( + PREFIX contrib/python/pluggy/py3/ + .dist-info/METADATA + .dist-info/top_level.txt +) + +END() + +RECURSE_FOR_TESTS( + tests +) diff --git a/contrib/python/pluggy/ya.make b/contrib/python/pluggy/ya.make index 43e4c7b2ab..54babff7fc 100644 --- a/contrib/python/pluggy/ya.make +++ b/contrib/python/pluggy/ya.make @@ -4,17 +4,17 @@ LICENSE(Service-Py23-Proxy) OWNER(g:python-contrib) -IF (PYTHON2) - PEERDIR(contrib/python/pluggy/py2) -ELSE() - PEERDIR(contrib/python/pluggy/py3) -ENDIF() - -NO_LINT() +IF (PYTHON2) + PEERDIR(contrib/python/pluggy/py2) +ELSE() + PEERDIR(contrib/python/pluggy/py3) +ENDIF() + +NO_LINT() END() - -RECURSE( - py2 - py3 -) + +RECURSE( + py2 + py3 +) |