diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 2fa1f7e97e0..3e48a7552ef 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -5,8 +5,6 @@ import glob import logging import os -import tarfile -import zipfile from pathlib import Path from typing import TYPE_CHECKING @@ -25,13 +23,12 @@ from poetry.pyproject.toml import PyProjectTOML from poetry.utils.env import EnvCommandError from poetry.utils.env import ephemeral_environment +from poetry.utils.helpers import extractall from poetry.utils.setup_reader import SetupReader if TYPE_CHECKING: - from collections.abc import Callable from collections.abc import Iterator - from contextlib import AbstractContextManager from poetry.core.packages.project_package import ProjectPackage @@ -291,26 +288,18 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo: # Still not dependencies found # So, we unpack and introspect suffix = path.suffix + zip = suffix == ".zip" - context: Callable[ - [str], AbstractContextManager[zipfile.ZipFile | tarfile.TarFile] - ] - if suffix == ".zip": - context = zipfile.ZipFile - else: - if suffix == ".bz2": - suffixes = path.suffixes - if len(suffixes) > 1 and suffixes[-2] == ".tar": - suffix = ".tar.bz2" - else: - suffix = ".tar.gz" - - context = tarfile.open + if suffix == ".bz2": + suffixes = path.suffixes + if len(suffixes) > 1 and suffixes[-2] == ".tar": + suffix = ".tar.bz2" + elif not zip: + suffix = ".tar.gz" with temporary_directory() as tmp_str: tmp = Path(tmp_str) - with context(path.as_posix()) as archive: - archive.extractall(tmp.as_posix()) + extractall(source=path, dest=tmp, zip=zip) # a little bit of guess work to determine the directory we care about elements = list(tmp.glob("*")) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 9575963e201..d801fb6dce2 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -1,9 +1,7 @@ from __future__ import annotations import os -import tarfile import tempfile -import zipfile from contextlib import redirect_stdout from io import StringIO @@ -18,12 +16,11 @@ from poetry.utils._compat import decode from poetry.utils.env import ephemeral_environment +from poetry.utils.helpers import extractall if TYPE_CHECKING: - from collections.abc import Callable from collections.abc import Collection - from contextlib import AbstractContextManager from poetry.repositories import RepositoryPool from poetry.utils.cache import ArtifactCache @@ -174,19 +171,11 @@ def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path from poetry.core.packages.utils.link import Link suffix = archive.suffix - context: Callable[ - [str], AbstractContextManager[zipfile.ZipFile | tarfile.TarFile] - ] - if suffix == ".zip": # noqa: SIM108 - context = zipfile.ZipFile - else: - context = tarfile.open + zip = suffix == ".zip" with temporary_directory() as tmp_dir: - with context(archive.as_posix()) as archive_archive: - archive_archive.extractall(tmp_dir) - archive_dir = Path(tmp_dir) + extractall(source=archive, dest=archive_dir, zip=zip) elements = list(archive_dir.glob("*")) diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py index fb14e819079..e9431fdb2a7 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py @@ -6,13 +6,16 @@ import shutil import stat import sys +import tarfile import tempfile +import zipfile from collections.abc import Mapping from contextlib import contextmanager from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import overload from requests.utils import atomic_open @@ -22,6 +25,7 @@ if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Iterator + from types import TracebackType from poetry.core.packages.package import Package from requests import Session @@ -39,7 +43,23 @@ def directory(path: Path) -> Iterator[Path]: os.chdir(cwd) -def _on_rm_error(func: Callable[[str], None], path: str, exc_info: Exception) -> None: +# Correct type signature when used as `shutil.rmtree(..., onexc=_on_rm_error)`. +@overload +def _on_rm_error( + func: Callable[[str], None], path: str, exc_info: Exception +) -> None: ... + + +# Correct type signature when used as `shutil.rmtree(..., onerror=_on_rm_error)`. +@overload +def _on_rm_error( + func: Callable[[str], None], + path: str, + exc_info: tuple[type[BaseException], BaseException, TracebackType], +) -> None: ... + + +def _on_rm_error(func: Callable[[str], None], path: str, exc_info: Any) -> None: if not os.path.exists(path): return @@ -47,9 +67,7 @@ def _on_rm_error(func: Callable[[str], None], path: str, exc_info: Exception) -> func(path) -def remove_directory( - path: Path, *args: Any, force: bool = False, **kwargs: Any -) -> None: +def remove_directory(path: Path, force: bool = False) -> 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 @@ -60,8 +78,12 @@ def remove_directory( if path.is_symlink(): return os.unlink(path) - kwargs["onerror"] = kwargs.pop("onerror", _on_rm_error if force else None) - shutil.rmtree(path, *args, **kwargs) + kwargs: dict[str, Any] = {} + if force: + onexc = "onexc" if sys.version_info >= (3, 12) else "onerror" + kwargs[onexc] = _on_rm_error + + shutil.rmtree(path, **kwargs) def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None: @@ -246,3 +268,16 @@ def get_file_hash(path: Path, hash_name: str = "sha256") -> str: h.update(content) return h.hexdigest() + + +def extractall(source: Path, dest: Path, zip: bool) -> None: + """Extract all members from either a zip or tar archive.""" + if zip: + with zipfile.ZipFile(source) as archive: + archive.extractall(dest) + else: + with tarfile.open(source) as archive: + if hasattr(tarfile, "data_filter"): + archive.extractall(dest, filter="data") + else: + archive.extractall(dest)