Commit 7f5acc3f by Sébastien Eustace Committed by GitHub

Improve environment management (#1477)

* Refactor the environment management code

* Improve executable selection when current Python is incompatible
parent 92c460ef
...@@ -103,7 +103,7 @@ class DebugResolveCommand(InitCommand): ...@@ -103,7 +103,7 @@ class DebugResolveCommand(InitCommand):
return 0 return 0
env = EnvManager(self.poetry.config).get(self.poetry.file.parent) env = EnvManager(self.poetry).get()
current_python_version = parse_constraint( current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info) ".".join(str(v) for v in env.version_info)
) )
......
...@@ -13,8 +13,7 @@ class EnvInfoCommand(Command): ...@@ -13,8 +13,7 @@ class EnvInfoCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry env = EnvManager(self.poetry).get()
env = EnvManager(poetry.config).get(cwd=poetry.file.parent)
if self.option("path"): if self.option("path"):
if not env.is_venv(): if not env.is_venv():
......
...@@ -13,11 +13,10 @@ class EnvListCommand(Command): ...@@ -13,11 +13,10 @@ class EnvListCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry manager = EnvManager(self.poetry)
manager = EnvManager(poetry.config) current_env = manager.get()
current_env = manager.get(self.poetry.file.parent)
for venv in manager.list(self.poetry.file.parent): for venv in manager.list():
name = venv.path.name name = venv.path.name
if self.option("full-path"): if self.option("full-path"):
name = str(venv.path) name = str(venv.path)
......
...@@ -15,8 +15,7 @@ class EnvRemoveCommand(Command): ...@@ -15,8 +15,7 @@ class EnvRemoveCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry manager = EnvManager(self.poetry)
manager = EnvManager(poetry.config) venv = manager.remove(self.argument("python"))
venv = manager.remove(self.argument("python"), poetry.file.parent)
self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path)) self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path))
...@@ -13,14 +13,13 @@ class EnvUseCommand(Command): ...@@ -13,14 +13,13 @@ class EnvUseCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry manager = EnvManager(self.poetry)
manager = EnvManager(poetry.config)
if self.argument("python") == "system": if self.argument("python") == "system":
manager.deactivate(poetry.file.parent, self._io) manager.deactivate(self._io)
return return
env = manager.activate(self.argument("python"), poetry.file.parent, self._io) env = manager.activate(self.argument("python"), self._io)
self.line("Using virtualenv: <comment>{}</>".format(env.path)) self.line("Using virtualenv: <comment>{}</>".format(env.path))
...@@ -3,6 +3,7 @@ from __future__ import unicode_literals ...@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os import os
import re import re
import sys
from typing import Dict from typing import Dict
from typing import List from typing import List
...@@ -15,7 +16,6 @@ from tomlkit import inline_table ...@@ -15,7 +16,6 @@ from tomlkit import inline_table
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import OrderedDict from poetry.utils._compat import OrderedDict
from poetry.utils._compat import urlparse from poetry.utils._compat import urlparse
from poetry.utils.helpers import temporary_directory
from .command import Command from .command import Command
from .env_command import EnvCommand from .env_command import EnvCommand
...@@ -63,7 +63,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -63,7 +63,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
def handle(self): def handle(self):
from poetry.layouts import layout from poetry.layouts import layout
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import EnvManager from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig from poetry.vcs.git import GitConfig
if (Path.cwd() / "pyproject.toml").exists(): if (Path.cwd() / "pyproject.toml").exists():
...@@ -126,7 +126,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -126,7 +126,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
question.set_validator(self._validate_license) question.set_validator(self._validate_license)
license = self.ask(question) license = self.ask(question)
current_env = EnvManager().get(Path.cwd()) current_env = SystemEnv(Path(sys.executable))
default_python = "^{}".format( default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2]) ".".join(str(v) for v in current_env.version_info[:2])
) )
......
...@@ -28,10 +28,7 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -28,10 +28,7 @@ class ApplicationConfig(BaseApplicationConfig):
self.add_event_listener(ConsoleEvents.PRE_HANDLE.value, self.set_env) self.add_event_listener(ConsoleEvents.PRE_HANDLE.value, self.set_env)
def register_command_loggers( def register_command_loggers(
self, self, event, event_name, _ # type: PreHandleEvent # type: str
event, # type: PreHandleEvent
event_name, # type: str
_,
): # type: (...) -> None ): # type: (...) -> None
command = event.command.config.handler command = event.command.config.handler
if not isinstance(command, Command): if not isinstance(command, Command):
...@@ -70,25 +67,8 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -70,25 +67,8 @@ class ApplicationConfig(BaseApplicationConfig):
io = event.io io = event.io
poetry = command.poetry poetry = command.poetry
env_manager = EnvManager(poetry.config) env_manager = EnvManager(poetry)
env = env_manager.create_venv(io)
# Checking compatibility of the current environment with
# the python dependency specified in pyproject.toml
current_env = env_manager.get(poetry.file.parent)
supported_python = poetry.package.python_constraint
current_python = parse_constraint(
".".join(str(v) for v in current_env.version_info[:3])
)
if not supported_python.allows(current_python):
raise RuntimeError(
"The current Python version ({}) is not supported by the project ({})\n"
"Please activate a compatible Python version.".format(
current_python, poetry.package.python_versions
)
)
env = env_manager.create_venv(poetry.file.parent, io, poetry.package.name)
if env.is_venv() and io.is_verbose(): if env.is_venv() and io.is_verbose():
io.write_line("Using virtualenv: <comment>{}</>".format(env.path)) io.write_line("Using virtualenv: <comment>{}</>".format(env.path))
......
...@@ -27,7 +27,7 @@ AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+? ...@@ -27,7 +27,7 @@ AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?
class Package(object): class Package(object):
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"} AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8"}
def __init__(self, name, version, pretty_version=None): def __init__(self, name, version, pretty_version=None):
""" """
......
...@@ -38,6 +38,7 @@ from poetry.utils.helpers import safe_rmtree ...@@ -38,6 +38,7 @@ from poetry.utils.helpers import safe_rmtree
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.env import VirtualEnv
from poetry.utils.inspector import Inspector from poetry.utils.inspector import Inspector
from poetry.utils.setup_reader import SetupReader from poetry.utils.setup_reader import SetupReader
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -326,9 +327,10 @@ class Provider: ...@@ -326,9 +327,10 @@ class Provider:
os.chdir(str(directory)) os.chdir(str(directory))
try: try:
cwd = directory with temporary_directory() as tmp_dir:
venv = EnvManager().get(cwd) EnvManager.build_venv(tmp_dir)
venv.run("python", "setup.py", "egg_info") venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir))
venv.run("python", "setup.py", "egg_info")
except EnvCommandError: except EnvCommandError:
result = SetupReader.read_from_directory(directory) result = SetupReader.read_from_directory(directory)
if not result["name"]: if not result["name"]:
......
import collections
import os import os
import re import re
import shutil import shutil
import stat import stat
import tempfile import tempfile
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
from contextlib import contextmanager from contextlib import contextmanager
from typing import List from typing import List
from typing import Optional from typing import Optional
...@@ -144,11 +148,7 @@ def safe_rmtree(path): ...@@ -144,11 +148,7 @@ def safe_rmtree(path):
def merge_dicts(d1, d2): def merge_dicts(d1, d2):
for k, v in d2.items(): for k, v in d2.items():
if ( if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping):
k in d1
and isinstance(d1[k], dict)
and isinstance(d2[k], collections.Mapping)
):
merge_dicts(d1[k], d2[k]) merge_dicts(d1[k], d2[k])
else: else:
d1[k] = d2[k] d1[k] = d2[k]
...@@ -11,7 +11,7 @@ def test_none_activated(app, tmp_dir): ...@@ -11,7 +11,7 @@ def test_none_activated(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
...@@ -34,7 +34,7 @@ def test_activated(app, tmp_dir): ...@@ -34,7 +34,7 @@ def test_activated(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......
...@@ -11,7 +11,7 @@ def test_remove_by_python_version(app, tmp_dir, mocker): ...@@ -11,7 +11,7 @@ def test_remove_by_python_version(app, tmp_dir, mocker):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
...@@ -39,7 +39,7 @@ def test_remove_by_name(app, tmp_dir): ...@@ -39,7 +39,7 @@ def test_remove_by_name(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......
...@@ -57,7 +57,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m ...@@ -57,7 +57,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m
tester.execute("3.7") tester.execute("3.7")
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
m.assert_called_with( m.assert_called_with(
...@@ -88,7 +88,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( ...@@ -88,7 +88,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
os.environ["VIRTUAL_ENV"] = "/environment/prefix" os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
current_python = sys.version_info[:3] current_python = sys.version_info[:3]
python_minor = ".".join(str(v) for v in current_python[:2]) python_minor = ".".join(str(v) for v in current_python[:2])
...@@ -130,7 +130,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var( ...@@ -130,7 +130,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var(
os.environ["VIRTUAL_ENV"] = "/environment/prefix" os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
current_python = sys.version_info[:3] current_python = sys.version_info[:3]
python_minor = ".".join(str(v) for v in current_python[:2]) python_minor = ".".join(str(v) for v in current_python[:2])
......
...@@ -88,6 +88,7 @@ def test_get_metadata_content(): ...@@ -88,6 +88,7 @@ def test_get_metadata_content():
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
] ]
......
...@@ -216,6 +216,7 @@ Classifier: License :: OSI Approved :: MIT License ...@@ -216,6 +216,7 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time Provides-Extra: time
...@@ -318,6 +319,7 @@ Classifier: License :: OSI Approved :: MIT License ...@@ -318,6 +319,7 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time Provides-Extra: time
......
...@@ -94,6 +94,7 @@ Classifier: License :: OSI Approved :: MIT License ...@@ -94,6 +94,7 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time Provides-Extra: time
......
...@@ -118,7 +118,10 @@ def test_search_for_vcs_read_setup_with_extras(provider, mocker): ...@@ -118,7 +118,10 @@ def test_search_for_vcs_read_setup_with_extras(provider, mocker):
def test_search_for_vcs_read_setup_raises_error_if_no_version(provider, mocker): def test_search_for_vcs_read_setup_raises_error_if_no_version(provider, mocker):
mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) mocker.patch(
"poetry.utils.env.VirtualEnv.run",
side_effect=EnvCommandError(CalledProcessError(1, "python", output="")),
)
dependency = VCSDependency("demo", "git", "https://github.com/demo/no-version.git") dependency = VCSDependency("demo", "git", "https://github.com/demo/no-version.git")
......
...@@ -107,6 +107,7 @@ def test_create_poetry(): ...@@ -107,6 +107,7 @@ def test_create_poetry():
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
] ]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment