2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/850fb37c6062/ Changeset: 850fb37c6062 User: hpk42 Date: 2015-05-08 11:16:20+00:00 Summary: Backed out changeset cc1933175162 Affected #: 13 files
diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,8 +48,8 @@ # built documents. # # The short X.Y version. -release = "2.0" -version = "2.0.0" +release = "1.9" +version = "1.9.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -5,7 +5,7 @@ --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part -of a larger vision of easing the packaging, testing and release process +of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? @@ -21,7 +21,6 @@ * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. - Basic example ----------------- @@ -63,10 +62,10 @@ - test-tool agnostic: runs py.test, nose or unittests in a uniform manner -* :doc:`(new in 2.0) plugin system <plugins>` to modify tox execution with simple hooks. +* supports :ref:`using different / multiple PyPI index servers <multiindex>` * uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command + support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, @@ -75,11 +74,11 @@ * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ - (formerly known as Hudson) and helps you to avoid boilerplatish + (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and - is used for testing in the devpi_ system, a versatile pypi + is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** @@ -90,9 +89,6 @@ * **professionally** :doc:`supported <support>` -* supports :ref:`using different / multiple PyPI index servers <multiindex>` - - .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 doc/plugins.txt --- a/doc/plugins.txt +++ /dev/null @@ -1,69 +0,0 @@ -.. be in -*- rst -*- mode! - -tox plugins -=========== - -.. versionadded:: 2.0 - -With tox-2.0 a few aspects of tox running can be experimentally modified -by writing hook functions. We expect the list of hook function to grow -over time. - -writing a setuptools entrypoints plugin ---------------------------------------- - -If you have a ``tox_MYPLUGIN.py`` module you could use the following -rough ``setup.py`` to make it into a package which you can upload to the -Python packaging index:: - - # content of setup.py - from setuptools import setup - - if __name__ == "__main__": - setup( - name='tox-MYPLUGIN', - description='tox plugin decsription', - license="MIT license", - version='0.1', - py_modules=['tox_MYPLUGIN'], - entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, - install_requires=['tox>=2.0'], - ) - -You can then install the plugin to develop it via:: - - pip install -e . - -and later publish it. - -The ``entry_points`` part allows tox to see your plugin during startup. - - -Writing hook implementations ----------------------------- - -A plugin module needs can define one or more hook implementation functions:: - - from tox import hookimpl - - @hookimpl - def tox_addoption(parser): - # add your own command line options - - - @hookimpl - def tox_configure(config): - # post process tox configuration after cmdline/ini file have - # been parsed - -If you put this into a module and make it pypi-installable with the ``tox`` -entry point you'll get your code executed as part of a tox run. - - - -tox hook specifications ----------------------------- - -.. automodule:: tox.hookspecs - :members: - diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] if version < (2, 7): install_requires += ['argparse'] setup( diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,6 +6,7 @@ import tox import tox._config from tox._config import * # noqa +from tox._config import _split_env from tox._venv import VirtualEnv @@ -1560,16 +1561,31 @@ ]) -@pytest.mark.parametrize("cmdline,envlist", [ - ("-e py26", ['py26']), - ("-e py26,py33", ['py26', 'py33']), - ("-e py26,py26", ['py26', 'py26']), - ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) -]) -def test_env_spec(cmdline, envlist): - args = cmdline.split() - config = parseconfig(args) - assert config.envlist == envlist +class TestArgumentParser: + + def test_dash_e_single_1(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26'] + + def test_dash_e_single_2(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26,py33'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26', 'py33'] + + def test_dash_e_same(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26,py26'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26', 'py26'] + + def test_dash_e_combine(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] class TestCommandParser: diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,13 +3,11 @@ import pytest from tox.interpreters import * # noqa -from tox._config import get_plugin_manager @pytest.fixture def interpreters(): - pm = get_plugin_manager() - return Interpreters(hook=pm.hook) + return Interpreters() @pytest.mark.skipif("sys.platform != 'win32'") @@ -30,8 +28,8 @@ assert locate_via_py('3', '2') == sys.executable -def test_tox_get_python_executable(): - p = tox_get_python_executable(sys.executable) +def test_find_executable(): + p = find_executable(sys.executable) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -44,7 +42,7 @@ else: if not py.path.local.sysfind(name): continue - p = tox_get_python_executable(name) + p = find_executable(name) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -57,7 +55,7 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = tox_get_python_executable("qweqwe") + t = find_executable("qweqwe") assert t == "hello" diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -65,7 +65,7 @@ # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.envconfig.python_info.executable + assert interp == venv.envconfig._basepython_info.executable assert venv.path_config.check(exists=False) diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox.ini --- a/tox.ini +++ b/tox.ini @@ -22,9 +22,7 @@ deps = pytest-flakes>=0.2 pytest-pep8 -commands = - py.test --flakes -m flakes tox tests - py.test --pep8 -m pep8 tox tests +commands = py.test -x --flakes --pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,8 +1,6 @@ # __version__ = '2.0.0.dev1' -from .hookspecs import hookspec, hookimpl # noqa - class exception: class Error(Exception): diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -24,7 +24,7 @@ def main(args=None): try: - config = parseconfig(args) + config = parseconfig(args, 'tox') retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: @@ -551,7 +551,8 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) + self.report.line(" _basepython_info=%s" % + envconfig._basepython_info) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,10 +8,8 @@ import string import pkg_resources import itertools -import pluggy -import tox.interpreters -from tox import hookspecs +from tox.interpreters import Interpreters import py @@ -24,43 +22,20 @@ for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) -hookimpl = pluggy.HookimplMarker("tox") - -def get_plugin_manager(): - # initialize plugin manager - pm = pluggy.PluginManager("tox") - pm.add_hookspecs(hookspecs) - pm.register(tox._config) - pm.register(tox.interpreters) - pm.load_setuptools_entrypoints("tox") - pm.check_pending() - return pm - - -def parseconfig(args=None): +def parseconfig(args=None, pkg=None): """ :param list[str] args: Optional list of arguments. :type pkg: str :rtype: :class:`Config` :raise SystemExit: toxinit file is not found """ - - pm = get_plugin_manager() - if args is None: args = sys.argv[1:] - - # prepare command line options - parser = argparse.ArgumentParser(description=__doc__) - pm.hook.tox_addoption(parser=parser) - - # parse command line options - option = parser.parse_args(args) - interpreters = tox.interpreters.Interpreters(hook=pm.hook) - config = Config(pluginmanager=pm, option=option, interpreters=interpreters) - - # parse ini file + parser = prepare_parse(pkg) + opts = parser.parse_args(args) + config = Config() + config.option = opts basename = config.option.configfile if os.path.isabs(basename): inipath = py.path.local(basename) @@ -77,10 +52,6 @@ exn = sys.exc_info()[1] # Use stdout to match test expectations py.builtin.print_("ERROR: " + str(exn)) - - # post process config object - pm.hook.tox_configure(config=config) - return config @@ -92,8 +63,10 @@ class VersionAction(argparse.Action): def __call__(self, argparser, *args, **kwargs): - version = tox.__version__ - py.builtin.print_("%s imported from %s" % (version, tox.__file__)) + name = argparser.pkgname + mod = __import__(name) + version = mod.__version__ + py.builtin.print_("%s imported from %s" % (version, mod.__file__)) raise SystemExit(0) @@ -105,9 +78,10 @@ setattr(namespace, self.dest, 0) -@hookimpl -def tox_addoption(parser): +def prepare_parse(pkgname): + parser = argparse.ArgumentParser(description=__doc__,) # formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") @@ -179,12 +153,10 @@ class Config(object): - def __init__(self, pluginmanager, option, interpreters): + def __init__(self): self.envconfigs = {} self.invocationcwd = py.path.local() - self.interpreters = interpreters - self.pluginmanager = pluginmanager - self.option = option + self.interpreters = Interpreters() @property def homedir(self): @@ -220,14 +192,10 @@ def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( - info=self.python_info, + info=self._basepython_info, envdir=self.envdir) return x - @property - def python_info(self): - return self.config.interpreters.get_info(self.basepython) - def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: @@ -388,7 +356,7 @@ bp = next((default_factors[f] for f in factors if f in default_factors), sys.executable) vc.basepython = reader.getdefault(section, "basepython", bp) - + vc._basepython_info = config.interpreters.get_info(vc.basepython) reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -143,7 +143,7 @@ self.envconfig.deps, v) def _getliveconfig(self): - python = self.envconfig.python_info.executable + python = self.envconfig._basepython_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -2,14 +2,12 @@ import py import re import inspect -from tox import hookimpl class Interpreters: - def __init__(self, hook): + def __init__(self): self.name2executable = {} self.executable2info = {} - self.hook = hook def get_executable(self, name): """ return path object to the executable for the given @@ -20,9 +18,8 @@ try: return self.name2executable[name] except KeyError: - exe = self.hook.tox_get_python_executable(name=name) - self.name2executable[name] = exe - return exe + self.name2executable[name] = e = find_executable(name) + return e def get_info(self, name=None, executable=None): if name is None and executable is None: @@ -128,13 +125,31 @@ return "<executable not found for: %s>" % self.name if sys.platform != "win32": - @hookimpl - def tox_get_python_executable(name): + def find_executable(name): return py.path.local.sysfind(name) else: - @hookimpl - def tox_get_python_executable(name): + # Exceptions to the usual windows mapping + win32map = { + 'python': sys.executable, + 'jython': "c:\jython2.5.1\jython.bat", + } + + def locate_via_py(v_maj, v_min): + ver = "-%s.%s" % (v_maj, v_min) + script = "import sys; print(sys.executable)" + py_exe = py.path.local.sysfind('py') + if py_exe: + try: + exe = py_exe.sysexec(ver, '-c', script).strip() + except py.process.cmdexec.Error: + exe = None + if exe: + exe = py.path.local(exe) + if exe.check(): + return exe + + def find_executable(name): p = py.path.local.sysfind(name) if p: return p @@ -155,26 +170,6 @@ if m: return locate_via_py(*m.groups()) - # Exceptions to the usual windows mapping - win32map = { - 'python': sys.executable, - 'jython': "c:\jython2.5.1\jython.bat", - } - - def locate_via_py(v_maj, v_min): - ver = "-%s.%s" % (v_maj, v_min) - script = "import sys; print(sys.executable)" - py_exe = py.path.local.sysfind('py') - if py_exe: - try: - exe = py_exe.sysexec(ver, '-c', script).strip() - except py.process.cmdexec.Error: - exe = None - if exe: - exe = py.path.local(exe) - if exe.check(): - return exe - def pyinfo(): import sys https://bitbucket.org/hpk42/tox/commits/581c658b9b39/ Changeset: 581c658b9b39 Branch: pluggy User: hpk42 Date: 2015-05-08 11:18:23+00:00 Summary: introduce little plugin system which allows to add command line options, perform extra configuration and determine how python executables are found (see hookspec) Affected #: 14 files diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,8 +48,8 @@ # built documents. # # The short X.Y version. -release = "1.9" -version = "1.9.0" +release = "2.0" +version = "2.0.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -5,7 +5,7 @@ --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part -of a larger vision of easing the packaging, testing and release process +of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? @@ -21,6 +21,7 @@ * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. + Basic example ----------------- @@ -62,10 +63,10 @@ - test-tool agnostic: runs py.test, nose or unittests in a uniform manner -* supports :ref:`using different / multiple PyPI index servers <multiindex>` +* :doc:`(new in 2.0) plugin system <plugins>` to modify tox execution with simple hooks. * uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command + support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, @@ -74,11 +75,11 @@ * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ - (formerly known as Hudson) and helps you to avoid boilerplatish + (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and - is used for testing in the devpi_ system, a versatile pypi + is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** @@ -89,6 +90,9 @@ * **professionally** :doc:`supported <support>` +* supports :ref:`using different / multiple PyPI index servers <multiindex>` + + .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 doc/plugins.txt --- /dev/null +++ b/doc/plugins.txt @@ -0,0 +1,69 @@ +.. be in -*- rst -*- mode! + +tox plugins +=========== + +.. versionadded:: 2.0 + +With tox-2.0 a few aspects of tox running can be experimentally modified +by writing hook functions. We expect the list of hook function to grow +over time. + +writing a setuptools entrypoints plugin +--------------------------------------- + +If you have a ``tox_MYPLUGIN.py`` module you could use the following +rough ``setup.py`` to make it into a package which you can upload to the +Python packaging index:: + + # content of setup.py + from setuptools import setup + + if __name__ == "__main__": + setup( + name='tox-MYPLUGIN', + description='tox plugin decsription', + license="MIT license", + version='0.1', + py_modules=['tox_MYPLUGIN'], + entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, + install_requires=['tox>=2.0'], + ) + +You can then install the plugin to develop it via:: + + pip install -e . + +and later publish it. + +The ``entry_points`` part allows tox to see your plugin during startup. + + +Writing hook implementations +---------------------------- + +A plugin module needs can define one or more hook implementation functions:: + + from tox import hookimpl + + @hookimpl + def tox_addoption(parser): + # add your own command line options + + + @hookimpl + def tox_configure(config): + # post process tox configuration after cmdline/ini file have + # been parsed + +If you put this into a module and make it pypi-installable with the ``tox`` +entry point you'll get your code executed as part of a tox run. + + + +tox hook specifications +---------------------------- + +.. automodule:: tox.hookspecs + :members: + diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] if version < (2, 7): install_requires += ['argparse'] setup( diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,6 @@ import tox import tox._config from tox._config import * # noqa -from tox._config import _split_env from tox._venv import VirtualEnv @@ -1561,31 +1560,16 @@ ]) -class TestArgumentParser: - - def test_dash_e_single_1(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26'] - - def test_dash_e_single_2(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py33'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py33'] - - def test_dash_e_same(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py26'] - - def test_dash_e_combine(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] +@pytest.mark.parametrize("cmdline,envlist", [ + ("-e py26", ['py26']), + ("-e py26,py33", ['py26', 'py33']), + ("-e py26,py26", ['py26', 'py26']), + ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) +]) +def test_env_spec(cmdline, envlist): + args = cmdline.split() + config = parseconfig(args) + assert config.envlist == envlist class TestCommandParser: diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,11 +3,13 @@ import pytest from tox.interpreters import * # noqa +from tox._config import get_plugin_manager @pytest.fixture def interpreters(): - return Interpreters() + pm = get_plugin_manager() + return Interpreters(hook=pm.hook) @pytest.mark.skipif("sys.platform != 'win32'") @@ -28,8 +30,8 @@ assert locate_via_py('3', '2') == sys.executable -def test_find_executable(): - p = find_executable(sys.executable) +def test_tox_get_python_executable(): + p = tox_get_python_executable(sys.executable) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -42,7 +44,7 @@ else: if not py.path.local.sysfind(name): continue - p = find_executable(name) + p = tox_get_python_executable(name) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -55,7 +57,7 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = find_executable("qweqwe") + t = tox_get_python_executable("qweqwe") assert t == "hello" diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -65,7 +65,7 @@ # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.envconfig._basepython_info.executable + assert interp == venv.envconfig.python_info.executable assert venv.path_config.check(exists=False) diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox.ini --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,9 @@ deps = pytest-flakes>=0.2 pytest-pep8 -commands = py.test -x --flakes --pep8 tox tests +commands = + py.test --flakes -m flakes tox tests + py.test --pep8 -m pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,6 +1,8 @@ # __version__ = '2.0.0.dev1' +from .hookspecs import hookspec, hookimpl # noqa + class exception: class Error(Exception): diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -24,7 +24,7 @@ def main(args=None): try: - config = parseconfig(args, 'tox') + config = parseconfig(args) retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: @@ -551,8 +551,7 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" _basepython_info=%s" % - envconfig._basepython_info) + self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,8 +8,10 @@ import string import pkg_resources import itertools +import pluggy -from tox.interpreters import Interpreters +import tox.interpreters +from tox import hookspecs import py @@ -22,20 +24,43 @@ for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) +hookimpl = pluggy.HookimplMarker("tox") -def parseconfig(args=None, pkg=None): + +def get_plugin_manager(): + # initialize plugin manager + pm = pluggy.PluginManager("tox") + pm.add_hookspecs(hookspecs) + pm.register(tox._config) + pm.register(tox.interpreters) + pm.load_setuptools_entrypoints("tox") + pm.check_pending() + return pm + + +def parseconfig(args=None): """ :param list[str] args: Optional list of arguments. :type pkg: str :rtype: :class:`Config` :raise SystemExit: toxinit file is not found """ + + pm = get_plugin_manager() + if args is None: args = sys.argv[1:] - parser = prepare_parse(pkg) - opts = parser.parse_args(args) - config = Config() - config.option = opts + + # prepare command line options + parser = argparse.ArgumentParser(description=__doc__) + pm.hook.tox_addoption(parser=parser) + + # parse command line options + option = parser.parse_args(args) + interpreters = tox.interpreters.Interpreters(hook=pm.hook) + config = Config(pluginmanager=pm, option=option, interpreters=interpreters) + + # parse ini file basename = config.option.configfile if os.path.isabs(basename): inipath = py.path.local(basename) @@ -52,6 +77,10 @@ exn = sys.exc_info()[1] # Use stdout to match test expectations py.builtin.print_("ERROR: " + str(exn)) + + # post process config object + pm.hook.tox_configure(config=config) + return config @@ -63,10 +92,8 @@ class VersionAction(argparse.Action): def __call__(self, argparser, *args, **kwargs): - name = argparser.pkgname - mod = __import__(name) - version = mod.__version__ - py.builtin.print_("%s imported from %s" % (version, mod.__file__)) + version = tox.__version__ + py.builtin.print_("%s imported from %s" % (version, tox.__file__)) raise SystemExit(0) @@ -78,10 +105,9 @@ setattr(namespace, self.dest, 0) -def prepare_parse(pkgname): - parser = argparse.ArgumentParser(description=__doc__,) +@hookimpl +def tox_addoption(parser): # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") @@ -153,10 +179,12 @@ class Config(object): - def __init__(self): + def __init__(self, pluginmanager, option, interpreters): self.envconfigs = {} self.invocationcwd = py.path.local() - self.interpreters = Interpreters() + self.interpreters = interpreters + self.pluginmanager = pluginmanager + self.option = option @property def homedir(self): @@ -192,10 +220,14 @@ def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( - info=self._basepython_info, + info=self.python_info, envdir=self.envdir) return x + @property + def python_info(self): + return self.config.interpreters.get_info(self.basepython) + def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: @@ -356,7 +388,7 @@ bp = next((default_factors[f] for f in factors if f in default_factors), sys.executable) vc.basepython = reader.getdefault(section, "basepython", bp) - vc._basepython_info = config.interpreters.get_info(vc.basepython) + reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -143,7 +143,7 @@ self.envconfig.deps, v) def _getliveconfig(self): - python = self.envconfig._basepython_info.executable + python = self.envconfig.python_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/hookspecs.py --- /dev/null +++ b/tox/hookspecs.py @@ -0,0 +1,28 @@ +""" Hook specifications for tox. + +""" + +from pluggy import HookspecMarker, HookimplMarker + +hookspec = HookspecMarker("tox") +hookimpl = HookimplMarker("tox") + + +@hookspec +def tox_addoption(parser): + """ add command line options to the argparse-style parser object.""" + + +@hookspec +def tox_configure(config): + """ called after command line options have been parsed and the ini-file has + been read. Please be aware that the config object layout may change as its + API was not designed yet wrt to providing stability (it was an internal + thing purely before tox-2.0). """ + + +@hookspec(firstresult=True) +def tox_get_python_executable(name): + """ return a python executable for the given python base name. + The first plugin/hook which returns an executable path will determine it. + """ diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -2,12 +2,14 @@ import py import re import inspect +from tox import hookimpl class Interpreters: - def __init__(self): + def __init__(self, hook): self.name2executable = {} self.executable2info = {} + self.hook = hook def get_executable(self, name): """ return path object to the executable for the given @@ -18,8 +20,9 @@ try: return self.name2executable[name] except KeyError: - self.name2executable[name] = e = find_executable(name) - return e + exe = self.hook.tox_get_python_executable(name=name) + self.name2executable[name] = exe + return exe def get_info(self, name=None, executable=None): if name is None and executable is None: @@ -125,10 +128,33 @@ return "<executable not found for: %s>" % self.name if sys.platform != "win32": - def find_executable(name): + @hookimpl + def tox_get_python_executable(name): return py.path.local.sysfind(name) else: + @hookimpl + def tox_get_python_executable(name): + p = py.path.local.sysfind(name) + if p: + return p + actual = None + # Is this a standard PythonX.Y name? + m = re.match(r"python(\d)\.(\d)", name) + if m: + # The standard names are in predictable places. + actual = r"c:\python%s%s\python.exe" % m.groups() + if not actual: + actual = win32map.get(name, None) + if actual: + actual = py.path.local(actual) + if actual.check(): + return actual + # The standard executables can be found as a last resort via the + # Python launcher py.exe + if m: + return locate_via_py(*m.groups()) + # Exceptions to the usual windows mapping win32map = { 'python': sys.executable, @@ -149,27 +175,6 @@ if exe.check(): return exe - def find_executable(name): - p = py.path.local.sysfind(name) - if p: - return p - actual = None - # Is this a standard PythonX.Y name? - m = re.match(r"python(\d)\.(\d)", name) - if m: - # The standard names are in predictable places. - actual = r"c:\python%s%s\python.exe" % m.groups() - if not actual: - actual = win32map.get(name, None) - if actual: - actual = py.path.local(actual) - if actual.check(): - return actual - # The standard executables can be found as a last resort via the - # Python launcher py.exe - if m: - return locate_via_py(*m.groups()) - def pyinfo(): import sys Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit