diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 87c5232f4be..05777d0c8de 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -102,6 +102,15 @@ Defaults to one of the following directories: - Windows: `C:\Users\\AppData\Local\pypoetry\Cache` - Unix: `~/.cache/pypoetry` +### `installer.parallel`: boolean + +Use parallel execution when using the new (`>=1.1.0`) installer. +Defaults to `true`. + +!!!note: + This configuration will be ignored, and parallel execution disabled when running + Python 2.7 under Windows. + ### `virtualenvs.create`: boolean Create a new virtual environment if one doesn't already exist. diff --git a/poetry/config/config.py b/poetry/config/config.py index be585575c05..e8c4f0b32fa 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -38,6 +38,7 @@ class Config(object): "path": os.path.join("{cache-dir}", "virtualenvs"), }, "experimental": {"new-installer": True}, + "installer": {"parallel": True}, } def __init__( @@ -131,14 +132,22 @@ def process(self, value): # type: (Any) -> Any return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) def _get_validator(self, name): # type: (str) -> Callable - if name in {"virtualenvs.create", "virtualenvs.in-project"}: + if name in { + "virtualenvs.create", + "virtualenvs.in-project", + "installer.parallel", + }: return boolean_validator if name == "virtualenvs.path": return str def _get_normalizer(self, name): # type: (str) -> Callable - if name in {"virtualenvs.create", "virtualenvs.in-project"}: + if name in { + "virtualenvs.create", + "virtualenvs.in-project", + "installer.parallel", + }: return boolean_normalizer if name == "virtualenvs.path": diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 551876912c2..f410971f11f 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -63,6 +63,7 @@ def unique_config_values(self): boolean_normalizer, True, ), + "installer.parallel": (boolean_validator, boolean_normalizer, True,), } return unique_config_values diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 9effaf3ebeb..9f3bca6baa3 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -32,7 +32,7 @@ class Executor(object): - def __init__(self, env, pool, config, io, parallel=True): + def __init__(self, env, pool, config, io, parallel=None): self._env = env self._io = io self._dry_run = False @@ -42,6 +42,9 @@ def __init__(self, env, pool, config, io, parallel=True): self._chef = Chef(config, self._env) self._chooser = Chooser(pool, self._env) + if parallel is None: + parallel = config.get("installer.parallel", True) + if parallel and not (PY2 and WINDOWS): # This should be directly handled by ThreadPoolExecutor # however, on some systems the number of CPUs cannot be determined diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 07373ade0b4..4bd0cd048da 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -1,16 +1,29 @@ import os +import pytest -def test_config_get_default_value(config): - assert config.get("virtualenvs.create") is True + +@pytest.mark.parametrize( + ("name", "value"), [("installer.parallel", True), ("virtualenvs.create", True)] +) +def test_config_get_default_value(config, name, value): + assert config.get(name) is value def test_config_get_processes_depended_on_values(config): assert os.path.join("/foo", "virtualenvs") == config.get("virtualenvs.path") -def test_config_get_from_environment_variable(config, environ): - assert config.get("virtualenvs.create") - - os.environ["POETRY_VIRTUALENVS_CREATE"] = "false" - assert not config.get("virtualenvs.create") +@pytest.mark.parametrize( + ("name", "env_value", "value"), + [ + ("installer.parallel", "true", True), + ("installer.parallel", "false", False), + ("virtualenvs.create", "true", True), + ("virtualenvs.create", "false", False), + ], +) +def test_config_get_from_environment_variable(config, environ, name, env_value, value): + env_var = "POETRY_{}".format("_".join(k.upper() for k in name.split("."))) + os.environ[env_var] = env_value + assert config.get(name) is value diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 4bf102073d5..9c7596c354b 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -5,6 +5,8 @@ from poetry.config.config_source import ConfigSource from poetry.factory import Factory +from poetry.utils._compat import PY2 +from poetry.utils._compat import WINDOWS @pytest.fixture() @@ -17,6 +19,7 @@ def test_list_displays_default_value_if_not_set(tester, config): expected = """cache-dir = "/foo" experimental.new-installer = true +installer.parallel = true virtualenvs.create = true virtualenvs.in-project = null virtualenvs.path = {path} # /foo{sep}virtualenvs @@ -34,6 +37,7 @@ def test_list_displays_set_get_setting(tester, config): expected = """cache-dir = "/foo" experimental.new-installer = true +installer.parallel = true virtualenvs.create = false virtualenvs.in-project = null virtualenvs.path = {path} # /foo{sep}virtualenvs @@ -73,6 +77,7 @@ def test_list_displays_set_get_local_setting(tester, config): expected = """cache-dir = "/foo" experimental.new-installer = true +installer.parallel = true virtualenvs.create = false virtualenvs.in-project = null virtualenvs.path = {path} # /foo{sep}virtualenvs @@ -108,3 +113,25 @@ def test_set_cert(tester, auth_config_source, mocker): tester.execute("certificates.foo.cert path/to/ca.pem") assert "path/to/ca.pem" == auth_config_source.config["certificates"]["foo"]["cert"] + + +def test_config_installer_parallel(tester, command_tester_factory): + serial_enforced = PY2 and WINDOWS + + tester.execute("--local installer.parallel") + assert tester.io.fetch_output().strip() == "true" + + workers = command_tester_factory( + "install" + )._command._installer._executor._max_workers + assert workers > 1 or (serial_enforced and workers == 1) + + tester.io.clear_output() + tester.execute("--local installer.parallel false") + tester.execute("--local installer.parallel") + assert tester.io.fetch_output().strip() == "false" + + workers = command_tester_factory( + "install" + )._command._installer._executor._max_workers + assert workers == 1