Projects
openEuler:Mainline
python-pluggy
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 2
View file
_service:tar_scm:python-pluggy.spec
Changed
@@ -1,12 +1,12 @@ %global _empty_manifest_terminate_build 0 %bcond_with test Name: python-pluggy -Version: 0.13.1 -Release: 2 +Version: 1.0.0 +Release: 1 Summary: plugin and hook calling mechanisms for python License: MIT URL: https://github.com/pytest-dev/pluggy -Source0: https://files.pythonhosted.org/packages/f8/04/7a8542bed4b16a65c2714bf76cf5a0b026157da7f75e87cc88774aa10b14/pluggy-0.13.1.tar.gz +Source0: https://files.pythonhosted.org/packages/a1/16/db2d7de3474b6e37cbb9c008965ee63835bba517e22cdb8c35b5116b5ce1/pluggy-1.0.0.tar.gz BuildArch: noarch Requires: python3-importlib-metadata @@ -40,7 +40,7 @@ %{_description} %prep -%autosetup -n pluggy-0.13.1 +%autosetup -n pluggy-%{version} %build %py3_build @@ -85,6 +85,9 @@ %{_pkgdocdir} %changelog +* Wed Feb 15 2023 wubijie <wubijie@kylinos.cn> - 1.0.0-1 +- Update package to version 1.0.0 + * Sat Nov 27 2021 shixuantong <shixuantong@huawei> - 0.13.1-2 - disable %check
View file
_service
Changed
@@ -2,7 +2,7 @@ <service name="tar_scm"> <param name="scm">git</param> <param name="url">git@gitee.com:src-openeuler/python-pluggy.git</param> - <param name="revision">871a4ce302d60b617843c2b6d4368fcc52fbd3fe</param> + <param name="revision">master</param> <param name="exclude">*</param> <param name="extract">*</param> </service>
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/.travis.yml
Deleted
@@ -1,81 +0,0 @@ -dist: xenial -language: python - -stages: -- name: test - if: tag IS NOT present -- name: deploy - if: repo = pytest-dev/pluggy AND tag IS present - -jobs: - include: - - python: '3.6' - env: TOXENV=linting - cache: - directories: - - $HOME/.cache/pre-commit - - python: '3.6' - env: TOXENV=docs - - python: '2.7' - env: TOXENV=py27-coverage - - python: '3.4' - env: TOXENV=py34-coverage - - python: '3.5' - env: TOXENV=py35-coverage - - python: '3.6' - env: TOXENV=py36-coverage - - python: 'pypy2.7-6.0' - env: TOXENV=pypy-coverage - - python: 'pypy3.5-6.0' - env: TOXENV=pypy3-coverage - - python: '3.7' - env: TOXENV=py37-coverage - - python: '3.8-dev' - env: TOXENV=py38-coverage - - python: '3.6' - env: TOXENV=py36-pytestmaster-coverage - - python: '3.6' - env: TOXENV=py36-pytestfeatures-coverage - - python: '3.6' - env: TOXENV=benchmark - - python: '3.7' - env: TOXENV=py37-pytestmaster-coverage - - python: '3.7' - env: TOXENV=py37-pytestfeatures-coverage - - - stage: deploy - python: '3.6' - env: - install: pip install -U setuptools setuptools_scm - script: skip - deploy: - provider: pypi - user: nicoddemus - distributions: sdist bdist_wheel - skip_upload_docs: true - password: - secure: PDvQCKfXrF1V/tdwEOfeDEjDs6vJ9gKWo4yrMUmBx1JL5plHZaqfHLftlGMoHekaQTHcfyYYbxignFw5IGsn97/nFKKWDPNBfZA+3RJJmeJfz2NQunYkSnoqtBtfEtWHzFPdkCm0w/CN9C8IpRjhvLnFTUQzil6iMy6wZG276gU= - on: - tags: true - repo: pytest-dev/pluggy - -install: - - pip install -U pip - - pip install -U --force-reinstall setuptools tox - -script: - - tox - -after_script: - - | - if "${TOXENV%-coverage}" != "$TOXENV" ; then - bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml -n $TOXENV - fi - -notifications: - irc: - channels: - - "chat.freenode.net#pytest" - on_success: change - on_failure: change - skip_join: true
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/HOWTORELEASE.rst
Deleted
@@ -1,23 +0,0 @@ -Release Procedure ------------------ - -#. From a clean work tree, execute:: - - tox -e release -- VERSION - - This will create the branch ready to be pushed. - -#. Open a PR targeting ``master``. - -#. All tests must pass and the PR must be approved by at least another maintainer. - -#. Publish to PyPI by pushing a tag:: - - git tag X.Y.Z release-X.Y.Z - git push git@github.com:pytest-dev/pluggy.git X.Y.Z - - The tag will trigger a new build, which will deploy to PyPI. - -#. Make sure it is `available on PyPI <https://pypi.org/project/pluggy>`_. - -#. Merge the PR into ``master``, either manually or using GitHub's web interface.
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/appveyor.yml
Deleted
@@ -1,38 +0,0 @@ -environment: - matrix: - # note: please use "tox --listenvs" to populate the build matrix below - - TOXENV: "linting" - - TOXENV: "docs" - - TOXENV: "py27" - - TOXENV: "py34" - - TOXENV: "py35" - - TOXENV: "py36" - - TOXENV: "py37" - - TOXENV: "pypy" - - TOXENV: "py36-pytestmaster" - - TOXENV: "py36-pytestfeatures" - -install: - - echo Installed Pythons - - dir c:\Python* - - # install pypy using choco (redirect to a file and write to console in case - # choco install returns non-zero, because choco install python.pypy is too - # noisy) - # pypy is disabled until #1963 gets fixed - - choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1) - - set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy - - echo PyPy installed - - pypy --version - - - C:\Python35\python -m pip install -U pip - - C:\Python35\python -m pip install -U --force-reinstall setuptools tox - -build: false # Not a C# project, build stuff at the test step instead. - -test_script: - - C:\Python35\Scripts\tox - -# We don't deploy anything on tags with AppVeyor, we use Travis instead, so we -# might as well save resources -skip_tags: true
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy/callers.py
Deleted
@@ -1,208 +0,0 @@ -""" -Call loop machinery -""" -import sys -import warnings - -_py3 = sys.version_info > (3, 0) - - -if not _py3: - exec( - """ -def _reraise(cls, val, tb): - raise cls, val, tb -""" - ) - - -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(object): - def __init__(self, result, excinfo): - self._result = result - self._excinfo = excinfo - - @property - def excinfo(self): - return self._excinfo - - @property - def result(self): - """Get the result(s) for this hook call (DEPRECATED in favor of ``get_result()``).""" - msg = "Use get_result() which forces correct exception handling" - warnings.warn(DeprecationWarning(msg), stacklevel=2) - return self._result - - @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 - if _py3: - raise ex1.with_traceback(ex2) - _reraise(*ex) # noqa - - -def _wrapped_call(wrap_controller, func): - """ Wrap calling to a function with a generator which needs to yield - exactly once. The yield point will trigger calling the wrapped function - and return its ``_Result`` to the yield point. The generator then needs - to finish (raise StopIteration) in order for the wrapped call to complete. - """ - try: - next(wrap_controller) # first yield - except StopIteration: - _raise_wrapfail(wrap_controller, "did not yield") - call_outcome = _Result.from_call(func) - try: - wrap_controller.send(call_outcome) - _raise_wrapfail(wrap_controller, "has second yield") - except StopIteration: - pass - return call_outcome.get_result() - - -class _LegacyMultiCall(object): - """ execute a call into multiple python functions/methods. """ - - # XXX note that the __multicall__ argument is supported only - # for pytest compatibility reasons. It was never officially - # supported there and is explicitely deprecated since 2.8 - # so we can remove it soon, allowing to avoid the below recursion - # in execute() and simplify/speed up the execute loop. - - def __init__(self, hook_impls, kwargs, firstresult=False): - self.hook_impls = hook_impls - self.caller_kwargs = kwargs # come from _HookCaller.__call__() - self.caller_kwargs"__multicall__" = self - self.firstresult = firstresult - - def execute(self): - caller_kwargs = self.caller_kwargs - self.results = results = - firstresult = self.firstresult - - while self.hook_impls: - hook_impl = self.hook_impls.pop() - try: - args = caller_kwargsargname for argname in hook_impl.argnames - except KeyError: - for argname in hook_impl.argnames: - if argname not in caller_kwargs: - raise HookCallError( - "hook call must provide argument %r" % (argname,) - ) - if hook_impl.hookwrapper: - return _wrapped_call(hook_impl.function(*args), self.execute) - res = hook_impl.function(*args) - if res is not None: - if firstresult: - return res - results.append(res) - - if not firstresult: - return results - - def __repr__(self): - status = "%d meths" % (len(self.hook_impls),) - if hasattr(self, "results"): - status = ("%d results, " % len(self.results)) + status - return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs) - - -def _legacymulticall(hook_impls, caller_kwargs, firstresult=False): - return _LegacyMultiCall( - hook_impls, caller_kwargs, firstresult=firstresult - ).execute() - - -def _multicall(hook_impls, caller_kwargs, firstresult=False): - """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_kwargsargname for argname in hook_impl.argnames - except KeyError: - for argname in hook_impl.argnames: - if argname not in caller_kwargs: - raise HookCallError( - "hook call must provide argument %r" % (argname,) - ) - - 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(results0 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()
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy/hooks.py
Deleted
@@ -1,359 +0,0 @@ -""" -Internal hook annotation, representation and calling machinery. -""" -import inspect -import sys -import warnings -from .callers import _legacymulticall, _multicall - - -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. - """ - - 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(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. - """ - - def __init__(self, project_name): - self.project_name = project_name - - def __call__( - self, - function=None, - hookwrapper=False, - optionalhook=False, - tryfirst=False, - trylast=False, - ): - - """ 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). - - """ - - def setattr_hookimpl_opts(func): - setattr( - func, - self.project_name + "_impl", - dict( - hookwrapper=hookwrapper, - optionalhook=optionalhook, - tryfirst=tryfirst, - trylast=trylast, - ), - ) - 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) - - -if hasattr(inspect, "getfullargspec"): - - def _getargspec(func): - return inspect.getfullargspec(func) - - -else: - - def _getargspec(func): - return inspect.getargspec(func) - - -_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. - - In case of a class, its ``__init__`` method is considered. - For methods the ``self`` parameter is not included. - """ - cache = getattr(func, "__dict__", {}) - try: - return cache"_varnames" - except KeyError: - pass - - 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 = _getargspec(func) - except TypeError: - return (), () - - args, defaults = tuple(spec.args), spec.defaults - if defaults: - index = -len(defaults) - args, kwargs = args:index, tuple(argsindex:) - else: - 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") - if args: - if inspect.ismethod(func) or ( - "." in getattr(func, "__qualname__", ()) and args0 in implicit_names - ): - args = args1: - - try: - cache"_varnames" = args, kwargs - except TypeError: - pass - return args, kwargs - - -class _HookRelay(object): - """ hook holder object for performing 1:N hook calls where N is the number - of registered plugins. - - """ - - -class _HookCaller(object): - 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.argnames = None - self.kwargnames = None - self.multicall = _multicall - 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 hasattr(self, "_call_history") - - def _remove_plugin(self, plugin): - def remove(wrappers): - for i, method in enumerate(wrappers): - if method.plugin == plugin: - del wrappersi - return True - - if remove(self._wrappers) is None: - if remove(self._nonwrappers) is None: - raise ValueError("plugin %r not found" % (plugin,)) - - 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 methodsi.tryfirst: - i -= 1 - methods.insert(i + 1, hookimpl) - - if "__multicall__" in hookimpl.argnames: - warnings.warn( - "Support for __multicall__ is now deprecated and will be" - "removed in an upcoming release.", - DeprecationWarning, - ) - self.multicall = _legacymulticall - - def __repr__(self): - return "<_HookCaller %r>" % (self.name,) - - def __call__(self, *args, **kwargs): - if args: - raise TypeError("hook calling supports only keyword arguments") - assert not self.is_historic() - if self.spec and self.spec.argnames: - notincall = ( - set(self.spec.argnames) - set("__multicall__") - set(kwargs.keys()) - ) - if notincall: - warnings.warn( - "Argument(s) {} which are declared in the hookspec " - "can not be found in this hook call".format(tuple(notincall)), - stacklevel=2, - ) - return self._hookexec(self, self.get_hookimpls(), kwargs) - - def call_historic(self, result_callback=None, kwargs=None, proc=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. - - .. note:: - The ``proc`` argument is now deprecated. - """ - if proc is not None: - warnings.warn( - "Support for `proc` argument is now deprecated and will be" - "removed in an upcoming release.", - DeprecationWarning, - ) - result_callback = proc - - self._call_history.append((kwargs or {}, result_callback)) - # historizing hooks don't return results - res = self._hookexec(self, self.get_hookimpls(), kwargs) - if result_callback is None: - return - # XXX: remember firstresult isn't compat with historic - 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, method, kwargs) - if res and result_callback is not None: - result_callback(res0) - - -class HookImpl(object): - 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 "<HookImpl plugin_name=%r, plugin=%r>" % (self.plugin_name, self.plugin) - - -class HookSpec(object): - 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.argnames = "__multicall__" + list(self.argnames) - self.warn_on_impl = opts.get("warn_on_impl")
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy/manager.py
Deleted
@@ -1,394 +0,0 @@ -import inspect -import sys -from . import _tracing -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 - - -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(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 - 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, implprefix=None): - """If ``implprefix`` is given implementation functions - 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() - 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, - ) - self._implprefix = implprefix - self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall( - methods, - kwargs, - firstresult=hook.spec.opts.get("firstresult") if hook.spec else False, - ) - - def _hookexec(self, hook, methods, kwargs): - # called from all hookcaller instances. - # enable_tracing will set its own wrapping function at self._inner_hookexec - 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. """ - 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._name2pluginplugin_name = plugin - - # register matching hook implementations of the plugin - self._plugin2hookcallersplugin = 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) - 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 - # TODO: remove when we drop implprefix in 1.0 - elif res is None and self._implprefix and name.startswith(self._implprefix): - _warn_for_function( - DeprecationWarning( - "The `implprefix` system is deprecated please decorate " - "this function using an instance of HookimplMarker." - ), - method, - ) - res = {} - 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._name2pluginname == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2pluginname - - 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._name2pluginname = None - - def is_blocked(self, name): - """ return ``True`` if the given plugin name is blocked. """ - return name in self._name2plugin and self._name2pluginname 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( - "did not find any %r hooks in %r" % (self.project_name, module_or_class) - ) - - 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, - ), - ) - - 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 name0 != "_": - 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 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, 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") - - 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 - - -if hasattr(inspect, "signature"): - - def _formatdef(func): - return "%s%s" % (func.__name__, str(inspect.signature(func))) - - -else: - - def _formatdef(func): - return "%s%s" % ( - func.__name__, - inspect.formatargspec(*inspect.getargspec(func)), - )
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_deprecations.py
Deleted
@@ -1,54 +0,0 @@ -""" -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())
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/.github
Added
+(directory)
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/.github/workflows
Added
+(directory)
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/.github/workflows/main.yml
Added
@@ -0,0 +1,148 @@ +name: main + +on: + push: + branches: + - main + tags: + - "*" + + pull_request: + branches: + - main + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + name: + "windows-py36", + "windows-py39", + "windows-pypy3", + + "ubuntu-py36", + "ubuntu-py36-pytestmain", + "ubuntu-py37", + "ubuntu-py38", + "ubuntu-py39", + "ubuntu-pypy3", + "ubuntu-benchmark", + + "linting", + "docs", + + + include: + - name: "windows-py36" + python: "3.6" + os: windows-latest + tox_env: "py36" + - name: "windows-py39" + python: "3.9" + os: windows-latest + tox_env: "py39" + - name: "windows-pypy3" + python: "pypy3" + os: windows-latest + tox_env: "pypy3" + - name: "ubuntu-py36" + python: "3.6" + os: ubuntu-latest + tox_env: "py36" + use_coverage: true + - name: "ubuntu-py36-pytestmain" + python: "3.6" + os: ubuntu-latest + tox_env: "py36-pytestmain" + use_coverage: true + - name: "ubuntu-py37" + python: "3.7" + os: ubuntu-latest + tox_env: "py37" + use_coverage: true + - name: "ubuntu-py38" + python: "3.8" + os: ubuntu-latest + tox_env: "py38" + use_coverage: true + - name: "ubuntu-py39" + python: "3.9" + os: ubuntu-latest + tox_env: "py39" + use_coverage: true + - name: "ubuntu-pypy3" + python: "pypy3" + os: ubuntu-latest + tox_env: "pypy3" + use_coverage: true + - name: "ubuntu-benchmark" + python: "3.8" + os: ubuntu-latest + tox_env: "benchmark" + - name: "linting" + python: "3.8" + os: ubuntu-latest + tox_env: "linting" + - name: "docs" + python: "3.8" + os: ubuntu-latest + tox_env: "docs" + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + python -m pip install tox coverage + + - name: Test without coverage + if: "! matrix.use_coverage" + run: "tox -e ${{ matrix.tox_env }}" + + - name: Test with coverage + if: "matrix.use_coverage" + run: "tox -e ${{ matrix.tox_env }}-coverage" + + - name: Upload coverage + if: matrix.use_coverage && github.repository == 'pytest-dev/pluggy' + env: + CODECOV_NAME: ${{ matrix.name }} + run: bash scripts/upload-coverage.sh -F GHA,${{ runner.os }} + + deploy: + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pluggy' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v2 + with: + python-version: "3.8" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade wheel setuptools setuptools_scm + + - name: Build package + run: python setup.py sdist bdist_wheel + + - name: Publish package + uses: pypa/gh-action-pypi-publish@v1.4.1 + with: + user: __token__ + password: ${{ secrets.pypi_token }}
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/.pre-commit-config.yaml -> _service:tar_scm:pluggy-1.0.0.tar.gz/.pre-commit-config.yaml
Changed
@@ -1,16 +1,14 @@ repos: - repo: https://github.com/ambv/black - rev: 19.3b0 + rev: 21.7b0 hooks: - id: black args: --safe, --quiet - language_version: python3.6 - repo: https://github.com/asottile/blacken-docs - rev: v0.5.0 + rev: v1.10.0 hooks: - id: blacken-docs - additional_dependencies: black==19.3b0 - language_version: python3.6 + additional_dependencies: black==21.7b0 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.1.0 hooks: @@ -25,8 +23,12 @@ files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst|changelog/.*)$ language: python additional_dependencies: pygments, restructuredtext_lint - language_version: python3.6 - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.3.0 + rev: v1.9.0 hooks: - id: rst-backticks +- repo: https://github.com/asottile/pyupgrade + rev: v2.23.3 + hooks: + - id: pyupgrade + args: --py36-plus
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/CHANGELOG.rst -> _service:tar_scm:pluggy-1.0.0.tar.gz/CHANGELOG.rst
Changed
@@ -4,6 +4,82 @@ .. towncrier release notes start +pluggy 1.0.0 (2021-08-25) +========================= + +Deprecations and Removals +------------------------- + +- `#116 <https://github.com/pytest-dev/pluggy/issues/116>`_: Remove deprecated ``implprefix`` support. + Decorate hook implementations using an instance of HookimplMarker instead. + The deprecation was announced in release ``0.7.0``. + + +- `#120 <https://github.com/pytest-dev/pluggy/issues/120>`_: Remove the deprecated ``proc`` argument to ``call_historic``. + Use ``result_callback`` instead, which has the same behavior. + The deprecation was announced in release ``0.7.0``. + + +- `#265 <https://github.com/pytest-dev/pluggy/issues/265>`_: Remove the ``_Result.result`` property. Use ``_Result.get_result()`` instead. + Note that unlike ``result``, ``get_result()`` raises the exception if the hook raised. + The deprecation was announced in release ``0.6.0``. + + +- `#267 <https://github.com/pytest-dev/pluggy/issues/267>`_: Remove official support for Python 3.4. + + +- `#272 <https://github.com/pytest-dev/pluggy/issues/272>`_: Dropped support for Python 2. + Continue to use pluggy 0.13.x for Python 2 support. + + +- `#308 <https://github.com/pytest-dev/pluggy/issues/308>`_: Remove official support for Python 3.5. + + +- `#313 <https://github.com/pytest-dev/pluggy/issues/313>`_: The internal ``pluggy.callers``, ``pluggy.manager`` and ``pluggy.hooks`` are now explicitly marked private by a ``_`` prefix (e.g. ``pluggy._callers``). + Only API exported by the top-level ``pluggy`` module is considered public. + + +- `#59 <https://github.com/pytest-dev/pluggy/issues/59>`_: Remove legacy ``__multicall__`` recursive hook calling system. + The deprecation was announced in release ``0.5.0``. + + + +Features +-------- + +- `#282 <https://github.com/pytest-dev/pluggy/issues/282>`_: When registering a hookimpl which is declared as ``hookwrapper=True`` but whose + function is not a generator function, a ``PluggyValidationError`` exception is + now raised. + + Previously this problem would cause an error only later, when calling the hook. + + In the unlikely case that you have a hookwrapper that *returns* a generator + instead of yielding directly, for example: + + .. code-block:: python + + def my_hook_real_implementation(arg): + print("before") + yield + print("after") + + + @hookimpl(hookwrapper=True) + def my_hook(arg): + return my_hook_implementation(arg) + + change it to use ``yield from`` instead: + + .. code-block:: python + + @hookimpl(hookwrapper=True) + def my_hook(arg): + yield from my_hook_implementation(arg) + + +- `#309 <https://github.com/pytest-dev/pluggy/issues/309>`_: Add official support for Python 3.9. + + pluggy 0.13.1 (2019-11-21) ==========================
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/PKG-INFO -> _service:tar_scm:pluggy-1.0.0.tar.gz/PKG-INFO
Changed
@@ -1,460 +1,16 @@ Metadata-Version: 2.1 Name: pluggy -Version: 0.13.1 +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 license -Description: ==================================================== - 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 - +License: MIT Platform: unix Platform: linux Platform: osx Platform: win32 -Classifier: Development Status :: 4 - Beta +Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX @@ -465,13 +21,118 @@ 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 :: Only 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.* +Classifier: Programming Language :: Python :: 3.9 +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst Provides-Extra: dev +Provides-Extra: testing +License-File: LICENSE + +==================================================== +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/ + +
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/README.rst -> _service:tar_scm:pluggy-1.0.0.tar.gz/README.rst
Changed
@@ -2,7 +2,7 @@ pluggy - A minimalist production ready plugin system ==================================================== -|pypi| |conda-forge| |versions| |travis| |appveyor| |gitter| |black| |codecov| +|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov| This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects. @@ -18,19 +18,16 @@ hookimpl = pluggy.HookimplMarker("myproject") - class MySpec(object): - """A hook specification namespace. - """ + class MySpec: + """A hook specification namespace.""" @hookspec def myhook(self, arg1, arg2): - """My special little hook that you can customize. - """ + """My special little hook that you can customize.""" - class Plugin_1(object): - """A hook implementation namespace. - """ + class Plugin_1: + """A hook implementation namespace.""" @hookimpl def myhook(self, arg1, arg2): @@ -38,9 +35,8 @@ return arg1 + arg2 - class Plugin_2(object): - """A 2nd hook implementation namespace. - """ + class Plugin_2: + """A 2nd hook implementation namespace.""" @hookimpl def myhook(self, arg1, arg2): @@ -77,11 +73,8 @@ .. |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 +.. |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
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/RELEASING.rst
Added
@@ -0,0 +1,23 @@ +Release Procedure +----------------- + +#. From a clean work tree, execute:: + + tox -e release -- VERSION + + This will create the branch ready to be pushed. + +#. Open a PR targeting ``main``. + +#. All tests must pass and the PR must be approved by at least another maintainer. + +#. Publish to PyPI by pushing a tag:: + + git tag X.Y.Z release-X.Y.Z + git push git@github.com:pytest-dev/pluggy.git X.Y.Z + + The tag will trigger a new build, which will deploy to PyPI. + +#. Make sure it is `available on PyPI <https://pypi.org/project/pluggy>`_. + +#. Merge the PR into ``main``, either manually or using GitHub's web interface.
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/codecov.yml
Added
@@ -0,0 +1,7 @@ +coverage: + status: + project: true + patch: true + changes: true + +comment: off
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/docs/api_reference.rst -> _service:tar_scm:pluggy-1.0.0.tar.gz/docs/api_reference.rst
Changed
@@ -8,12 +8,12 @@ :undoc-members: :show-inheritance: -.. autoclass:: pluggy.callers._Result -.. automethod:: pluggy.callers._Result.get_result -.. automethod:: pluggy.callers._Result.force_result +.. autoclass:: pluggy._callers._Result +.. automethod:: pluggy._callers._Result.get_result +.. automethod:: pluggy._callers._Result.force_result -.. autoclass:: pluggy.hooks._HookCaller -.. automethod:: pluggy.hooks._HookCaller.call_extra -.. automethod:: pluggy.hooks._HookCaller.call_historic +.. autoclass:: pluggy._hooks._HookCaller +.. automethod:: pluggy._hooks._HookCaller.call_extra +.. automethod:: pluggy._hooks._HookCaller.call_historic -.. autoclass:: pluggy.hooks._HookRelay +.. autoclass:: pluggy._hooks._HookRelay
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/docs/conf.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/docs/conf.py
Changed
@@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys if sys.version_info >= (3, 8): @@ -26,12 +25,12 @@ # General information about the project. project = "pluggy" -copyright = u"2016, Holger Krekel" +copyright = "2016, Holger Krekel" author = "Holger Krekel" release = metadata.version(project) # The short X.Y version. -version = u".".join(release.split("."):2) +version = ".".join(release.split("."):2) language = None @@ -47,7 +46,6 @@ "github_button": "true", "github_banner": "true", "github_type": "star", - "travis_button": "true", "badge_branch": "master", "page_width": "1080px", "fixed_sidebar": "false", @@ -59,7 +57,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = (master_doc, "pluggy", u"pluggy Documentation", author, 1) +man_pages = (master_doc, "pluggy", "pluggy Documentation", author, 1) # -- Options for Texinfo output ------------------------------------------- @@ -71,7 +69,7 @@ ( master_doc, "pluggy", - u"pluggy Documentation", + "pluggy Documentation", author, "pluggy", "One line description of project.",
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/docs/examples/eggsample-spam/eggsample_spam.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/docs/examples/eggsample-spam/eggsample_spam.py
Changed
@@ -19,4 +19,4 @@ except KeyError: pass condiments"spam sauce" = 42 - return f"Now this is what I call a condiments tray!" + return "Now this is what I call a condiments tray!"
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/docs/examples/toy-example.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/docs/examples/toy-example.py
Changed
@@ -4,7 +4,7 @@ hookimpl = pluggy.HookimplMarker("myproject") -class MySpec(object): +class MySpec: """A hook specification namespace.""" @hookspec @@ -12,7 +12,7 @@ """My special little hook that you can customize.""" -class Plugin_1(object): +class Plugin_1: """A hook implementation namespace.""" @hookimpl @@ -21,7 +21,7 @@ return arg1 + arg2 -class Plugin_2(object): +class Plugin_2: """A 2nd hook implementation namespace.""" @hookimpl
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/docs/index.rst -> _service:tar_scm:pluggy-1.0.0.tar.gz/docs/index.rst
Changed
@@ -25,7 +25,7 @@ `method overriding <https://en.wikipedia.org/wiki/Method_overriding>`_ (e.g. Jinja2) or `monkey patching <https://en.wikipedia.org/wiki/Monkey_patch>`_ (e.g. gevent -or for :std:doc:`writing tests <pytest:monkeypatch>`). +or for :std:doc:`writing tests <pytest:how-to/monkeypatch>`). These strategies become problematic though when several parties want to participate in the modification of the same program. Therefore ``pluggy`` does not rely on these mechanisms to enable a more structured approach and @@ -268,6 +268,40 @@ return config +.. _specname: + +Hookspec name matching +^^^^^^^^^^^^^^^^^^^^^^ + +During plugin :ref:`registration <registration>`, pluggy attempts to match each +hook implementation declared by the *plugin* to a hook +:ref:`specification <specs>` in the *host* program with the **same name** as +the function being decorated by ``@hookimpl`` (e.g. ``setup_project`` in the +example above). Note: there is *no* strict requirement that each *hookimpl* +has a corresponding *hookspec* (see +:ref:`enforcing spec validation <enforcing>`). + +*new in version 0.13.2:* + +To override the default behavior, a *hookimpl* may also be matched to a +*hookspec* in the *host* program with a non-matching function name by using +the ``specname`` option. Continuing the example above, the *hookimpl* function +does not need to be named ``setup_project``, but if the argument +``specname="setup_project"`` is provided to the ``hookimpl`` decorator, it will +be matched and checked against the ``setup_project`` hookspec: + +.. code-block:: python + + @hookimpl(specname="setup_project") + def any_plugin_function(config, args): + """This hook is used to process the initial config + and possibly input arguments. + """ + if args: + config.process_args(args) + + return config + Call time order ^^^^^^^^^^^^^^^ By default hooks are :ref:`called <calling>` in LIFO registered order, however, @@ -285,17 +319,15 @@ @hookimpl(trylast=True) def setup_project(config, args): - """Default implementation. - """ + """Default implementation.""" if args: config.process_args(args) return config - class SomeOtherPlugin(object): - """Some other plugin defining the same hook. - """ + class SomeOtherPlugin: + """Some other plugin defining the same hook.""" @hookimpl(tryfirst=True) def setup_project(self, config, args): @@ -315,7 +347,7 @@ # load a plugin defined on a class pm.register(SomeOtherPlugin()) -For another example see the `hook function ordering`_ section of the +For another example see the :ref:`pytest:plugin-hookorder` section of the ``pytest`` docs. .. note:: @@ -361,17 +393,17 @@ if config.use_defaults: outcome.force_result(defaults) -The generator is :py:meth:`sent <python:generator.send>` a :py:class:`pluggy.callers._Result` object which can +The generator is :py:meth:`sent <python:generator.send>` a :py:class:`pluggy._callers._Result` object which can be assigned in the ``yield`` expression and used to override or inspect the final result(s) returned back to the caller using the -:py:meth:`~pluggy.callers._Result.force_result` or -:py:meth:`~pluggy.callers._Result.get_result` methods. +:py:meth:`~pluggy._callers._Result.force_result` or +:py:meth:`~pluggy._callers._Result.get_result` methods. .. note:: Hook wrappers can **not** return results (as per generator function semantics); they can only modify them using the ``_Result`` API. -Also see the `hookwrapper`_ section in the ``pytest`` docs. +Also see the :ref:`pytest:hookwrapper` section in the ``pytest`` docs. .. _specs: @@ -423,6 +455,8 @@ which defines ``hookspec`` decorated functions as in the case of ``pytest``'s `hookspec module`_ +.. _enforcing: + Enforcing spec validation ^^^^^^^^^^^^^^^^^^^^^^^^^ By default there is no strict requirement that each *hookimpl* has @@ -487,7 +521,7 @@ This can be useful for optimizing a call loop for which you are only interested in a single core *hookimpl*. An example is the -`pytest_cmdline_main`_ central routine of ``pytest``. +:func:`~_pytest.hookspec.pytest_cmdline_main` central routine of ``pytest``. Note that all ``hookwrappers`` are still invoked with the first result. Also see the :ref:`pytest:firstresult` section in the ``pytest`` docs. @@ -497,7 +531,7 @@ Historic hooks ^^^^^^^^^^^^^^ You can mark a *hookspec* as being *historic* meaning that the hook -can be called with :py:meth:`~pluggy.hooks._HookCaller.call_historic()` **before** +can be called with :py:meth:`~pluggy._hooks._HookCaller.call_historic()` **before** having been registered: .. code-block:: python @@ -553,6 +587,7 @@ This allows for multiple plugin managers from multiple projects to define hooks alongside each other. +.. _registration: Registration ------------ @@ -574,7 +609,7 @@ Loading ``setuptools`` entry points ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can automatically load plugins registered through -:ref:`setuptools entry points <setuptools:entry points>` +:ref:`setuptools entry points <setuptools:entry_points>` with the :py:meth:`~pluggy.PluginManager.load_setuptools_entrypoints()` method. @@ -618,13 +653,13 @@ to override function calls made at certain points throughout a program. A particular *hook* is invoked by calling an instance of -a :py:class:`pluggy.hooks._HookCaller` which in turn *loops* through the +a :py:class:`pluggy._hooks._HookCaller` which in turn *loops* through the ``1:N`` registered *hookimpls* and calls them in sequence. Every :py:class:`~pluggy.PluginManager` has a ``hook`` attribute -which is an instance of this :py:class:`pluggy.hooks._HookRelay`. -The :py:class:`~pluggy.hooks._HookRelay` itself contains references -(by hook name) to each registered *hookimpl*'s :py:class:`~pluggy.hooks._HookCaller` instance. +which is an instance of this :py:class:`pluggy._hooks._HookRelay`. +The :py:class:`~pluggy._hooks._HookRelay` itself contains references +(by hook name) to each registered *hookimpl*'s :py:class:`~pluggy._hooks._HookCaller` instance. More practically you call a *hook* like so: @@ -656,27 +691,24 @@ hookimpl = HookimplMarker("myproject") - class Plugin1(object): + class Plugin1: @hookimpl def myhook(self, args): - """Default implementation. - """ + """Default implementation.""" return 1 - class Plugin2(object): + class Plugin2: @hookimpl def myhook(self, args): - """Default implementation. - """ + """Default implementation.""" return 2 - class Plugin3(object): + class Plugin3: @hookimpl def myhook(self, args): - """Default implementation. - """ + """Default implementation.""" return 3 @@ -714,19 +746,19 @@ hookimpl = HookimplMarker("myproject") - class Plugin1(object): + class Plugin1: @hookimpl def myhook(self, args): return 1 - class Plugin2(object): + class Plugin2: @hookimpl def myhook(self, args): raise RuntimeError - class Plugin3(object): + class Plugin3: @hookimpl def myhook(self, args): return 3 @@ -764,7 +796,7 @@ hook is initially invoked. Historic hooks must be :ref:`specially marked <historic>` and called -using the :py:meth:`~pluggy.hooks._HookCaller.call_historic()` method: +using the :py:meth:`~pluggy._hooks._HookCaller.call_historic()` method: .. code-block:: python @@ -785,8 +817,8 @@ # historic callback is invoked here pm.register(mylateplugin) -Note that if you :py:meth:`~pluggy.hooks._HookCaller.call_historic()` -the :py:class:`~pluggy.hooks._HookCaller` (and thus your calling code) +Note that if you :py:meth:`~pluggy._hooks._HookCaller.call_historic()` +the :py:class:`~pluggy._hooks._HookCaller` (and thus your calling code) can not receive results back from the underlying *hookimpl* functions. Instead you can provide a *callback* for processing results (like the ``callback`` function above) which will be called as each new plugin @@ -801,19 +833,19 @@ ------------------- You can call a hook with temporarily participating *implementation* functions (that aren't in the registry) using the -:py:meth:`pluggy.hooks._HookCaller.call_extra()` method. +:py:meth:`pluggy._hooks._HookCaller.call_extra()` method. Calling with a subset of registered plugins ------------------------------------------- You can make a call using a subset of plugins by asking the :py:class:`~pluggy.PluginManager` first for a -:py:class:`~pluggy.hooks._HookCaller` with those plugins removed +:py:class:`~pluggy._hooks._HookCaller` with those plugins removed using the :py:meth:`pluggy.PluginManager.subset_hook_caller()` method. -You then can use that :py:class:`_HookCaller <pluggy.hooks._HookCaller>` -to make normal, :py:meth:`~pluggy.hooks._HookCaller.call_historic`, or -:py:meth:`~pluggy.hooks._HookCaller.call_extra` calls as necessary. +You then can use that :py:class:`_HookCaller <pluggy._hooks._HookCaller>` +to make normal, :py:meth:`~pluggy._hooks._HookCaller.call_historic`, or +:py:meth:`~pluggy._hooks._HookCaller.call_extra` calls as necessary. Built-in tracing **************** @@ -896,14 +928,8 @@ .. hyperlinks -.. _pytest_cmdline_main: - https://docs.pytest.org/en/latest/_modules/_pytest/hookspec.html#pytest_cmdline_main .. _hookspec module: https://docs.pytest.org/en/latest/_modules/_pytest/hookspec.html -.. _hookwrapper: - http://doc.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks -.. _hook function ordering: - http://doc.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example .. _request-response pattern: https://en.wikipedia.org/wiki/Request%E2%80%93response .. _publish-subscribe:
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/pyproject.toml -> _service:tar_scm:pluggy-1.0.0.tar.gz/pyproject.toml
Changed
@@ -5,6 +5,9 @@ "wheel", +tool.setuptools_scm +write_to = "src/pluggy/_version.py" + tool.towncrier package = "pluggy" package_dir = "src/pluggy"
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/scripts/release.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/scripts/release.py
Changed
@@ -10,16 +10,16 @@ def create_branch(version): - """Create a fresh branch from upstream/master""" + """Create a fresh branch from upstream/main""" repo = Repo.init(".") if repo.is_dirty(untracked_files=True): - raise RuntimeError(f"Repository is dirty, please commit/stash your changes.") + raise RuntimeError("Repository is dirty, please commit/stash your changes.") branch_name = f"release-{version}" - print(f"{Fore.CYAN}Create {branch_name} branch from upstream master") + print(f"{Fore.CYAN}Create {branch_name} branch from upstream main") upstream = get_upstream(repo) upstream.fetch() - release_branch = repo.create_head(branch_name, upstream.refs.master, force=True) + release_branch = repo.create_head(branch_name, upstream.refs.main, force=True) release_branch.checkout() return repo
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/scripts/upload-coverage.sh
Added
@@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e +set -x + +if -z "$TOXENV" ; then + python -m pip install coverage +else + # Add last TOXENV to $PATH. + PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH" +fi + +python -m coverage xml +# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461 +curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh +bash codecov-upload.sh -Z -X fix -f coverage.xml "$@"
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/setup.cfg -> _service:tar_scm:pluggy-1.0.0.tar.gz/setup.cfg
Changed
@@ -2,7 +2,52 @@ universal = 1 metadata -license_file = LICENSE +name = pluggy +description = plugin and hook calling mechanisms for python +long_description = file: README.rst +long_description_content_type = text/x-rst +license = MIT +platforms = unix, linux, osx, win32 +author = Holger Krekel +author_email = holger@merlinux.eu +url = https://github.com/pytest-dev/pluggy +classifiers = + Development Status :: 6 - Mature + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: POSIX + Operating System :: Microsoft :: Windows + Operating System :: MacOS :: MacOS X + Topic :: Software Development :: Testing + Topic :: Software Development :: Libraries + Topic :: Utilities + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + +options +packages = + pluggy +install_requires = + importlib-metadata>=0.12;python_version<"3.8" +python_requires = >=3.6 +package_dir = + =src +setup_requires = + setuptools-scm + +options.extras_require +dev = + pre-commit + tox +testing = + pytest + pytest-benchmark devpi:upload formats = sdist.tgz,bdist_wheel
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/setup.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/setup.py
Changed
@@ -1,49 +1,5 @@ from setuptools import setup -classifiers = - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: POSIX", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS :: MacOS X", - "Topic :: Software Development :: Testing", - "Topic :: Software Development :: Libraries", - "Topic :: Utilities", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - + - ("Programming Language :: Python :: %s" % x) - for x in "2 2.7 3 3.4 3.5 3.6 3.7 3.8".split() - - -with open("README.rst", "rb") as fd: - long_description = fd.read().decode("utf-8") - -with open("CHANGELOG.rst", "rb") as fd: - long_description += "\n\n" + fd.read().decode("utf-8") - - -def main(): - setup( - name="pluggy", - description="plugin and hook calling mechanisms for python", - long_description=long_description, - use_scm_version={"write_to": "src/pluggy/_version.py"}, - setup_requires="setuptools-scm", - license="MIT license", - platforms="unix", "linux", "osx", "win32", - author="Holger Krekel", - author_email="holger@merlinux.eu", - url="https://github.com/pytest-dev/pluggy", - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", - install_requires='importlib-metadata>=0.12;python_version<"3.8"', - extras_require={"dev": "pre-commit", "tox"}, - classifiers=classifiers, - packages="pluggy", - package_dir={"": "src"}, - ) - if __name__ == "__main__": - main() + setup(use_scm_version={"write_to": "src/pluggy/_version.py"})
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy.egg-info/PKG-INFO -> _service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy.egg-info/PKG-INFO
Changed
@@ -1,460 +1,16 @@ Metadata-Version: 2.1 Name: pluggy -Version: 0.13.1 +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 license -Description: ==================================================== - 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 - +License: MIT Platform: unix Platform: linux Platform: osx Platform: win32 -Classifier: Development Status :: 4 - Beta +Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX @@ -465,13 +21,118 @@ 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 :: Only 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.* +Classifier: Programming Language :: Python :: 3.9 +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst Provides-Extra: dev +Provides-Extra: testing +License-File: LICENSE + +==================================================== +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/ + +
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy.egg-info/SOURCES.txt -> _service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy.egg-info/SOURCES.txt
Changed
@@ -1,17 +1,17 @@ .coveragerc .gitignore .pre-commit-config.yaml -.travis.yml CHANGELOG.rst -HOWTORELEASE.rst LICENSE MANIFEST.in README.rst -appveyor.yml +RELEASING.rst +codecov.yml pyproject.toml setup.cfg setup.py tox.ini +.github/workflows/main.yml changelog/README.rst changelog/_template.rst docs/api_reference.rst @@ -28,12 +28,14 @@ docs/examples/eggsample/eggsample/host.py docs/examples/eggsample/eggsample/lib.py scripts/release.py +scripts/upload-coverage.sh src/pluggy/__init__.py +src/pluggy/_callers.py +src/pluggy/_hooks.py +src/pluggy/_manager.py +src/pluggy/_result.py src/pluggy/_tracing.py src/pluggy/_version.py -src/pluggy/callers.py -src/pluggy/hooks.py -src/pluggy/manager.py src/pluggy.egg-info/PKG-INFO src/pluggy.egg-info/SOURCES.txt src/pluggy.egg-info/dependency_links.txt @@ -41,7 +43,6 @@ src/pluggy.egg-info/top_level.txt testing/benchmark.py testing/conftest.py -testing/test_deprecations.py testing/test_details.py testing/test_helpers.py testing/test_hookcaller.py
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy.egg-info/requires.txt -> _service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy.egg-info/requires.txt
Changed
@@ -5,3 +5,7 @@ dev pre-commit tox + +testing +pytest +pytest-benchmark
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy/__init__.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/__init__.py
Changed
@@ -13,6 +13,6 @@ "HookimplMarker", -from .manager import PluginManager, PluginValidationError -from .callers import HookCallError -from .hooks import HookspecMarker, HookimplMarker +from ._manager import PluginManager, PluginValidationError +from ._callers import HookCallError +from ._hooks import HookspecMarker, HookimplMarker
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/_callers.py
Added
@@ -0,0 +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_kwargsargname 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(results0 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()
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/_hooks.py
Added
@@ -0,0 +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(argsindex:) + 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 args0 in implicit_names + ): + args = args1: + + 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 wrappersi + 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 methodsi.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(res0) + + +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")
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/_manager.py
Added
@@ -0,0 +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._name2pluginplugin_name = plugin + + # register matching hook implementations of the plugin + self._plugin2hookcallersplugin = 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._name2pluginname == None registration was blocked: ignore + if self._name2plugin.get(name): + del self._name2pluginname + + 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._name2pluginname = None + + def is_blocked(self, name): + """return ``True`` if the given plugin name is blocked.""" + return name in self._name2plugin and self._name2pluginname 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 name0 != "_": + 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)}"
View file
_service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/_result.py
Added
@@ -0,0 +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 ex1.with_traceback(ex2)
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy/_tracing.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/_tracing.py
Changed
@@ -3,7 +3,7 @@ """ -class TagTracer(object): +class TagTracer: def __init__(self): self._tags2proc = {} self._writer = None @@ -22,10 +22,10 @@ content = " ".join(map(str, args)) indent = " " * self.indent - lines = "%s%s %s\n" % (indent, content, ":".join(tags)) + lines = "{}{} {}\n".format(indent, content, ":".join(tags)) for name, value in extra.items(): - lines.append("%s %s: %s\n" % (indent, name, value)) + lines.append(f"{indent} {name}: {value}\n") return "".join(lines) @@ -50,7 +50,7 @@ self._tags2proctags = processor -class TagTracerSub(object): +class TagTracerSub: def __init__(self, root, tags): self.root = root self.tags = tags
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/src/pluggy/_version.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/src/pluggy/_version.py
Changed
@@ -1,4 +1,5 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '0.13.1' +version = '1.0.0' +version_tuple = (1, 0, 0)
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/benchmark.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/benchmark.py
Changed
@@ -2,22 +2,15 @@ Benchmarking and performance tests. """ import pytest -from pluggy import HookspecMarker, HookimplMarker -from pluggy.hooks import HookImpl -from pluggy.callers import _multicall, _legacymulticall +from pluggy import HookspecMarker, HookimplMarker, PluginManager +from pluggy._hooks import HookImpl +from pluggy._callers import _multicall + 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 @@ -38,14 +31,72 @@ 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) +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)
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/conftest.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/conftest.py
Changed
@@ -10,7 +10,7 @@ hookspec = HookspecMarker("example") - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): return arg + 1
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_details.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/test_details.py
Changed
@@ -15,15 +15,15 @@ opts = {} return opts - class Plugin(object): + class Plugin: def x1meth(self): pass @hookimpl(hookwrapper=True, tryfirst=True) def x1meth2(self): - pass + yield # pragma: no cover - class Spec(object): + class Spec: @hookspec def x1meth(self): pass @@ -47,12 +47,12 @@ def test_warn_when_deprecated_specified(recwarn): warning = DeprecationWarning("foo is deprecated") - class Spec(object): + class Spec: @hookspec(warn_on_impl=warning) def foo(self): pass - class Plugin(object): + class Plugin: @hookimpl def foo(self): pass @@ -73,11 +73,11 @@ when getattr() gets called (#11). """ - class DontTouchMe(object): + class DontTouchMe: def __getattr__(self, x): raise Exception("cant touch me") - class Module(object): + class Module: pass module = Module() @@ -123,7 +123,7 @@ def test_repr(): class Plugin: @hookimpl - def myhook(): + def myhook(self): raise NotImplementedError() pm = PluginManager(hookspec.project_name) @@ -131,5 +131,5 @@ plugin = Plugin() pname = pm.register(plugin) assert repr(pm.hook.myhook._nonwrappers0) == ( - "<HookImpl plugin_name=%r, plugin=%r>" % (pname, plugin) + f"<HookImpl plugin_name={pname!r}, plugin={plugin!r}>" )
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_helpers.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/test_helpers.py
Changed
@@ -1,19 +1,16 @@ -from pluggy.hooks import varnames -from pluggy.manager import _formatdef - -import sys -import pytest +from pluggy._hooks import varnames +from pluggy._manager import _formatdef def test_varnames(): def f(x): i = 3 # noqa - class A(object): + class A: def f(self, y): pass - class B(object): + class B: def __call__(self, z): pass @@ -30,18 +27,18 @@ def test_varnames_class(): - class C(object): + class C: def __init__(self, x): pass - class D(object): + class D: pass - class E(object): + class E: def __init__(self, x): pass - class F(object): + class F: pass assert varnames(C) == (("x",), ()) @@ -50,22 +47,19 @@ 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 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():
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_hookcaller.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/test_hookcaller.py
Changed
@@ -1,7 +1,7 @@ import pytest -from pluggy import HookimplMarker, HookspecMarker -from pluggy.hooks import HookImpl +from pluggy import HookimplMarker, HookspecMarker, PluginValidationError +from pluggy._hooks import HookImpl hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") @@ -9,7 +9,7 @@ @pytest.fixture def hc(pm): - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): pass @@ -155,7 +155,7 @@ def test_hookspec(pm): - class HookSpec(object): + class HookSpec: @hookspec() def he_myhook1(arg1): pass @@ -191,7 +191,7 @@ """Verify hook caller instances are registered by name onto the relay and can be likewise unregistered.""" - class Api(object): + class Api: @hookspec def hello(self, arg): "api hook 1" @@ -201,7 +201,7 @@ assert hasattr(hook, "hello") assert repr(hook.hello).find("hello") != -1 - class Plugin(object): + class Plugin: @hookimpl def hello(self, arg): return arg + 1 @@ -213,3 +213,60 @@ 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()
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_invocations.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/test_invocations.py
Changed
@@ -7,14 +7,14 @@ def test_argmismatch(pm): - class Api(object): + class Api: @hookspec def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) - class Plugin(object): + class Plugin: @hookimpl def hello(self, argwrong): pass @@ -26,7 +26,7 @@ def test_only_kwargs(pm): - class Api(object): + class Api: @hookspec def hello(self, arg): "api hook 1" @@ -44,17 +44,17 @@ under the same spec. """ - class Api(object): + class Api: @hookspec def hello(self, arg1, arg2, common_arg): "api hook 1" - class Plugin1(object): + class Plugin1: @hookimpl def hello(self, arg1, common_arg): return arg1 + common_arg - class Plugin2(object): + class Plugin2: @hookimpl def hello(self, arg2, common_arg): return arg2 + common_arg @@ -68,29 +68,29 @@ def test_call_order(pm): - class Api(object): + class Api: @hookspec def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) - class Plugin1(object): + class Plugin1: @hookimpl def hello(self, arg): return 1 - class Plugin2(object): + class Plugin2: @hookimpl def hello(self, arg): return 2 - class Plugin3(object): + class Plugin3: @hookimpl def hello(self, arg): return 3 - class Plugin4(object): + class Plugin4: @hookimpl(hookwrapper=True) def hello(self, arg): assert arg == 0 @@ -106,29 +106,29 @@ def test_firstresult_definition(pm): - class Api(object): + class Api: @hookspec(firstresult=True) def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) - class Plugin1(object): + class Plugin1: @hookimpl def hello(self, arg): return arg + 1 - class Plugin2(object): + class Plugin2: @hookimpl def hello(self, arg): return arg - 1 - class Plugin3(object): + class Plugin3: @hookimpl def hello(self, arg): return None - class Plugin4(object): + class Plugin4: @hookimpl(hookwrapper=True) def hello(self, arg): assert arg == 3 @@ -144,22 +144,21 @@ def test_firstresult_force_result(pm): - """Verify forcing a result in a wrapper. - """ + """Verify forcing a result in a wrapper.""" - class Api(object): + class Api: @hookspec(firstresult=True) def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) - class Plugin1(object): + class Plugin1: @hookimpl def hello(self, arg): return arg + 1 - class Plugin2(object): + class Plugin2: @hookimpl(hookwrapper=True) def hello(self, arg): assert arg == 3 @@ -167,7 +166,7 @@ assert outcome.get_result() == 4 outcome.force_result(0) - class Plugin3(object): + class Plugin3: @hookimpl def hello(self, arg): return None @@ -184,14 +183,14 @@ the multi-call loop returns a None value. """ - class Api(object): + class Api: @hookspec(firstresult=True) def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) - class Plugin1(object): + class Plugin1: @hookimpl def hello(self, arg): return None @@ -206,7 +205,7 @@ hook the multi-call loop should return a None value. """ - class Api(object): + class Api: @hookspec(firstresult=True) def hello(self, arg): "api hook 1"
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_multicall.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/test_multicall.py
Changed
@@ -1,7 +1,7 @@ import pytest from pluggy import HookCallError, HookspecMarker, HookimplMarker -from pluggy.hooks import HookImpl -from pluggy.callers import _multicall, _legacymulticall +from pluggy._hooks import HookImpl +from pluggy._callers import _multicall hookspec = HookspecMarker("example") @@ -14,32 +14,7 @@ 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 + return caller("foo", hookfuncs, kwargs, firstresult) def test_keyword_args(): @@ -47,7 +22,7 @@ def f(x): return x + 1 - class A(object): + class A: @hookimpl def f(self, x, y): return x + y @@ -74,20 +49,6 @@ 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():
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/testing/test_pluginmanager.py -> _service:tar_scm:pluggy-1.0.0.tar.gz/testing/test_pluginmanager.py
Changed
@@ -2,16 +2,14 @@ ``PluginManager`` unit and public API testing. """ import pytest -import types from pluggy import ( - PluginManager, PluginValidationError, HookCallError, HookimplMarker, HookspecMarker, ) -from pluggy.manager import importlib_metadata +from pluggy._manager import importlib_metadata hookspec = HookspecMarker("example") @@ -30,7 +28,7 @@ def test_pm(pm): """Basic registration with objects""" - class A(object): + class A: pass a1, a2 = A(), A() @@ -51,7 +49,7 @@ def test_has_plugin(pm): - class A(object): + class A: pass a1 = A() @@ -61,7 +59,7 @@ def test_register_dynamic_attr(he_pm): - class A(object): + class A: def __getattr__(self, name): if name0 != "_": return 42 @@ -73,7 +71,7 @@ def test_pm_name(pm): - class A(object): + class A: pass a1 = A() @@ -92,7 +90,7 @@ def test_set_blocked(pm): - class A(object): + class A: pass a1 = A() @@ -111,7 +109,7 @@ def test_register_mismatch_method(he_pm): - class hello(object): + class hello: @hookimpl def he_method_notexists(self): pass @@ -125,7 +123,7 @@ def test_register_mismatch_arg(he_pm): - class hello(object): + class hello: @hookimpl def he_method1(self, qlwkje): pass @@ -137,8 +135,21 @@ 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(object): + class MyPlugin: pass my = MyPlugin() @@ -146,7 +157,7 @@ assert my in pm.get_plugins() my2 = MyPlugin() pm.register(my2) - assert set(my, my2).issubset(pm.get_plugins()) + assert {my, my2}.issubset(pm.get_plugins()) assert pm.is_registered(my) assert pm.is_registered(my2) @@ -156,14 +167,14 @@ def test_register_unknown_hooks(pm): - class Plugin1(object): + class Plugin1: @hookimpl def he_method1(self, arg): return arg + 1 pname = pm.register(Plugin1()) - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): pass @@ -175,7 +186,7 @@ def test_register_historic(pm): - class Hooks(object): + class Hooks: @hookspec(historic=True) def he_method1(self, arg): pass @@ -185,7 +196,7 @@ pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) out = - class Plugin(object): + class Plugin: @hookimpl def he_method1(self, arg): out.append(arg) @@ -193,7 +204,7 @@ pm.register(Plugin()) assert out == 1 - class Plugin2(object): + class Plugin2: @hookimpl def he_method1(self, arg): out.append(arg * 10) @@ -219,14 +230,14 @@ else: callback = None - class Hooks(object): + class Hooks: @hookspec(historic=True) def he_method1(self, arg): pass pm.add_hookspecs(Hooks) - class Plugin1(object): + class Plugin1: @hookimpl def he_method1(self, arg): return arg * 10 @@ -236,7 +247,7 @@ he_method1 = pm.hook.he_method1 he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1)) - class Plugin2(object): + class Plugin2: @hookimpl def he_method1(self, arg): return arg * 10 @@ -249,24 +260,24 @@ def test_with_callbacks_immediately_executed(pm): - class Hooks(object): + class Hooks: @hookspec(historic=True) def he_method1(self, arg): pass pm.add_hookspecs(Hooks) - class Plugin1(object): + class Plugin1: @hookimpl def he_method1(self, arg): return arg * 10 - class Plugin2(object): + class Plugin2: @hookimpl def he_method1(self, arg): return arg * 20 - class Plugin3(object): + class Plugin3: @hookimpl def he_method1(self, arg): return arg * 30 @@ -283,7 +294,7 @@ def test_register_historic_incompat_hookwrapper(pm): - class Hooks(object): + class Hooks: @hookspec(historic=True) def he_method1(self, arg): pass @@ -292,7 +303,7 @@ out = - class Plugin(object): + class Plugin: @hookimpl(hookwrapper=True) def he_method1(self, arg): out.append(arg) @@ -302,7 +313,7 @@ def test_call_extra(pm): - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): pass @@ -317,14 +328,14 @@ def test_call_with_too_few_args(pm): - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): pass pm.add_hookspecs(Hooks) - class Plugin1(object): + class Plugin1: @hookimpl def he_method1(self, arg): 0 / 0 @@ -336,7 +347,7 @@ def test_subset_hook_caller(pm): - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): pass @@ -345,17 +356,17 @@ out = - class Plugin1(object): + class Plugin1: @hookimpl def he_method1(self, arg): out.append(arg) - class Plugin2(object): + class Plugin2: @hookimpl def he_method1(self, arg): out.append(arg * 10) - class PluginNo(object): + class PluginNo: pass plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() @@ -386,7 +397,7 @@ def test_get_hookimpls(pm): - class Hooks(object): + class Hooks: @hookspec def he_method1(self, arg): pass @@ -394,17 +405,17 @@ pm.add_hookspecs(Hooks) assert pm.hook.he_method1.get_hookimpls() == - class Plugin1(object): + class Plugin1: @hookimpl def he_method1(self, arg): pass - class Plugin2(object): + class Plugin2: @hookimpl def he_method1(self, arg): pass - class PluginNo(object): + class PluginNo: pass plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() @@ -422,44 +433,19 @@ 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): + class EntryPoint: name = "myname" group = "hello" value = "myname:foo" def load(self): - class PseudoPlugin(object): + class PseudoPlugin: x = 42 return PseudoPlugin() - class Distribution(object): + class Distribution: entry_points = (EntryPoint(),) dist = Distribution() @@ -485,12 +471,12 @@ def test_add_tracefuncs(he_pm): out = - class api1(object): + class api1: @hookimpl def he_method1(self): out.append("he_method1-api1") - class api2(object): + class api2: @hookimpl def he_method1(self): out.append("he_method1-api2") @@ -524,12 +510,12 @@ def test_hook_tracing(he_pm): saveindent = - class api1(object): + class api1: @hookimpl def he_method1(self): saveindent.append(he_pm.trace.root.indent) - class api2(object): + class api2: @hookimpl def he_method1(self): saveindent.append(he_pm.trace.root.indent) @@ -556,45 +542,3 @@ assert saveindent0 > 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()
View file
_service:tar_scm:pluggy-0.13.1.tar.gz/tox.ini -> _service:tar_scm:pluggy-1.0.0.tar.gz/tox.ini
Changed
@@ -1,5 +1,5 @@ tox -envlist=linting,docs,py{27,34,35,36,37,38,py,py3},py{36,37}-pytest{master,features} +envlist=linting,docs,py{36,37,38,39,py3},py{36,37}-pytest{main} testenv commands= @@ -9,11 +9,10 @@ setenv= _PYTEST_SETUP_SKIP_PLUGGY_DEP=1 coverage: _PLUGGY_TOX_CMD=coverage run -m pytest - pytestmaster: _PYTEST_DEP=git+https://github.com/pytest-dev/pytest.git@master - pytestfeatures: _PYTEST_DEP=git+https://github.com/pytest-dev/pytest.git@features +extras=testing deps= coverage: coverage - {env:_PYTEST_DEP:pytest} + pytestmain: git+https://github.com/pytest-dev/pytest.git@main testenv:benchmark commands=pytest {posargs:testing/benchmark.py} @@ -23,7 +22,7 @@ testenv:linting skip_install = true -basepython = python3.6 +basepython = python3 deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2