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,8 +327,9 @@ class Provider: ...@@ -326,8 +327,9 @@ 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 = VirtualEnv(Path(tmp_dir), Path(tmp_dir))
venv.run("python", "setup.py", "egg_info") venv.run("python", "setup.py", "egg_info")
except EnvCommandError: except EnvCommandError:
result = SetupReader.read_from_directory(directory) result = SetupReader.read_from_directory(directory)
......
...@@ -20,8 +20,9 @@ from typing import Tuple ...@@ -20,8 +20,9 @@ from typing import Tuple
from clikit.api.io import IO from clikit.api.io import IO
from poetry.config.config import Config
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.poetry import Poetry
from poetry.semver import parse_constraint
from poetry.semver.version import Version from poetry.semver.version import Version
from poetry.utils._compat import CalledProcessError from poetry.utils._compat import CalledProcessError
from poetry.utils._compat import Path from poetry.utils._compat import Path
...@@ -127,6 +128,26 @@ class EnvCommandError(EnvError): ...@@ -127,6 +128,26 @@ class EnvCommandError(EnvError):
super(EnvCommandError, self).__init__(message) super(EnvCommandError, self).__init__(message)
class NoCompatiblePythonVersionFound(EnvError):
def __init__(self, expected, given=None):
if given:
message = (
"The specified Python version ({}) "
"is not supported by the project ({}).\n"
"Please choose a compatible version "
"or loosen the python constraint specified "
"in the pyproject.toml file.".format(given, expected)
)
else:
message = (
"Poetry was unable to find a compatible version. "
"If you have one, you can explicitly use it "
'via the "env use" command.'
)
super(NoCompatiblePythonVersionFound, self).__init__(message)
class EnvManager(object): class EnvManager(object):
""" """
Environments manager Environments manager
...@@ -136,19 +157,18 @@ class EnvManager(object): ...@@ -136,19 +157,18 @@ class EnvManager(object):
ENVS_FILE = "envs.toml" ENVS_FILE = "envs.toml"
def __init__(self, config=None): # type: (Config) -> None def __init__(self, poetry): # type: (Poetry) -> None
if config is None: self._poetry = poetry
config = Config()
self._config = config def activate(self, python, io): # type: (str, IO) -> Env
venv_path = self._poetry.config.get("virtualenvs.path")
def activate(self, python, cwd, io): # type: (str, Optional[Path], IO) -> Env
venv_path = self._config.get("virtualenvs.path")
if venv_path is None: if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs" venv_path = Path(CACHE_DIR) / "virtualenvs"
else: else:
venv_path = Path(venv_path) venv_path = Path(venv_path)
cwd = self._poetry.file.parent
envs_file = TomlFile(venv_path / self.ENVS_FILE) envs_file = TomlFile(venv_path / self.ENVS_FILE)
try: try:
...@@ -182,7 +202,7 @@ class EnvManager(object): ...@@ -182,7 +202,7 @@ class EnvManager(object):
create = False create = False
envs = tomlkit.document() envs = tomlkit.document()
base_env_name = self.generate_env_name(cwd.name, str(cwd)) base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if envs_file.exists(): if envs_file.exists():
envs = envs_file.read() envs = envs_file.read()
current_env = envs.get(base_env_name) current_env = envs.get(base_env_name)
...@@ -211,23 +231,23 @@ class EnvManager(object): ...@@ -211,23 +231,23 @@ class EnvManager(object):
if patch != current_patch: if patch != current_patch:
create = True create = True
self.create_venv(cwd, io, executable=python, force=create) self.create_venv(io, executable=python, force=create)
# Activate # Activate
envs[base_env_name] = {"minor": minor, "patch": patch} envs[base_env_name] = {"minor": minor, "patch": patch}
envs_file.write(envs) envs_file.write(envs)
return self.get(cwd, reload=True) return self.get(reload=True)
def deactivate(self, cwd, io): # type: (Optional[Path], IO) -> None def deactivate(self, io): # type: (IO) -> None
venv_path = self._config.get("virtualenvs.path") venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None: if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs" venv_path = Path(CACHE_DIR) / "virtualenvs"
else: else:
venv_path = Path(venv_path) venv_path = Path(venv_path)
name = cwd.name name = self._poetry.package.name
name = self.generate_env_name(name, str(cwd)) name = self.generate_env_name(name, str(self._poetry.file.parent))
envs_file = TomlFile(venv_path / self.ENVS_FILE) envs_file = TomlFile(venv_path / self.ENVS_FILE)
if envs_file.exists(): if envs_file.exists():
...@@ -243,21 +263,22 @@ class EnvManager(object): ...@@ -243,21 +263,22 @@ class EnvManager(object):
envs_file.write(envs) envs_file.write(envs)
def get(self, cwd, reload=False): # type: (Path, bool) -> Env def get(self, reload=False): # type: (bool) -> Env
if self._env is not None and not reload: if self._env is not None and not reload:
return self._env return self._env
python_minor = ".".join([str(v) for v in sys.version_info[:2]]) python_minor = ".".join([str(v) for v in sys.version_info[:2]])
venv_path = self._config.get("virtualenvs.path") venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None: if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs" venv_path = Path(CACHE_DIR) / "virtualenvs"
else: else:
venv_path = Path(venv_path) venv_path = Path(venv_path)
cwd = self._poetry.file.parent
envs_file = TomlFile(venv_path / self.ENVS_FILE) envs_file = TomlFile(venv_path / self.ENVS_FILE)
env = None env = None
base_env_name = self.generate_env_name(cwd.name, str(cwd)) base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if envs_file.exists(): if envs_file.exists():
envs = envs_file.read() envs = envs_file.read()
env = envs.get(base_env_name) env = envs.get(base_env_name)
...@@ -274,12 +295,12 @@ class EnvManager(object): ...@@ -274,12 +295,12 @@ class EnvManager(object):
return VirtualEnv(venv) return VirtualEnv(venv)
create_venv = self._config.get("virtualenvs.create", True) create_venv = self._poetry.config.get("virtualenvs.create", True)
if not create_venv: if not create_venv:
return SystemEnv(Path(sys.prefix)) return SystemEnv(Path(sys.prefix))
venv_path = self._config.get("virtualenvs.path") venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None: if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs" venv_path = Path(CACHE_DIR) / "virtualenvs"
else: else:
...@@ -303,13 +324,13 @@ class EnvManager(object): ...@@ -303,13 +324,13 @@ class EnvManager(object):
return VirtualEnv(prefix, base_prefix) return VirtualEnv(prefix, base_prefix)
def list(self, cwd, name=None): # type: (Path, Optional[str]) -> List[VirtualEnv] def list(self, name=None): # type: (Optional[str]) -> List[VirtualEnv]
if name is None: if name is None:
name = cwd.name name = self._poetry.package.name
venv_name = self.generate_env_name(name, str(cwd)) venv_name = self.generate_env_name(name, str(self._poetry.file.parent))
venv_path = self._config.get("virtualenvs.path") venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None: if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs" venv_path = Path(CACHE_DIR) / "virtualenvs"
else: else:
...@@ -320,18 +341,19 @@ class EnvManager(object): ...@@ -320,18 +341,19 @@ class EnvManager(object):
for p in sorted(venv_path.glob("{}-py*".format(venv_name))) for p in sorted(venv_path.glob("{}-py*".format(venv_name)))
] ]
def remove(self, python, cwd): # type: (str, Optional[Path]) -> Env def remove(self, python): # type: (str) -> Env
venv_path = self._config.get("virtualenvs.path") venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None: if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs" venv_path = Path(CACHE_DIR) / "virtualenvs"
else: else:
venv_path = Path(venv_path) venv_path = Path(venv_path)
cwd = self._poetry.file.parent
envs_file = TomlFile(venv_path / self.ENVS_FILE) envs_file = TomlFile(venv_path / self.ENVS_FILE)
base_env_name = self.generate_env_name(cwd.name, str(cwd)) base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if python.startswith(base_env_name): if python.startswith(base_env_name):
venvs = self.list(cwd) venvs = self.list()
for venv in venvs: for venv in venvs:
if venv.path.name == python: if venv.path.name == python:
# Exact virtualenv name # Exact virtualenv name
...@@ -413,20 +435,21 @@ class EnvManager(object): ...@@ -413,20 +435,21 @@ class EnvManager(object):
return VirtualEnv(venv) return VirtualEnv(venv)
def create_venv( def create_venv(
self, cwd, io, name=None, executable=None, force=False self, io, name=None, executable=None, force=False
): # type: (Path, IO, Optional[str], Optional[str], bool) -> Env ): # type: (IO, Optional[str], Optional[str], bool) -> Env
if self._env is not None and not force: if self._env is not None and not force:
return self._env return self._env
env = self.get(cwd, reload=True) cwd = self._poetry.file.parent
env = self.get(reload=True)
if env.is_venv() and not force: if env.is_venv() and not force:
# Already inside a virtualenv. # Already inside a virtualenv.
return env return env
create_venv = self._config.get("virtualenvs.create") create_venv = self._poetry.config.get("virtualenvs.create")
root_venv = self._config.get("virtualenvs.in-project") root_venv = self._poetry.config.get("virtualenvs.in-project")
venv_path = self._config.get("virtualenvs.path") venv_path = self._poetry.config.get("virtualenvs.path")
if root_venv: if root_venv:
venv_path = cwd / ".venv" venv_path = cwd / ".venv"
elif venv_path is None: elif venv_path is None:
...@@ -435,8 +458,9 @@ class EnvManager(object): ...@@ -435,8 +458,9 @@ class EnvManager(object):
venv_path = Path(venv_path) venv_path = Path(venv_path)
if not name: if not name:
name = cwd.name name = self._poetry.package.name
python_patch = ".".join([str(v) for v in sys.version_info[:3]])
python_minor = ".".join([str(v) for v in sys.version_info[:2]]) python_minor = ".".join([str(v) for v in sys.version_info[:2]])
if executable: if executable:
python_minor = decode( python_minor = decode(
...@@ -449,7 +473,82 @@ class EnvManager(object): ...@@ -449,7 +473,82 @@ class EnvManager(object):
] ]
), ),
shell=True, shell=True,
).strip()
)
supported_python = self._poetry.package.python_constraint
if not supported_python.allows(Version.parse(python_minor)):
# The currently activated or chosen Python version
# is not compatible with the Python constraint specified
# for the project.
# If an executable has been specified, we stop there
# and notify the user of the incompatibility.
# Otherwise, we try to find a compatible Python version.
if executable:
raise NoCompatiblePythonVersionFound(
self._poetry.package.python_versions, python_minor
)
io.write_line(
"<warning>The currently activated Python version {} "
"is not supported by the project ({}).\n"
"Trying to find and use a compatible version.</warning> ".format(
python_patch, self._poetry.package.python_versions
)
) )
for python_to_try in reversed(
sorted(
self._poetry.package.AVAILABLE_PYTHONS,
key=lambda v: (v.startswith("3"), -len(v), v),
)
):
if len(python_to_try) == 1:
if not parse_constraint("^{}.0".format(python_to_try)).allows_any(
supported_python
):
continue
elif not supported_python.allows_all(
parse_constraint(python_to_try + ".*")
):
continue
python = "python" + python_to_try
if io.is_debug():
io.write_line("<debug>Trying {}</debug>".format(python))
try:
python_patch = decode(
subprocess.check_output(
" ".join(
[
python,
"-c",
"\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
]
),
stderr=subprocess.STDOUT,
shell=True,
).strip()
)
except CalledProcessError:
continue
if not python_patch:
continue
if supported_python.allows(Version.parse(python_patch)):
io.write_line(
"Using <info>{}</info> ({})".format(python, python_patch)
)
executable = python
python_minor = ".".join(python_patch.split(".")[:2])
break
if not executable:
raise NoCompatiblePythonVersionFound(
self._poetry.package.python_versions
) )
if root_venv: if root_venv:
...@@ -503,7 +602,8 @@ class EnvManager(object): ...@@ -503,7 +602,8 @@ class EnvManager(object):
return VirtualEnv(venv) return VirtualEnv(venv)
def build_venv(self, path, executable=None): @classmethod
def build_venv(cls, path, executable=None):
if executable is not None: if executable is not None:
# Create virtualenv by using an external executable # Create virtualenv by using an external executable
try: try:
......
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",
] ]
......
...@@ -6,9 +6,11 @@ import tomlkit ...@@ -6,9 +6,11 @@ import tomlkit
from clikit.io import NullIO from clikit.io import NullIO
from poetry.semver import Version from poetry.semver import Version
from poetry.factory import Factory
from poetry.utils._compat import Path from poetry.utils._compat import Path
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 NoCompatiblePythonVersionFound
from poetry.utils.env import VirtualEnv from poetry.utils.env import VirtualEnv
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -25,32 +27,56 @@ print("nullpackage loaded"), ...@@ -25,32 +27,56 @@ print("nullpackage loaded"),
""" """
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir, config): @pytest.fixture()
def poetry(config):
poetry = Factory().create_poetry(
Path(__file__).parent.parent / "fixtures" / "simple_project"
)
poetry.set_config(config)
return poetry
@pytest.fixture()
def manager(poetry):
return EnvManager(poetry)
@pytest.fixture
def tmp_venv(tmp_dir, manager):
venv_path = Path(tmp_dir) / "venv"
manager.build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
yield venv
shutil.rmtree(str(venv.path))
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir, manager):
venv_path = Path(tmp_dir) / "Virtual Env" venv_path = Path(tmp_dir) / "Virtual Env"
EnvManager(config).build_venv(str(venv_path)) manager.build_venv(str(venv_path))
venv = VirtualEnv(venv_path) venv = VirtualEnv(venv_path)
assert venv.run("python", "-V", shell=True).startswith("Python") assert venv.run("python", "-V", shell=True).startswith("Python")
def test_env_get_in_project_venv(tmp_dir, config): def test_env_get_in_project_venv(manager, poetry):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
(Path(tmp_dir) / ".venv").mkdir() (poetry.file.parent / ".venv").mkdir()
venv = EnvManager(config).get(Path(tmp_dir)) venv = manager.get()
assert venv.path == Path(tmp_dir) / ".venv" assert venv.path == poetry.file.parent / ".venv"
shutil.rmtree(str(venv.path)) shutil.rmtree(str(venv.path))
CWD = Path(__file__).parent.parent / "fixtures" / "simple_project"
def build_venv(path, executable=None): def build_venv(path, executable=None):
os.mkdir(path) os.mkdir(path)
...@@ -72,7 +98,7 @@ def check_output_wrapper(version=Version.parse("3.7.1")): ...@@ -72,7 +98,7 @@ def check_output_wrapper(version=Version.parse("3.7.1")):
def test_activate_activates_non_existing_virtualenv_no_envs_file( def test_activate_activates_non_existing_virtualenv_no_envs_file(
tmp_dir, config, mocker tmp_dir, manager, poetry, config, mocker
): ):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
...@@ -89,8 +115,8 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( ...@@ -89,8 +115,8 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
) )
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)
env = EnvManager(config).activate("python3.7", CWD, NullIO()) env = manager.activate("python3.7", NullIO())
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = EnvManager.generate_env_name("simple-project", str(poetry.file.parent))
m.assert_called_with( m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7" os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
...@@ -106,11 +132,13 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( ...@@ -106,11 +132,13 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
assert env.base == Path("/prefix") assert env.base == Path("/prefix")
def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mocker): def test_activate_activates_existing_virtualenv_no_envs_file(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name))) os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name)))
...@@ -126,7 +154,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mo ...@@ -126,7 +154,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mo
) )
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)
env = EnvManager(config).activate("python3.7", CWD, NullIO()) env = manager.activate("python3.7", NullIO())
m.assert_not_called() m.assert_not_called()
...@@ -140,11 +168,13 @@ def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mo ...@@ -140,11 +168,13 @@ def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mo
assert env.base == Path("/prefix") assert env.base == Path("/prefix")
def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mocker): def test_activate_activates_same_virtualenv_with_envs_file(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml") envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document() doc = tomlkit.document()
...@@ -165,7 +195,7 @@ def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mock ...@@ -165,7 +195,7 @@ def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mock
) )
m = mocker.patch("poetry.utils.env.EnvManager.create_venv") m = mocker.patch("poetry.utils.env.EnvManager.create_venv")
env = EnvManager(config).activate("python3.7", CWD, NullIO()) env = manager.activate("python3.7", NullIO())
m.assert_not_called() m.assert_not_called()
...@@ -179,12 +209,12 @@ def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mock ...@@ -179,12 +209,12 @@ def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mock
def test_activate_activates_different_virtualenv_with_envs_file( def test_activate_activates_different_virtualenv_with_envs_file(
tmp_dir, config, mocker tmp_dir, manager, poetry, config, mocker
): ):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml") envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document() doc = tomlkit.document()
doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"} doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"}
...@@ -204,7 +234,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( ...@@ -204,7 +234,7 @@ def test_activate_activates_different_virtualenv_with_envs_file(
) )
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)
env = EnvManager(config).activate("python3.6", CWD, NullIO()) env = manager.activate("python3.6", NullIO())
m.assert_called_with( m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.6".format(venv_name)), executable="python3.6" os.path.join(tmp_dir, "{}-py3.6".format(venv_name)), executable="python3.6"
...@@ -219,11 +249,13 @@ def test_activate_activates_different_virtualenv_with_envs_file( ...@@ -219,11 +249,13 @@ def test_activate_activates_different_virtualenv_with_envs_file(
assert env.base == Path("/prefix") assert env.base == Path("/prefix")
def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocker): def test_activate_activates_recreates_for_different_patch(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml") envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document() doc = tomlkit.document()
doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"}
...@@ -254,7 +286,7 @@ def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocke ...@@ -254,7 +286,7 @@ def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocke
"poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv "poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv
) )
env = EnvManager(config).activate("python3.7", CWD, NullIO()) env = manager.activate("python3.7", NullIO())
build_venv_m.assert_called_with( build_venv_m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7" os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
...@@ -273,11 +305,13 @@ def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocke ...@@ -273,11 +305,13 @@ def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocke
assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)).exists() assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)).exists()
def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker): def test_activate_does_not_recreate_when_switching_minor(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml") envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document() doc = tomlkit.document()
doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"}
...@@ -303,7 +337,7 @@ def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker ...@@ -303,7 +337,7 @@ def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker
"poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv "poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv
) )
env = EnvManager(config).activate("python3.6", CWD, NullIO()) env = manager.activate("python3.6", NullIO())
build_venv_m.assert_not_called() build_venv_m.assert_not_called()
remove_venv_m.assert_not_called() remove_venv_m.assert_not_called()
...@@ -318,11 +352,13 @@ def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker ...@@ -318,11 +352,13 @@ def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists() assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker): def test_deactivate_non_activated_but_existing(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
( (
Path(tmp_dir) Path(tmp_dir)
...@@ -336,8 +372,8 @@ def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker): ...@@ -336,8 +372,8 @@ def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker):
side_effect=check_output_wrapper(), side_effect=check_output_wrapper(),
) )
EnvManager(config).deactivate(CWD, NullIO()) manager.deactivate(NullIO())
env = EnvManager(config).get(CWD) env = manager.get()
assert env.path == Path(tmp_dir) / "{}-py{}".format( assert env.path == Path(tmp_dir) / "{}-py{}".format(
venv_name, ".".join(str(c) for c in sys.version_info[:2]) venv_name, ".".join(str(c) for c in sys.version_info[:2])
...@@ -345,11 +381,11 @@ def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker): ...@@ -345,11 +381,11 @@ def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker):
assert Path("/prefix") assert Path("/prefix")
def test_deactivate_activated(tmp_dir, config, mocker): def test_deactivate_activated(tmp_dir, manager, poetry, config, mocker):
if "VIRTUAL_ENV" in os.environ: if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"] del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
version = Version.parse(".".join(str(c) for c in sys.version_info[:3])) version = Version.parse(".".join(str(c) for c in sys.version_info[:3]))
other_version = Version.parse("3.4") if version.major == 2 else version.next_minor other_version = Version.parse("3.4") if version.major == 2 else version.next_minor
( (
...@@ -375,8 +411,8 @@ def test_deactivate_activated(tmp_dir, config, mocker): ...@@ -375,8 +411,8 @@ def test_deactivate_activated(tmp_dir, config, mocker):
side_effect=check_output_wrapper(), side_effect=check_output_wrapper(),
) )
EnvManager(config).deactivate(CWD, NullIO()) manager.deactivate(NullIO())
env = EnvManager(config).get(CWD) env = manager.get()
assert env.path == Path(tmp_dir) / "{}-py{}.{}".format( assert env.path == Path(tmp_dir) / "{}-py{}.{}".format(
venv_name, version.major, version.minor venv_name, version.major, version.minor
...@@ -388,11 +424,11 @@ def test_deactivate_activated(tmp_dir, config, mocker): ...@@ -388,11 +424,11 @@ def test_deactivate_activated(tmp_dir, config, mocker):
def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
tmp_dir, config, mocker tmp_dir, manager, poetry, config, mocker
): ):
os.environ["VIRTUAL_ENV"] = "/environment/prefix" os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
config.merge({"virtualenvs": {"path": str(tmp_dir)}}) config.merge({"virtualenvs": {"path": str(tmp_dir)}})
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
...@@ -411,30 +447,30 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( ...@@ -411,30 +447,30 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
side_effect=[("/prefix", None)], side_effect=[("/prefix", None)],
) )
env = EnvManager(config).get(CWD) env = manager.get()
assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name) assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name)
assert env.base == Path("/prefix") assert env.base == Path("/prefix")
def test_list(tmp_dir, config): def test_list(tmp_dir, manager, poetry, config):
config.merge({"virtualenvs": {"path": str(tmp_dir)}}) config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(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()
venvs = EnvManager(config).list(CWD) venvs = manager.list()
assert 2 == len(venvs) assert 2 == len(venvs)
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venvs[0].path assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venvs[0].path
assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)) == venvs[1].path assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)) == venvs[1].path
def test_remove_by_python_version(tmp_dir, config, mocker): def test_remove_by_python_version(tmp_dir, manager, poetry, config, mocker):
config.merge({"virtualenvs": {"path": str(tmp_dir)}}) config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(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()
...@@ -443,18 +479,16 @@ def test_remove_by_python_version(tmp_dir, config, mocker): ...@@ -443,18 +479,16 @@ def test_remove_by_python_version(tmp_dir, config, mocker):
side_effect=check_output_wrapper(Version.parse("3.6.6")), side_effect=check_output_wrapper(Version.parse("3.6.6")),
) )
manager = EnvManager(config) venv = manager.remove("3.6")
venv = manager.remove("3.6", CWD)
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path
assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists() assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def test_remove_by_name(tmp_dir, config, mocker): def test_remove_by_name(tmp_dir, manager, poetry, config, mocker):
config.merge({"virtualenvs": {"path": str(tmp_dir)}}) config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(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()
...@@ -463,18 +497,16 @@ def test_remove_by_name(tmp_dir, config, mocker): ...@@ -463,18 +497,16 @@ def test_remove_by_name(tmp_dir, config, mocker):
side_effect=check_output_wrapper(Version.parse("3.6.6")), side_effect=check_output_wrapper(Version.parse("3.6.6")),
) )
manager = EnvManager(config) venv = manager.remove("{}-py3.6".format(venv_name))
venv = manager.remove("{}-py3.6".format(venv_name), CWD)
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path
assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists() assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def test_remove_also_deactivates(tmp_dir, config, mocker): def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker):
config.merge({"virtualenvs": {"path": str(tmp_dir)}}) config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) venv_name = manager.generate_env_name("simple-project", str(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()
...@@ -488,9 +520,7 @@ def test_remove_also_deactivates(tmp_dir, config, mocker): ...@@ -488,9 +520,7 @@ def test_remove_also_deactivates(tmp_dir, config, mocker):
doc[venv_name] = {"minor": "3.6", "patch": "3.6.6"} doc[venv_name] = {"minor": "3.6", "patch": "3.6.6"}
envs_file.write(doc) envs_file.write(doc)
manager = EnvManager(config) venv = manager.remove("python3.6")
venv = manager.remove("python3.6", CWD)
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path
assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists() assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
...@@ -499,18 +529,6 @@ def test_remove_also_deactivates(tmp_dir, config, mocker): ...@@ -499,18 +529,6 @@ def test_remove_also_deactivates(tmp_dir, config, mocker):
assert venv_name not in envs assert venv_name not in envs
@pytest.fixture
def tmp_venv(tmp_dir, config, request):
venv_path = Path(tmp_dir) / "venv"
EnvManager(config).build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
yield venv
shutil.rmtree(str(venv.path))
def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv): def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv):
venv_available = False venv_available = False
try: try:
...@@ -533,8 +551,110 @@ def test_run_with_input(tmp_dir, tmp_venv): ...@@ -533,8 +551,110 @@ def test_run_with_input(tmp_dir, tmp_venv):
def test_run_with_input_non_zero_return(tmp_dir, tmp_venv): def test_run_with_input_non_zero_return(tmp_dir, tmp_venv):
with pytest.raises(EnvCommandError) as processError: with pytest.raises(EnvCommandError) as processError:
# Test command that will return non-zero returncode. # Test command that will return non-zero returncode.
result = tmp_venv.run("python", "-", input_=ERRORING_SCRIPT) tmp_venv.run("python", "-", input_=ERRORING_SCRIPT)
assert processError.value.e.returncode == 1 assert processError.value.e.returncode == 1
def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^3.6"
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
mocker.patch("sys.version_info", (2, 7, 16))
mocker.patch(
"poetry.utils._compat.subprocess.check_output",
side_effect=check_output_wrapper(Version.parse("3.7.5")),
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
manager.create_venv(NullIO())
m.assert_called_with(
str(Path("/foo/virtualenvs/{}-py3.7".format(venv_name))), executable="python3"
)
def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific_ones(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^3.6"
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
mocker.patch("sys.version_info", (2, 7, 16))
mocker.patch(
"poetry.utils._compat.subprocess.check_output", side_effect=["3.5.3", "3.8.0"]
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
manager.create_venv(NullIO())
m.assert_called_with(
str(Path("/foo/virtualenvs/{}-py3.8".format(venv_name))), executable="python3.8"
)
def test_create_venv_fails_if_no_compatible_python_version_could_be_found(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^4.8"
mocker.patch(
"poetry.utils._compat.subprocess.check_output", side_effect=["", "", "", ""]
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
with pytest.raises(NoCompatiblePythonVersionFound) as e:
manager.create_venv(NullIO())
expected_message = (
"Poetry was unable to find a compatible version. "
"If you have one, you can explicitly use it "
'via the "env use" command.'
)
assert expected_message == str(e.value)
assert 0 == m.call_count
def test_create_venv_does_not_try_to_find_compatible_versions_with_executable(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^4.8"
mocker.patch("poetry.utils._compat.subprocess.check_output", side_effect=["3.8.0"])
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
with pytest.raises(NoCompatiblePythonVersionFound) as e:
manager.create_venv(NullIO(), executable="3.8")
expected_message = (
"The specified Python version (3.8.0) is not supported by the project (^4.8).\n"
"Please choose a compatible version or loosen the python constraint "
"specified in the pyproject.toml file."
)
assert expected_message == str(e.value)
assert 0 == m.call_count
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