Commit 6721ebe0 by Arun Babu Neelicattu Committed by GitHub

ensure remove_directory is used instead of shutil.rmtree (#5510)

This change renames `safe_rmtree` into a more appropriate name with
relevant arguments. We ensure that the force behaviour is opt-in
and not the default to avoid ambiguity.
parent 8580dfc9
...@@ -25,7 +25,7 @@ from poetry.utils._compat import decode ...@@ -25,7 +25,7 @@ from poetry.utils._compat import decode
from poetry.utils.authenticator import Authenticator from poetry.utils.authenticator import Authenticator
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import pluralize from poetry.utils.helpers import pluralize
from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import remove_directory
from poetry.utils.pip import pip_install from poetry.utils.pip import pip_install
...@@ -488,7 +488,7 @@ class Executor: ...@@ -488,7 +488,7 @@ class Executor:
if package.source_type == "git": if package.source_type == "git":
src_dir = self._env.path / "src" / package.name src_dir = self._env.path / "src" / package.name
if src_dir.exists(): if src_dir.exists():
safe_rmtree(str(src_dir)) remove_directory(src_dir, force=True)
try: try:
return self.run_pip("uninstall", package.name, "-y") return self.run_pip("uninstall", package.name, "-y")
...@@ -588,7 +588,7 @@ class Executor: ...@@ -588,7 +588,7 @@ class Executor:
src_dir = self._env.path / "src" / package.name src_dir = self._env.path / "src" / package.name
if src_dir.exists(): if src_dir.exists():
safe_rmtree(str(src_dir)) remove_directory(src_dir, force=True)
src_dir.parent.mkdir(exist_ok=True) src_dir.parent.mkdir(exist_ok=True)
......
...@@ -13,7 +13,7 @@ from poetry.core.pyproject.toml import PyProjectTOML ...@@ -13,7 +13,7 @@ from poetry.core.pyproject.toml import PyProjectTOML
from poetry.installation.base_installer import BaseInstaller from poetry.installation.base_installer import BaseInstaller
from poetry.utils._compat import encode from poetry.utils._compat import encode
from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import remove_directory
from poetry.utils.pip import pip_install from poetry.utils.pip import pip_install
...@@ -128,7 +128,7 @@ class PipInstaller(BaseInstaller): ...@@ -128,7 +128,7 @@ class PipInstaller(BaseInstaller):
if package.source_type == "git": if package.source_type == "git":
src_dir = self._env.path / "src" / package.name src_dir = self._env.path / "src" / package.name
if src_dir.exists(): if src_dir.exists():
safe_rmtree(str(src_dir)) remove_directory(src_dir, force=True)
def run(self, *args: Any, **kwargs: Any) -> str: def run(self, *args: Any, **kwargs: Any) -> str:
return self._env.run_pip(*args, **kwargs) return self._env.run_pip(*args, **kwargs)
...@@ -252,7 +252,7 @@ class PipInstaller(BaseInstaller): ...@@ -252,7 +252,7 @@ class PipInstaller(BaseInstaller):
src_dir = self._env.path / "src" / package.name src_dir = self._env.path / "src" / package.name
if src_dir.exists(): if src_dir.exists():
safe_rmtree(str(src_dir)) remove_directory(src_dir, force=True)
src_dir.parent.mkdir(exist_ok=True) src_dir.parent.mkdir(exist_ok=True)
......
...@@ -34,7 +34,7 @@ from poetry.packages import DependencyPackage ...@@ -34,7 +34,7 @@ from poetry.packages import DependencyPackage
from poetry.packages.package_collection import PackageCollection from poetry.packages.package_collection import PackageCollection
from poetry.puzzle.exceptions import OverrideNeeded from poetry.puzzle.exceptions import OverrideNeeded
from poetry.utils.helpers import download_file from poetry.utils.helpers import download_file
from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import remove_directory
if TYPE_CHECKING: if TYPE_CHECKING:
...@@ -211,7 +211,7 @@ class Provider: ...@@ -211,7 +211,7 @@ class Provider:
except Exception: except Exception:
raise raise
finally: finally:
safe_rmtree(str(tmp_dir)) remove_directory(tmp_dir, force=True)
return package return package
......
...@@ -7,7 +7,6 @@ import json ...@@ -7,7 +7,6 @@ import json
import os import os
import platform import platform
import re import re
import shutil
import subprocess import subprocess
import sys import sys
import sysconfig import sysconfig
...@@ -43,6 +42,7 @@ from poetry.utils._compat import list_to_shell_command ...@@ -43,6 +42,7 @@ from poetry.utils._compat import list_to_shell_command
from poetry.utils._compat import metadata from poetry.utils._compat import metadata
from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import is_dir_writable
from poetry.utils.helpers import paths_csv from poetry.utils.helpers import paths_csv
from poetry.utils.helpers import remove_directory
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
...@@ -365,7 +365,7 @@ class SitePackages: ...@@ -365,7 +365,7 @@ class SitePackages:
file.unlink() file.unlink()
if distribution._path.exists(): if distribution._path.exists():
shutil.rmtree(str(distribution._path)) remove_directory(str(distribution._path), force=True)
paths.append(distribution._path) paths.append(distribution._path)
...@@ -1070,7 +1070,7 @@ class EnvManager: ...@@ -1070,7 +1070,7 @@ class EnvManager:
path = Path(path) path = Path(path)
assert path.is_dir() assert path.is_dir()
try: try:
shutil.rmtree(str(path)) remove_directory(path)
return return
except OSError as e: except OSError as e:
# Continue only if e.errno == 16 # Continue only if e.errno == 16
...@@ -1085,7 +1085,7 @@ class EnvManager: ...@@ -1085,7 +1085,7 @@ class EnvManager:
if file_path.is_file() or file_path.is_symlink(): if file_path.is_file() or file_path.is_symlink():
file_path.unlink() file_path.unlink()
elif file_path.is_dir(): elif file_path.is_dir():
shutil.rmtree(str(file_path)) remove_directory(file_path, force=True)
@classmethod @classmethod
def get_system_env(cls, naive: bool = False) -> SystemEnv | GenericEnv: def get_system_env(cls, naive: bool = False) -> SystemEnv | GenericEnv:
......
...@@ -33,18 +33,13 @@ def module_name(name: str) -> str: ...@@ -33,18 +33,13 @@ def module_name(name: str) -> str:
return canonicalize_name(name).replace(".", "_").replace("-", "_") return canonicalize_name(name).replace(".", "_").replace("-", "_")
def _del_ro(action: Callable, name: str, exc: Exception) -> None:
os.chmod(name, stat.S_IWRITE)
os.remove(name)
@contextmanager @contextmanager
def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]: def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]:
name = tempfile.mkdtemp(*args, **kwargs) name = tempfile.mkdtemp(*args, **kwargs)
yield name yield name
shutil.rmtree(name, onerror=_del_ro) remove_directory(name, force=True)
def get_cert(config: Config, repository_name: str) -> Path | None: def get_cert(config: Config, repository_name: str) -> Path | None:
...@@ -71,11 +66,21 @@ def _on_rm_error(func: Callable, path: str, exc_info: Exception) -> None: ...@@ -71,11 +66,21 @@ def _on_rm_error(func: Callable, path: str, exc_info: Exception) -> None:
func(path) func(path)
def safe_rmtree(path: str) -> None: def remove_directory(
path: Path | str, *args: Any, force: bool = False, **kwargs: Any
) -> None:
"""
Helper function handle safe removal, and optionally forces stubborn file removal.
This is particularly useful when dist files are read-only or git writes read-only
files on Windows.
Internally, all arguments are passed to `shutil.rmtree`.
"""
if Path(path).is_symlink(): if Path(path).is_symlink():
return os.unlink(str(path)) return os.unlink(str(path))
shutil.rmtree(path, onerror=_on_rm_error) kwargs["onerror"] = kwargs.pop("onerror", _on_rm_error if force else None)
shutil.rmtree(path, *args, **kwargs)
def merge_dicts(d1: dict, d2: dict) -> None: def merge_dicts(d1: dict, d2: dict) -> None:
......
from __future__ import annotations from __future__ import annotations
import os import os
import shutil
import subprocess import subprocess
import sys import sys
...@@ -28,6 +27,7 @@ from poetry.utils.env import InvalidCurrentPythonVersionError ...@@ -28,6 +27,7 @@ from poetry.utils.env import InvalidCurrentPythonVersionError
from poetry.utils.env import NoCompatiblePythonVersionFound from poetry.utils.env import NoCompatiblePythonVersionFound
from poetry.utils.env import SystemEnv from poetry.utils.env import SystemEnv
from poetry.utils.env import VirtualEnv from poetry.utils.env import VirtualEnv
from poetry.utils.helpers import remove_directory
if TYPE_CHECKING: if TYPE_CHECKING:
...@@ -704,19 +704,19 @@ def test_remove_keeps_dir_if_not_deleteable( ...@@ -704,19 +704,19 @@ def test_remove_keeps_dir_if_not_deleteable(
side_effect=check_output_wrapper(Version.parse("3.6.6")), side_effect=check_output_wrapper(Version.parse("3.6.6")),
) )
original_rmtree = shutil.rmtree def err_on_rm_venv_only(path: Path | str, *args: Any, **kwargs: Any) -> None:
if str(path) == str(venv_path):
def err_on_rm_venv_only(path: str, *args: Any, **kwargs: Any) -> None:
if path == str(venv_path):
raise OSError(16, "Test error") # ERRNO 16: Device or resource busy raise OSError(16, "Test error") # ERRNO 16: Device or resource busy
else: else:
original_rmtree(path) remove_directory(path)
m = mocker.patch("shutil.rmtree", side_effect=err_on_rm_venv_only) m = mocker.patch(
"poetry.utils.env.remove_directory", side_effect=err_on_rm_venv_only
)
venv = manager.remove(f"{venv_name}-py3.6") venv = manager.remove(f"{venv_name}-py3.6")
m.assert_any_call(str(venv_path)) m.assert_any_call(venv_path)
assert venv_path == venv.path assert venv_path == venv.path
assert venv_path.exists() assert venv_path.exists()
...@@ -725,7 +725,7 @@ def test_remove_keeps_dir_if_not_deleteable( ...@@ -725,7 +725,7 @@ def test_remove_keeps_dir_if_not_deleteable(
assert not file1_path.exists() assert not file1_path.exists()
assert not file2_path.exists() assert not file2_path.exists()
m.side_effect = original_rmtree # Avoid teardown using `err_on_rm_venv_only` m.side_effect = remove_directory # Avoid teardown using `err_on_rm_venv_only`
@pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows") @pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows")
......
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