Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle removal of vcs namespace package dependency gracefully #2239

Merged
merged 4 commits into from
Apr 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 234 additions & 119 deletions poetry.lock

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions poetry/installation/pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ def update(self, package, target):
self.install(target, update=True)

def remove(self, package):
# If we have a VCS package, remove its source directory
if package.source_type == "git":
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
safe_rmtree(str(src_dir))

try:
self.run("uninstall", package.name, "-y")
except CalledProcessError as e:
Expand All @@ -119,6 +113,17 @@ def remove(self, package):

raise

# This is a workaround for https://github.com/pypa/pip/issues/4176
nspkg_pth_file = self._env.site_packages / "{}-nspkg.pth".format(package.name)
if nspkg_pth_file.exists():
nspkg_pth_file.unlink()

# If we have a VCS package, remove its source directory
if package.source_type == "git":
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
safe_rmtree(str(src_dir))

def run(self, *args, **kwargs): # type: (...) -> str
return self._env.run_pip(*args, **kwargs)

Expand Down
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from poetry.config.config import Config as BaseConfig
from poetry.config.dict_config_source import DictConfigSource
from poetry.utils._compat import Path
from poetry.utils.env import EnvManager
from poetry.utils.env import VirtualEnv
from tests.helpers import mock_clone
from tests.helpers import mock_download

Expand Down Expand Up @@ -122,3 +124,15 @@ def tmp_dir():
yield dir_

shutil.rmtree(dir_)


@pytest.fixture
def tmp_venv(tmp_dir):
venv_path = Path(tmp_dir) / "venv"

EnvManager.build_venv(str(venv_path))

venv = VirtualEnv(venv_path)
yield venv

shutil.rmtree(str(venv.path))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__import__("pkg_resources").declare_namespace(__name__)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name = "one"
15 changes: 15 additions & 0 deletions tests/fixtures/git/github.com/demo/namespace-package-one/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from setuptools import setup, find_packages


setup(
name="namespace_package_one",
version="1.0.0",
description="",
long_description="",
author="Python Poetry",
author_email="noreply@python-poetry.org",
license="MIT",
packages=find_packages(),
namespace_packages=["namespace_package"],
zip_safe=False,
)
41 changes: 41 additions & 0 deletions tests/installation/test_pip_installer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import shutil

import pytest

from poetry.installation.pip_installer import PipInstaller
Expand Down Expand Up @@ -152,3 +154,42 @@ def test_requirement_git_develop_true(installer, package_git):
expected = ["-e", "git+git@github.com:demo/demo.git@master#egg=demo"]

assert expected == result


def test_uninstall_git_package_nspkg_pth_cleanup(mocker, tmp_venv, pool):
# this test scenario requires a real installation using the pip installer
installer = PipInstaller(tmp_venv, NullIO(), pool)

# use a namepspace package
package = Package("namespace-package-one", "1.0.0")
package.source_type = "git"
package.source_url = "https://github.com/demo/namespace-package-one.git"
package.source_reference = "master"
package.develop = True

# we do this here because the virtual env might not be usable if failure case is triggered
pth_file_candidate = tmp_venv.site_packages / "{}-nspkg.pth".format(package.name)

# in order to reproduce the scenario where the git source is removed prior to proper
# clean up of nspkg.pth file, we need to make sure the fixture is copied and not
# symlinked into the git src directory
def copy_only(source, dest):
if dest.exists():
dest.unlink()

if source.is_dir():
shutil.copytree(str(source), str(dest))
else:
shutil.copyfile(str(source), str(dest))

mocker.patch("tests.helpers.copy_or_symlink", new=copy_only)

# install package and then remove it
installer.install(package)
installer.remove(package)

assert not Path(pth_file_candidate).exists()

# any command in the virtual environment should trigger the error message
output = tmp_venv.run("python", "-m", "site")
assert "Error processing line 1 of {}".format(pth_file_candidate) not in output
12 changes: 0 additions & 12 deletions tests/utils/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,6 @@ 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"

Expand Down