diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 184d3b4..ae3d9ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,18 +19,16 @@ repos: rev: 5.6.4 hooks: - id: isort -- repo: https://github.com/ambv/black +- repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black args: [--safe] - language_version: python3.8 - repo: https://github.com/asottile/blacken-docs rev: v1.8.0 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] - language_version: python3.8 - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.6.0 hooks: @@ -50,4 +48,3 @@ repos: hooks: - id: flake8 additional_dependencies: ["flake8-bugbear == 20.1.4"] - language_version: python3.8 diff --git a/docs/changelog/1995.feature.rst b/docs/changelog/1995.feature.rst new file mode 100644 index 0000000..0019634 --- /dev/null +++ b/docs/changelog/1995.feature.rst @@ -0,0 +1,2 @@ +The python specification can now take one or more values, first found is used to create the virtual environment - by +:user:`gaborbernat`. diff --git a/setup.py b/setup.py index 8ba6e37..cddd1d3 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ if int(__version__.split(".")[0]) < 41: setup( use_scm_version={ "write_to": "src/virtualenv/version.py", - "write_to_template": 'from __future__ import unicode_literals;\n\n__version__ = "{version}"', + "write_to_template": 'from __future__ import unicode_literals\n\n__version__ = "{version}"\n', }, setup_requires=["setuptools_scm >= 2"], ) diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py index 4d57fa5..93e2c7b 100644 --- a/src/virtualenv/discovery/builtin.py +++ b/src/virtualenv/discovery/builtin.py @@ -15,7 +15,7 @@ from .py_spec import PythonSpec class Builtin(Discover): def __init__(self, options): super(Builtin, self).__init__(options) - self.python_spec = options.python + self.python_spec = options.python if options.python else [sys.executable] self.app_data = options.app_data @classmethod @@ -25,18 +25,25 @@ class Builtin(Discover): "--python", dest="python", metavar="py", - help="target interpreter for which to create a virtual (either absolute path or identifier string)", - default=sys.executable, + action="append", + default=[], + help="interpreter based on what to create environment (path/identifier) " + "- by default use the interpreter where the tool is installed - first found wins", ) def run(self): - return get_interpreter(self.python_spec, self.app_data) + for python_spec in self.python_spec: + result = get_interpreter(python_spec, self.app_data) + if result is not None: + return result + return None def __repr__(self): return ensure_str(self.__unicode__()) def __unicode__(self): - return "{} discover of python_spec={!r}".format(self.__class__.__name__, self.python_spec) + spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec + return "{} discover of python_spec={!r}".format(self.__class__.__name__, spec) def get_interpreter(key, app_data=None): diff --git a/tests/unit/discovery/test_discovery.py b/tests/unit/discovery/test_discovery.py index e8e9c4d..210498a 100644 --- a/tests/unit/discovery/test_discovery.py +++ b/tests/unit/discovery/test_discovery.py @@ -3,11 +3,12 @@ from __future__ import absolute_import, unicode_literals import logging import os import sys +from argparse import Namespace from uuid import uuid4 import pytest -from virtualenv.discovery.builtin import get_interpreter +from virtualenv.discovery.builtin import Builtin, get_interpreter from virtualenv.discovery.py_info import PythonInfo from virtualenv.info import fs_supports_symlink from virtualenv.util.path import Path @@ -53,3 +54,24 @@ def test_relative_path(tmp_path, session_app_data, monkeypatch): relative = str(sys_executable.relative_to(cwd)) result = get_interpreter(relative, session_app_data) assert result is not None + + +def test_discovery_fallback_fail(session_app_data, caplog): + caplog.set_level(logging.DEBUG) + builtin = Builtin(Namespace(app_data=session_app_data, python=["magic-one", "magic-two"])) + + result = builtin.run() + assert result is None + + assert "accepted" not in caplog.text + + +def test_discovery_fallback_ok(session_app_data, caplog): + caplog.set_level(logging.DEBUG) + builtin = Builtin(Namespace(app_data=session_app_data, python=["magic-one", sys.executable])) + + result = builtin.run() + assert result is not None, caplog.text + assert result.executable == sys.executable, caplog.text + + assert "accepted" in caplog.text