diff --git a/_pytest/_pluggy.py b/_pytest/_pluggy.py index 6cc1d3d54..0cb4a4fb4 100644 --- a/_pytest/_pluggy.py +++ b/_pytest/_pluggy.py @@ -1,11 +1,6 @@ """ -imports symbols from vendored "pluggy" if available, otherwise -falls back to importing "pluggy" from the default namespace. +Import symbols from ``pluggy`` """ from __future__ import absolute_import, division, print_function -try: - from _pytest.vendored_packages.pluggy import * # noqa - from _pytest.vendored_packages.pluggy import __version__ # noqa -except ImportError: - from pluggy import * # noqa - from pluggy import __version__ # noqa +from pluggy import * +from pluggy import __version__ diff --git a/_pytest/vendored_packages/README.md b/_pytest/vendored_packages/README.md deleted file mode 100644 index b5fe6febb..000000000 --- a/_pytest/vendored_packages/README.md +++ /dev/null @@ -1,13 +0,0 @@ -This directory vendors the `pluggy` module. - -For a more detailed discussion for the reasons to vendoring this -package, please see [this issue](https://github.com/pytest-dev/pytest/issues/944). - -To update the current version, execute: - -``` -$ pip install -U pluggy== --no-compile --target=_pytest/vendored_packages -``` - -And commit the modified files. The `pluggy-.dist-info` directory -created by `pip` should be added as well. diff --git a/_pytest/vendored_packages/__init__.py b/_pytest/vendored_packages/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst deleted file mode 100644 index da0e7a6ed..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst +++ /dev/null @@ -1,11 +0,0 @@ - -Plugin registration and hook calling for Python -=============================================== - -This is the plugin manager as used by pytest but stripped -of pytest specific details. - -During the 0.x series this plugin does not have much documentation -except extensive docstrings in the pluggy.py module. - - diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e38..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt deleted file mode 100644 index 121017d08..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA deleted file mode 100644 index bd88517c9..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA +++ /dev/null @@ -1,40 +0,0 @@ -Metadata-Version: 2.0 -Name: pluggy -Version: 0.4.0 -Summary: plugin and hook calling mechanisms for python -Home-page: https://github.com/pytest-dev/pluggy -Author: Holger Krekel -Author-email: holger at merlinux.eu -License: MIT license -Platform: unix -Platform: linux -Platform: osx -Platform: win32 -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: POSIX -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Topic :: Software Development :: Testing -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Utilities -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 - - -Plugin registration and hook calling for Python -=============================================== - -This is the plugin manager as used by pytest but stripped -of pytest specific details. - -During the 0.x series this plugin does not have much documentation -except extensive docstrings in the pluggy.py module. - - diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD deleted file mode 100644 index 3003a3bf2..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD +++ /dev/null @@ -1,9 +0,0 @@ -pluggy.py,sha256=u0oG9cv-oLOkNvEBlwnnu8pp1AyxpoERgUO00S3rvpQ,31543 -pluggy-0.4.0.dist-info/DESCRIPTION.rst,sha256=ltvjkFd40LW_xShthp6RRVM6OB_uACYDFR3kTpKw7o4,307 -pluggy-0.4.0.dist-info/LICENSE.txt,sha256=ruwhUOyV1HgE9F35JVL9BCZ9vMSALx369I4xq9rhpkM,1134 -pluggy-0.4.0.dist-info/METADATA,sha256=pe2hbsqKFaLHC6wAQPpFPn0KlpcPfLBe_BnS4O70bfk,1364 -pluggy-0.4.0.dist-info/RECORD,, -pluggy-0.4.0.dist-info/WHEEL,sha256=9Z5Xm-eel1bTS7e6ogYiKz0zmPEqDwIypurdHN1hR40,116 -pluggy-0.4.0.dist-info/metadata.json,sha256=T3go5L2qOa_-H-HpCZi3EoVKb8sZ3R-fOssbkWo2nvM,1119 -pluggy-0.4.0.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7 -pluggy-0.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL deleted file mode 100644 index 8b6dd1b5a..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL +++ /dev/null @@ -1,6 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.29.0) -Root-Is-Purelib: true -Tag: py2-none-any -Tag: py3-none-any - diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json deleted file mode 100644 index cde22aff0..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"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 :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "holger at merlinux.eu", "name": "Holger Krekel", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "https://github.com/pytest-dev/pluggy"}}}, "generator": "bdist_wheel (0.29.0)", "license": "MIT license", "metadata_version": "2.0", "name": "pluggy", "platform": "unix", "summary": "plugin and hook calling mechanisms for python", "version": "0.4.0"} \ No newline at end of file diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt deleted file mode 100644 index 11bdb5c1f..000000000 --- a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -pluggy diff --git a/_pytest/vendored_packages/pluggy.py b/_pytest/vendored_packages/pluggy.py deleted file mode 100644 index aebddad01..000000000 --- a/_pytest/vendored_packages/pluggy.py +++ /dev/null @@ -1,802 +0,0 @@ -""" -PluginManager, basic initialization and tracing. - -pluggy is the cristallized core of plugin management as used -by some 150 plugins for pytest. - -Pluggy uses semantic versioning. Breaking changes are only foreseen for -Major releases (incremented X in "X.Y.Z"). If you want to use pluggy in -your project you should thus use a dependency restriction like -"pluggy>=0.1.0,<1.0" to avoid surprises. - -pluggy is concerned with hook specification, hook implementations and hook -calling. For any given hook specification a hook call invokes up to N implementations. -A hook implementation can influence its position and type of execution: -if attributed "tryfirst" or "trylast" it will be tried to execute -first or last. However, if attributed "hookwrapper" an implementation -can wrap all calls to non-hookwrapper implementations. A hookwrapper -can thus execute some code ahead and after the execution of other hooks. - -Hook specification is done by way of a regular python function where -both the function name and the names of all its arguments are significant. -Each hook implementation function is verified against the original specification -function, including the names of all its arguments. To allow for hook specifications -to evolve over the livetime of a project, hook implementations can -accept less arguments. One can thus add new arguments and semantics to -a hook specification by adding another argument typically without breaking -existing hook implementations. - -The chosen approach is meant to let a hook designer think carefuly about -which objects are needed by an extension writer. By contrast, subclass-based -extension mechanisms often expose a lot more state and behaviour than needed, -thus restricting future developments. - -Pluggy currently consists of functionality for: - -- a way to register new hook specifications. Without a hook - specification no hook calling can be performed. - -- a registry of plugins which contain hook implementation functions. It - is possible to register plugins for which a hook specification is not yet - known and validate all hooks when the system is in a more referentially - consistent state. Setting an "optionalhook" attribution to a hook - implementation will avoid PluginValidationError's if a specification - is missing. This allows to have optional integration between plugins. - -- a "hook" relay object from which you can launch 1:N calls to - registered hook implementation functions - -- a mechanism for ordering hook implementation functions - -- mechanisms for two different type of 1:N calls: "firstresult" for when - the call should stop when the first implementation returns a non-None result. - And the other (default) way of guaranteeing that all hook implementations - will be called and their non-None result collected. - -- mechanisms for "historic" extension points such that all newly - registered functions will receive all hook calls that happened - before their registration. - -- a mechanism for discovering plugin objects which are based on - setuptools based entry points. - -- a simple tracing mechanism, including tracing of plugin calls and - their arguments. - -""" -import sys -import inspect - -__version__ = '0.4.0' - -__all__ = ["PluginManager", "PluginValidationError", "HookCallError", - "HookspecMarker", "HookimplMarker"] - -_py3 = sys.version_info > (3, 0) - - -class HookspecMarker: - """ Decorator helper class for marking functions as hook specifications. - - You can instantiate it with a project_name to get a decorator. - Calling PluginManager.add_hookspecs later will discover all marked functions - if the 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): - """ if passed a function, directly sets attributes on the function - which will make it discoverable to 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)) - 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 PluginManager.register later will discover all marked functions - if the 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 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 specfication. - - 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 an ``_CallOutcome`` 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) - - -class _TagTracer: - def __init__(self): - self._tag2proc = {} - self.writer = None - self.indent = 0 - - def get(self, name): - return _TagTracerSub(self, (name,)) - - def format_message(self, tags, args): - if isinstance(args[-1], dict): - extra = args[-1] - args = args[:-1] - else: - extra = {} - - content = " ".join(map(str, args)) - indent = " " * self.indent - - lines = [ - "%s%s [%s]\n" % (indent, content, ":".join(tags)) - ] - - for name, value in extra.items(): - lines.append("%s %s: %s\n" % (indent, name, value)) - return lines - - def processmessage(self, tags, args): - if self.writer is not None and args: - lines = self.format_message(tags, args) - self.writer(''.join(lines)) - try: - self._tag2proc[tags](tags, args) - except KeyError: - pass - - def setwriter(self, writer): - self.writer = writer - - def setprocessor(self, tags, processor): - if isinstance(tags, str): - tags = tuple(tags.split(":")) - else: - assert isinstance(tags, tuple) - self._tag2proc[tags] = processor - - -class _TagTracerSub: - def __init__(self, root, tags): - self.root = root - self.tags = tags - - def __call__(self, *args): - self.root.processmessage(self.tags, args) - - def setmyprocessor(self, processor): - self.root.setprocessor(self.tags, processor) - - def get(self, name): - return self.__class__(self.root, self.tags + (name,)) - - -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)) - - -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 _CallOutcome 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 = _CallOutcome(func) - try: - wrap_controller.send(call_outcome) - _raise_wrapfail(wrap_controller, "has second yield") - except StopIteration: - pass - return call_outcome.get_result() - - -class _CallOutcome: - """ Outcome of a function call, either an exception or a proper result. - Calling the ``get_result`` method will return the result or reraise - the exception raised when the function was called. """ - excinfo = None - - def __init__(self, func): - try: - self.result = func() - except BaseException: - self.excinfo = sys.exc_info() - - def force_result(self, result): - self.result = result - self.excinfo = None - - def get_result(self): - if self.excinfo is None: - return self.result - else: - ex = self.excinfo - if _py3: - raise ex[1].with_traceback(ex[2]) - _reraise(*ex) # noqa - -if not _py3: - exec(""" -def _reraise(cls, val, tb): - raise cls, val, tb -""") - - -class _TracedHookExecution: - def __init__(self, pluginmanager, before, after): - self.pluginmanager = pluginmanager - self.before = before - self.after = after - self.oldcall = pluginmanager._inner_hookexec - assert not isinstance(self.oldcall, _TracedHookExecution) - self.pluginmanager._inner_hookexec = self - - def __call__(self, hook, hook_impls, kwargs): - self.before(hook.name, hook_impls, kwargs) - outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs)) - self.after(outcome, hook.name, hook_impls, kwargs) - return outcome.get_result() - - def undo(self): - self.pluginmanager._inner_hookexec = self.oldcall - - -class PluginManager(object): - """ Core Pluginmanager class which manages registration - of plugin objects and 1:N hook calling. - - You can register new hooks by calling ``add_hookspec(module_or_class)``. - You can register plugin objects (which contain hooks) by calling - ``register(plugin)``. The Pluginmanager is initialized with a - prefix that is searched for in the names of the dict of registered - plugin objects. An optional excludefunc allows to blacklist names which - are not considered as hooks despite a matching prefix. - - For debugging purposes you can call ``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 = _TagTracer().get("pluginmanage") - self.hook = _HookRelay(self.trace.root.get("hook")) - self._implprefix = implprefix - self._inner_hookexec = lambda hook, methods, kwargs: \ - _MultiCall(methods, kwargs, hook.spec_opts).execute() - - 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 ValueError if the plugin is already - registered. """ - plugin_name = name or self.get_canonical_name(plugin) - - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: - if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration - raise ValueError("Plugin already registered: %s=%s\n%s" % - (plugin_name, plugin, self._name2plugin)) - - # XXX if an error happens we should make sure no state has been - # changed at point of return - self._name2plugin[plugin_name] = plugin - - # register matching hook implementations of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] - for name in dir(plugin): - hookimpl_opts = self.parse_hookimpl_opts(plugin, name) - if hookimpl_opts is not None: - normalize_hookimpl_opts(hookimpl_opts) - method = getattr(plugin, name) - hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts) - 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) - 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 - elif res is None and self._implprefix and name.startswith(self._implprefix): - 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._name2plugin[name] == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2plugin[name] - - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - - return plugin - - def set_blocked(self, name): - """ block registrations of the given name, unregister if already registered. """ - self.unregister(name=name) - self._name2plugin[name] = None - - def is_blocked(self, name): - """ return True if the name blogs registering plugins of that name. """ - return name in self._name2plugin and self._name2plugin[name] is None - - def add_hookspecs(self, module_or_class): - """ add new hook specifications defined in the given module_or_class. - Functions are recognized if they have been decorated accordingly. """ - names = [] - for name in dir(module_or_class): - spec_opts = self.parse_hookspec_opts(module_or_class, name) - if spec_opts is not None: - hc = getattr(self.hook, name, None) - if hc is None: - hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts) - setattr(self.hook, name, hc) - else: - # plugins registered this hook without knowing the spec - hc.set_specification(module_or_class, spec_opts) - for hookfunction in (hc._wrappers + hc._nonwrappers): - 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 register(plugin, name). To obtain the name - of an registered plugin use ``get_name(plugin)`` 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( - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" % - (hookimpl.plugin_name, hook.name)) - - for arg in hookimpl.argnames: - if arg not in hook.argnames: - raise PluginValidationError( - "Plugin %r\nhook %r\nargument %r not available\n" - "plugin definition: %s\n" - "available hookargs: %s" % - (hookimpl.plugin_name, hook.name, arg, - _formatdef(hookimpl.function), ", ".join(hook.argnames))) - - def check_pending(self): - """ Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise PluginValidationError""" - for name in self.hook.__dict__: - if name[0] != "_": - hook = getattr(self.hook, name) - if not hook.has_spec(): - for hookimpl in (hook._wrappers + hook._nonwrappers): - if not hookimpl.optionalhook: - raise PluginValidationError( - "unknown hook %r in plugin %r" % - (name, hookimpl.plugin)) - - def load_setuptools_entrypoints(self, entrypoint_name): - """ Load modules from querying the specified setuptools entrypoint name. - Return the number of loaded plugins. """ - from pkg_resources import (iter_entry_points, DistributionNotFound, - VersionConflict) - for ep in iter_entry_points(entrypoint_name): - # is the plugin registered or blocked? - if self.get_plugin(ep.name) or self.is_blocked(ep.name): - continue - try: - plugin = ep.load() - except DistributionNotFound: - continue - except VersionConflict as e: - raise PluginValidationError( - "Plugin %r could not be loaded: %s!" % (ep.name, e)) - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((plugin, ep.dist)) - return len(self._plugin_distinfo) - - 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:`_CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` object - which represents the result of the overall hook call. - """ - return _TracedHookExecution(self, before, after).undo - - def enable_tracing(self): - """ enable tracing of hook calls and return an undo function. """ - hooktrace = self.hook._trace - - 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.result) - hooktrace.root.indent -= 1 - - return self.add_hookcall_monitoring(before, after) - - def subset_hook_caller(self, name, remove_plugins): - """ Return a new _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._specmodule_or_class, - orig.spec_opts) - for hookimpl in (orig._wrappers + orig._nonwrappers): - 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 - - -class _MultiCall: - """ 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, specopts={}): - self.hook_impls = hook_impls - self.kwargs = kwargs - self.kwargs["__multicall__"] = self - self.specopts = specopts - - def execute(self): - all_kwargs = self.kwargs - self.results = results = [] - firstresult = self.specopts.get("firstresult") - - while self.hook_impls: - hook_impl = self.hook_impls.pop() - try: - args = [all_kwargs[argname] for argname in hook_impl.argnames] - except KeyError: - for argname in hook_impl.argnames: - if argname not in all_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.kwargs) - - -def varnames(func, startindex=None): - """ return argument name tuple 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 unless you are passing - an unbound method with Python3 (which has no supports for unbound methods) - """ - cache = getattr(func, "__dict__", {}) - try: - return cache["_varnames"] - except KeyError: - pass - if inspect.isclass(func): - try: - func = func.__init__ - except AttributeError: - return () - startindex = 1 - else: - if not inspect.isfunction(func) and not inspect.ismethod(func): - try: - func = getattr(func, '__call__', func) - except Exception: - return () - if startindex is None: - startindex = int(inspect.ismethod(func)) - - try: - rawcode = func.__code__ - except AttributeError: - return () - try: - x = rawcode.co_varnames[startindex:rawcode.co_argcount] - except AttributeError: - x = () - else: - defaults = func.__defaults__ - if defaults: - x = x[:-len(defaults)] - try: - cache["_varnames"] = x - except TypeError: - pass - return x - - -class _HookRelay: - """ hook holder object for performing 1:N hook calls where N is the number - of registered plugins. - - """ - - def __init__(self, trace): - self._trace = trace - - -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 - 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 hasattr(self, "_specmodule_or_class") - - def set_specification(self, specmodule_or_class, spec_opts): - assert not self.has_spec() - self._specmodule_or_class = specmodule_or_class - specfunc = getattr(specmodule_or_class, self.name) - argnames = varnames(specfunc, startindex=inspect.isclass(specmodule_or_class)) - assert "self" not in argnames # sanity check - self.argnames = ["__multicall__"] + list(argnames) - self.spec_opts = 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 wrappers[i] - return True - if remove(self._wrappers) is None: - if remove(self._nonwrappers) is None: - raise ValueError("plugin %r not found" % (plugin,)) - - def _add_hookimpl(self, hookimpl): - if hookimpl.hookwrapper: - methods = self._wrappers - else: - methods = self._nonwrappers - - if hookimpl.trylast: - methods.insert(0, hookimpl) - elif hookimpl.tryfirst: - methods.append(hookimpl) - else: - # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and methods[i].tryfirst: - i -= 1 - methods.insert(i + 1, hookimpl) - - def __repr__(self): - return "<_HookCaller %r>" % (self.name,) - - def __call__(self, **kwargs): - assert not self.is_historic() - return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_historic(self, proc=None, kwargs=None): - self._call_history.append((kwargs or {}, proc)) - # historizing hooks don't return results - self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - 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, "", method, opts) - self._add_hookimpl(hookimpl) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old - - def _maybe_apply_history(self, method): - if self.is_historic(): - for kwargs, proc in self._call_history: - res = self._hookexec(self, [method], kwargs) - if res and proc is not None: - proc(res[0]) - - -class HookImpl: - def __init__(self, plugin, plugin_name, function, hook_impl_opts): - self.function = function - self.argnames = varnames(self.function) - self.plugin = plugin - self.opts = hook_impl_opts - self.plugin_name = plugin_name - self.__dict__.update(hook_impl_opts) - - -class PluginValidationError(Exception): - """ plugin failed validation. """ - - -class HookCallError(Exception): - """ Hook was called wrongly. """ - - -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)) - ) diff --git a/setup.py b/setup.py index 792be6b41..e7fb194d2 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ def has_environment_marker_support(): def main(): - install_requires = ['py>=1.4.33', 'six>=1.10.0','setuptools'] # pluggy is vendored in _pytest.vendored_packages + install_requires = ['py>=1.4.33', 'six>=1.10.0','setuptools', 'pluggy==0.4.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6"'] = ['argparse', 'ordereddict']