Commit 32c87c9d by Sébastien Eustace Committed by GitHub

Merge pull request #2691 from abn/merge-master-into-develop

Merge unreleased changes from master into develop
parents ca1c3977 9ab737d6
......@@ -48,7 +48,6 @@ jobs:
- name: Install poetry
shell: bash
run: |
curl -fsS -o get-poetry.py https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
python get-poetry.py -y --preview
echo "::set-env name=PATH::$HOME/.poetry/bin:$PATH"
......
......@@ -50,7 +50,7 @@ This release **must** be downloaded via the `get-poetry.py` script and not via t
- The exceptions are now beautifully displayed in the terminal with various level of details depending on the verbosity ([2230](https://github.com/python-poetry/poetry/pull/2230)).
## [1.0.9] - 2010-06-09
## [1.0.9] - 2020-06-09
### Fixed
......
......@@ -480,7 +480,7 @@ poetry export -f requirements.txt > requirements.txt
The `env` command regroups sub commands to interact with the virtualenvs
associated with a specific project.
See [Managing environments](/docs/managing-environments.md) for more information about these commands.
See [Managing environments](/docs/managing-environments/) for more information about these commands.
## cache
......
......@@ -33,7 +33,7 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poet
!!! note
You only need to install Poetry once. It will automatically pick up the current
Python version and use it to [create virtualenvs](/docs/managing-environments.md) accordingly.
Python version and use it to [create virtualenvs](/docs/managing-environments) accordingly.
The installer installs the `poetry` tool to Poetry's `bin` directory.
On Unix it is located at `$HOME/.poetry/bin` and on Windows at `%USERPROFILE%\.poetry\bin`.
......@@ -82,6 +82,15 @@ POETRY_VERSION=0.12.0 python get-poetry.py
Note that the installer does not support Poetry releases < 0.12.0.
!!!note
The setup script must be able to find one of following executables in your shell's path environment:
- `python` (which can be a py3 or py2 interpreter)
- `python3`
- `py.exe -3` (Windows)
- `py.exe -2` (Windows)
### Alternative installation methods (not recommended)
!!!note
......
......@@ -92,7 +92,7 @@ poetry env info --path
## Listing the environments associated with the project
You can also list all the virtual environments associated with the current virtual environment
You can also list all the virtual environments associated with the current project
with the `env list` command:
```bash
......
......@@ -38,7 +38,7 @@ The recommended notation for the most common licenses is (alphabetical):
* MIT
Optional, but it is highly recommended to supply this.
More identifiers are listed at the [SPDX Open Source License Registry](https://www.spdx.org/licenses/).
More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/).
!!!note
......
......@@ -449,6 +449,26 @@ class Installer:
break
current_version = None
if os.path.exists(POETRY_LIB):
with open(
os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8"
) as f:
version_content = f.read()
current_version_re = re.match(
'(?ms).*__version__ = "(.+)".*', version_content
)
if not current_version_re:
print(
colorize(
"warning",
"Unable to get the current Poetry version. Assuming None",
)
)
else:
current_version = current_version_re.group(1)
if current_version == version and not self._force:
print("Latest version already installed.")
return None, current_version
......@@ -637,6 +657,7 @@ class Installer:
if match and tuple(map(int, match.groups())) >= (3, 0):
# favor the first py3 executable we can find.
return executable
if fallback is None:
# keep this one as the fallback; it was the first valid executable we found.
fallback = executable
......@@ -669,7 +690,9 @@ class Installer:
)
with open(os.path.join(POETRY_BIN, "poetry"), "w", encoding="utf-8") as f:
if not WINDOWS:
if WINDOWS:
python_executable = "python"
f.write(u("#!/usr/bin/env {}\n".format(python_executable)))
f.write(u(BIN))
......
......@@ -47,11 +47,18 @@ class AddCommand(InstallerCommand, InitCommand):
),
option("lock", None, "Do not perform operations (only update the lockfile)."),
]
help = """The add command adds required packages to your <comment>pyproject.toml</> and installs them.
If you do not specify a version constraint, poetry will choose a suitable one based on the available package versions.
"""
help = (
"The add command adds required packages to your <comment>pyproject.toml</> and installs them.\n\n"
"If you do not specify a version constraint, poetry will choose a suitable one based on the available package versions.\n\n"
"You can specify a package in the following forms:\n"
" - A single name (<b>requests</b>)\n"
" - A name and a constraint (<b>requests@^2.23.0</b>)\n"
" - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)\n"
" - A git url with a revision (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)\n"
" - A file path (<b>../my-package/my-package.whl</b>)\n"
" - A directory (<b>../my-package/</b>)\n"
" - A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n"
)
loggers = ["poetry.repositories.pypi_repository"]
......
......@@ -149,7 +149,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
help_message = (
"You can specify a package in the following forms:\n"
" - A single name (<b>requests</b>)\n"
" - A name and a constraint (<b>requests ^2.23.0</b>)\n"
" - A name and a constraint (<b>requests@^2.23.0</b>)\n"
" - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)\n"
" - A git url with a revision (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)\n"
" - A file path (<b>../my-package/my-package.whl</b>)\n"
......
......@@ -142,7 +142,7 @@ class PipInstaller(BaseInstaller):
if package.source_type in ["file", "directory"]:
if package.root_dir:
req = os.path.join(package.root_dir, package.source_url)
req = (package.root_dir / package.source_url).as_posix()
else:
req = os.path.realpath(package.source_url)
......@@ -185,7 +185,7 @@ class PipInstaller(BaseInstaller):
from poetry.utils.toml_file import TomlFile
if package.root_dir:
req = os.path.join(package.root_dir, package.source_url)
req = (package.root_dir / package.source_url).as_posix()
else:
req = os.path.realpath(package.source_url)
......
......@@ -424,7 +424,7 @@ class EnvManager(object):
if venv.path.name == python:
# Exact virtualenv name
if not envs_file.exists():
self.remove_venv(str(venv.path))
self.remove_venv(venv.path)
return venv
......@@ -434,7 +434,7 @@ class EnvManager(object):
current_env = envs.get(base_env_name)
if not current_env:
self.remove_venv(str(venv.path))
self.remove_venv(venv.path)
return venv
......@@ -442,7 +442,7 @@ class EnvManager(object):
del envs[base_env_name]
envs_file.write(envs)
self.remove_venv(str(venv.path))
self.remove_venv(venv.path)
return venv
......@@ -496,7 +496,7 @@ class EnvManager(object):
del envs[base_env_name]
envs_file.write(envs)
self.remove_venv(str(venv))
self.remove_venv(venv)
return VirtualEnv(venv)
......@@ -642,7 +642,7 @@ class EnvManager(object):
"Creating virtualenv <c1>{}</> in {}".format(name, str(venv_path))
)
self.build_venv(str(venv), executable=executable)
self.build_venv(venv, executable=executable)
else:
if force:
if not env.is_sane():
......@@ -654,8 +654,8 @@ class EnvManager(object):
io.write_line(
"Recreating virtualenv <c1>{}</> in {}".format(name, str(venv))
)
self.remove_venv(str(venv))
self.build_venv(str(venv), executable=executable)
self.remove_venv(venv)
self.build_venv(venv, executable=executable)
elif io.is_very_verbose():
io.write_line("Virtualenv <c1>{}</> already exists.".format(name))
......@@ -680,21 +680,41 @@ class EnvManager(object):
@classmethod
def build_venv(
cls, path, executable=None
): # type: (str, Optional[Union[str, Path]]) -> virtualenv.run.session.Session
): # type: (Union[Path,str], Optional[Union[str, Path]]) -> virtualenv.run.session.Session
if isinstance(executable, Path):
executable = executable.resolve()
executable = executable.resolve().as_posix()
return virtualenv.cli_run(
[
"--no-download",
"--no-periodic-update",
"--python",
executable or sys.executable,
path,
str(path),
]
)
def remove_venv(self, path): # type: (str) -> None
shutil.rmtree(path)
@classmethod
def remove_venv(cls, path): # type: (Union[Path,str]) -> None
if isinstance(path, str):
path = Path(path)
assert path.is_dir()
try:
shutil.rmtree(str(path))
return
except OSError as e:
# Continue only if e.errno == 16
if e.errno != 16: # ERRNO 16: Device or resource busy
raise e
# Delete all files and folders but the toplevel one. This is because sometimes
# the venv folder is mounted by the OS, such as in a docker volume. In such
# cases, an attempt to delete the folder itself will result in an `OSError`.
# See https://github.com/python-poetry/poetry/pull/2064
for file_path in path.iterdir():
if file_path.is_file() or file_path.is_symlink():
file_path.unlink()
elif file_path.is_dir():
shutil.rmtree(str(file_path))
def get_base_prefix(self): # type: () -> Path
if hasattr(sys, "real_prefix"):
......
......@@ -94,6 +94,8 @@ class Shell:
suffix = ".fish"
elif "csh" == self._name:
suffix = ".csh"
elif "tcsh" == self._name:
suffix = ".csh"
else:
suffix = ""
......@@ -104,6 +106,8 @@ class Shell:
return "source"
elif "csh" == self._name:
return "source"
elif "tcsh" == self._name:
return "source"
return "."
......
import os
import shutil
import sys
from typing import Optional
from typing import Union
import tomlkit
from cleo.testers import CommandTester
......@@ -16,12 +18,8 @@ from poetry.utils.toml_file import TomlFile
CWD = Path(__file__).parent.parent / "fixtures" / "simple_project"
def build_venv(path, executable=None):
os.mkdir(path)
def remove_venv(path):
shutil.rmtree(path)
def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> ()
os.mkdir(str(path))
def check_output_wrapper(version=Version.parse("3.7.1")):
......@@ -62,7 +60,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m
)
m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7"
)
envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
......
......@@ -2,6 +2,9 @@ import os
import shutil
import sys
from typing import Optional
from typing import Union
import pytest
import tomlkit
......@@ -84,12 +87,8 @@ def test_env_get_in_project_venv(manager, poetry):
shutil.rmtree(str(venv.path))
def build_venv(path, executable=None):
os.mkdir(path)
def remove_venv(path):
shutil.rmtree(path)
def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> ()
os.mkdir(str(path))
def check_output_wrapper(version=Version.parse("3.7.1")):
......@@ -126,7 +125,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
venv_name = EnvManager.generate_env_name("simple-project", str(poetry.file.parent))
m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7"
)
envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
......@@ -244,7 +243,7 @@ def test_activate_activates_different_virtualenv_with_envs_file(
env = manager.activate("python3.6", NullIO())
m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.6".format(venv_name)), executable="python3.6"
Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6"
)
assert envs_file.exists()
......@@ -290,17 +289,15 @@ def test_activate_activates_recreates_for_different_patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=build_venv
)
remove_venv_m = mocker.patch(
"poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv
"poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv
)
env = manager.activate("python3.7", NullIO())
build_venv_m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
)
remove_venv_m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name))
Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7"
)
remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name))
assert envs_file.exists()
envs = envs_file.read()
......@@ -341,7 +338,7 @@ def test_activate_does_not_recreate_when_switching_minor(
"poetry.utils.env.EnvManager.build_venv", side_effect=build_venv
)
remove_venv_m = mocker.patch(
"poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv
"poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv
)
env = manager.activate("python3.6", NullIO())
......@@ -536,6 +533,54 @@ def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker):
assert venv_name not in envs
def test_remove_keeps_dir_if_not_deleteable(tmp_dir, manager, poetry, config, mocker):
# Ensure we empty rather than delete folder if its is an active mount point.
# See https://github.com/python-poetry/poetry/pull/2064
config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
venv_path = Path(tmp_dir) / "{}-py3.6".format(venv_name)
venv_path.mkdir()
folder1_path = venv_path / "folder1"
folder1_path.mkdir()
file1_path = folder1_path / "file1"
file1_path.touch(exist_ok=False)
file2_path = venv_path / "file2"
file2_path.touch(exist_ok=False)
mocker.patch(
"poetry.utils._compat.subprocess.check_output",
side_effect=check_output_wrapper(Version.parse("3.6.6")),
)
original_rmtree = shutil.rmtree
def err_on_rm_venv_only(path, *args, **kwargs):
print(path)
if path == str(venv_path):
raise OSError(16, "Test error") # ERRNO 16: Device or resource busy
else:
original_rmtree(path)
m = mocker.patch("shutil.rmtree", side_effect=err_on_rm_venv_only)
venv = manager.remove("{}-py3.6".format(venv_name))
m.assert_any_call(str(venv_path))
assert venv_path == venv.path
assert venv_path.exists()
assert not folder1_path.exists()
assert not file1_path.exists()
assert not file2_path.exists()
m.side_effect = original_rmtree # Avoid teardown using `err_on_rm_venv_only`
@pytest.mark.skipif(
os.name == "nt" or PY2, reason="Symlinks are not support for Windows"
)
......@@ -579,7 +624,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_
manager.create_venv(NullIO())
m.assert_called_with(
str(Path("/foo/virtualenvs/{}-py3.7".format(venv_name))), executable="python3"
Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), executable="python3"
)
......@@ -603,7 +648,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific
manager.create_venv(NullIO())
m.assert_called_with(
str(Path("/foo/virtualenvs/{}-py3.8".format(venv_name))), executable="python3.8"
Path("/foo/virtualenvs/{}-py3.8".format(venv_name)), executable="python3.8"
)
......@@ -686,12 +731,10 @@ def test_create_venv_uses_patch_version_to_detect_compatibility(
assert not check_output.called
m.assert_called_with(
str(
Path(
"/foo/virtualenvs/{}-py{}.{}".format(
venv_name, version.major, version.minor
)
)
),
executable=None,
)
......@@ -725,12 +768,10 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable(
assert check_output.called
m.assert_called_with(
str(
Path(
"/foo/virtualenvs/{}-py{}.{}".format(
venv_name, version.major, version.minor - 1
)
)
),
executable="python{}.{}".format(version.major, version.minor - 1),
)
......@@ -763,9 +804,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir(
manager.activate("python3.7", NullIO())
m.assert_called_with(
os.path.join(str(poetry.file.parent), ".venv"), executable="python3.7"
)
m.assert_called_with(poetry.file.parent / ".venv", executable="python3.7")
envs_file = TomlFile(Path(tmp_dir) / "virtualenvs" / "envs.toml")
assert not envs_file.exists()
......
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