...
 
Commits (20)
    https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/eeb3d8fdff94080b7978c4d2d7126020cddf9c93 synthesize a traceback to get a normal exc_info from an exception 2023-07-28T01:00:06-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com - as per <a href="https://docs.python.org/3.12/whatsnew/3.12.html#shutil" rel="nofollow noreferrer noopener" target="_blank">https://docs.python.org/3.12/whatsnew/3.12.html#shutil</a>, we must expect only an exception and *not* the full exc_info from the new onexc function (the documentation of this is very misleading and still uses the label "excinfo": <a href="https://docs.python.org/3.12/library/shutil.html#shutil.rmtree" rel="nofollow noreferrer noopener" target="_blank">https://docs.python.org/3.12/library/shutil.html#shutil.rmtree</a>) https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/4f036be496a40b90fa665432c001e9f9cce3dbee add news entry 2023-07-28T01:20:07-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/eabefd40214f96e30eb2b0e607f9a4c7ffbf5e43 revert the traceback wrapping 2023-07-28T02:18:36-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/454e9768fbc4250db28aa17f3365fea0d131253d incorporate review comments 2023-07-28T02:43:49-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/6cc961ee799745a8ccf0256f670364c59d29b8c7 Uppercase Python 2023-07-28T14:46:23+08:00 Tzu-ping Chung uranusjr@gmail.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/4babc076631d5ac48a0f9573fff406ab2dd6b6e4 Move metadata-fetching log to VERBOSE level 2023-07-28T15:32:29+08:00 Tzu-ping Chung uranusjr@gmail.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/b69ed811f0bfae87772c8a9d13b61f0ebf6a4bd4 Merge pull request #12187 from cosmicexplorer/fix-tempdir-cleanup 2023-07-30T20:48:58+08:00 Tzu-ping Chung uranusjr@gmail.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/e4a0df158664f75fd94bf996f17598f53c8b9703 Merge branch 'main' into cleaner-metadata-log 2023-07-30T20:49:16+08:00 Tzu-ping Chung uranusjr@gmail.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/3dc5ac540755da1559aff6ab3851bee1e836f199 Merge pull request #12188 from uranusjr/cleaner-metadata-log 2023-07-31T08:18:59+08:00 Tzu-ping Chung uranusjr@gmail.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/b47f77d330bb7a8642af5490c0f03642f039974c add lots of comments on the function of BuildTracker 2023-08-01T13:50:40-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/023b3d923746ffd4a7cafce471250af7daaa4fed add news 2023-08-01T13:59:42-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/e4d2e6e6b250dcb5e5bfafaed85472c8baf54886 Remove superfluous callout of new resolver 2023-08-02T12:26:27-07:00 Jeff Widman jeff@jeffwidman.com The new resolver has been out for nearly three years now. I don't think we need to highlight it on the Readme anymore. Folks who are truly affected are far more likely to google their errors and then get redirected to it. By removing the noise from the Readme, it stops distracting from other stuff. https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/3c0147a8899c2615bef717850f399b3ae99d391f Merge pull request #12198 from jeffwidman/patch-1 2023-08-02T21:23:23+01:00 Paul Moore p.f.moore@gmail.com Remove superfluous callout of new resolver https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/a19ade74a5cebbe73e8ab6e88f4123e0e2e54c06 Use strict optional checking in req_install.py (#11379) 2023-08-05T00:07:27+01:00 Shantanu 12621235+hauntsaninja@users.noreply.github.com * Use strict optional checking in req_install.py Suggested by pradyunsg in #11374 Since half of the API of this class depends on self.req not being None, it seems like we should just prevent users from passing None here. However, I wasn't able to make that change. Rather than sprinkle asserts everywhere, I added "checked" properties. I find this less ad hoc and easier to adapt if e.g. we're able to make self.req never None in the future. There are now some code paths where we have asserts that we didn't before. I relied on other type hints in pip's code base to be accurate. If that is not the case and we actually relied on some function being able to accept None when not typed as such, we may hit these asserts. But hopefully tests would catch such a thing. * news * black * inline asserts * code review * fix up merge issue * fix specifier bug https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/d311e6e603ea7d4a2ef6c6f465308f523cfe83d2 Upgrade certifi to 2023.7.22 (#12206) 2023-08-06T09:26:54-05:00 Seth Michael Larson seth@python.org https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/901db9cf8dc01477dda0601a3d0bc20eaa16073e Use a set for TargetPython.get_tags for performance (#12204) 2023-08-06T11:08:16-05:00 Shantanu 12621235+hauntsaninja@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/d8cd93f4fa61df6be7786e301e4c17ab654d0107 Fix incorrect use of re function in tests (#12213) 2023-08-08T07:37:38-05:00 Shantanu 12621235+hauntsaninja@users.noreply.github.com https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/b1fd3ac3e483b59ce15b1006a0a757f4502864cb Update src/pip/_internal/operations/build/build_tracker.py 2023-08-11T04:37:31-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:p.f.moore@gmail.com" title="p.f.moore@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg1" style="text-decoration: none">N</a><a href="mailto:p.f.moore@gmail.com" title="p.f.moore@gmail.com">Paul Moore</a> &lt;<a href="mailto:p.f.moore@gmail.com" title="p.f.moore@gmail.com">p.f.moore@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/4a853ea34831e9fa9695cd966905cd5b591e0b01 Update src/pip/_internal/operations/build/build_tracker.py 2023-08-11T04:38:07-04:00 Danny McClanahan 1305167+cosmicexplorer@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:p.f.moore@gmail.com" title="p.f.moore@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg4" style="text-decoration: none">N</a><a href="mailto:p.f.moore@gmail.com" title="p.f.moore@gmail.com">Paul Moore</a> &lt;<a href="mailto:p.f.moore@gmail.com" title="p.f.moore@gmail.com">p.f.moore@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/pypa/pip/-/commit/5378a6e110501a9cfd4e79443251233d66684aee Merge pull request #12194 from cosmicexplorer/build-tracker-comments 2023-08-11T12:05:38+01:00 Paul Moore p.f.moore@gmail.com Add lots of comments on the function of BuildTracker
......@@ -19,8 +19,6 @@ We release updates regularly, with a new version every 3 months. Find more detai
* `Release notes`_
* `Release process`_
In pip 20.3, we've `made a big improvement to the heart of pip`_; `learn more`_. We want your input, so `sign up for our user experience research studies`_ to help us do it right.
**Note**: pip 21.0, in January 2021, removed Python 2 support, per pip's `Python 2 support policy`_. Please migrate to Python 3.
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
......@@ -49,9 +47,6 @@ rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
.. _GitHub page: https://github.com/pypa/pip
.. _Development documentation: https://pip.pypa.io/en/latest/development
.. _made a big improvement to the heart of pip: https://pyfound.blogspot.com/2020/11/pip-20-3-new-resolver.html
.. _learn more: https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020
.. _sign up for our user experience research studies: https://pyfound.blogspot.com/2020/03/new-pip-resolver-to-roll-out-this-year.html
.. _Python 2 support policy: https://pip.pypa.io/en/latest/development/release-process/#python-2-support
.. _Issue tracking: https://github.com/pypa/pip/issues
.. _Discourse channel: https://discuss.python.org/c/packaging
......
The metadata-fetching log message is moved to the VERBOSE level and now hidden
by default. The more significant information in this message to most users are
already available in surrounding logs (the package name and version of the
metadata being fetched), while the URL to the exact metadata file is generally
too long and clutters the output. The message can be brought back with
``--verbose``.
Fix improper handling of the new onexc argument of ``shutil.rmtree()`` in Python 3.12.
Add lots of comments to the ``BuildTracker``.
Improve use of datastructures to make candidate selection 1.6x faster
Upgrade certifi to 2023.7.22
......@@ -105,7 +105,7 @@ def show_tags(options: Values) -> None:
tag_limit = 10
target_python = make_target_python(options)
tags = target_python.get_tags()
tags = target_python.get_sorted_tags()
# Display the target options that were explicitly provided.
formatted_target = target_python.format_given()
......
import abc
from typing import Optional
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata.base import BaseDistribution
......@@ -19,12 +20,23 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
- we must be able to create a Distribution object exposing the
above metadata.
- if we need to do work in the build tracker, we must be able to generate a unique
string to identify the requirement in the build tracker.
"""
def __init__(self, req: InstallRequirement) -> None:
super().__init__()
self.req = req
@abc.abstractproperty
def build_tracker_id(self) -> Optional[str]:
"""A string that uniquely identifies this requirement to the build tracker.
If None, then this dist has no work to do in the build tracker, and
``.prepare_distribution_metadata()`` will not be called."""
raise NotImplementedError()
@abc.abstractmethod
def get_metadata_distribution(self) -> BaseDistribution:
raise NotImplementedError()
......
from typing import Optional
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution
......@@ -10,6 +12,10 @@ class InstalledDistribution(AbstractDistribution):
been computed.
"""
@property
def build_tracker_id(self) -> Optional[str]:
return None
def get_metadata_distribution(self) -> BaseDistribution:
assert self.req.satisfied_by is not None, "not actually installed"
return self.req.satisfied_by
......
import logging
from typing import Iterable, Set, Tuple
from typing import Iterable, Optional, Set, Tuple
from pip._internal.build_env import BuildEnvironment
from pip._internal.distributions.base import AbstractDistribution
......@@ -18,6 +18,12 @@ class SourceDistribution(AbstractDistribution):
generated, either using PEP 517 or using the legacy `setup.py egg_info`.
"""
@property
def build_tracker_id(self) -> Optional[str]:
"""Identify this requirement uniquely by its link."""
assert self.req.link
return self.req.link.url_without_fragment
def get_metadata_distribution(self) -> BaseDistribution:
return self.req.get_dist()
......
from typing import Optional
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.distributions.base import AbstractDistribution
......@@ -15,6 +17,10 @@ class WheelDistribution(AbstractDistribution):
This does not need any preparation as wheels can be directly unpacked.
"""
@property
def build_tracker_id(self) -> Optional[str]:
return None
def get_metadata_distribution(self) -> BaseDistribution:
"""Loads the metadata from the wheel file into memory and returns a
Distribution that uses it, not relying on the wheel file or
......
......@@ -198,7 +198,7 @@ class LinkEvaluator:
reason = f"wrong project name (not {self.project_name})"
return (LinkType.different_project, reason)
supported_tags = self._target_python.get_tags()
supported_tags = self._target_python.get_unsorted_tags()
if not wheel.supported(supported_tags):
# Include the wheel's tags in the reason string to
# simplify troubleshooting compatibility issues.
......@@ -414,7 +414,7 @@ class CandidateEvaluator:
if specifier is None:
specifier = specifiers.SpecifierSet()
supported_tags = target_python.get_tags()
supported_tags = target_python.get_sorted_tags()
return cls(
project_name=project_name,
......
import sys
from typing import List, Optional, Tuple
from typing import List, Optional, Set, Tuple
from pip._vendor.packaging.tags import Tag
......@@ -22,6 +22,7 @@ class TargetPython:
"py_version",
"py_version_info",
"_valid_tags",
"_valid_tags_set",
]
def __init__(
......@@ -61,8 +62,9 @@ class TargetPython:
self.py_version = py_version
self.py_version_info = py_version_info
# This is used to cache the return value of get_tags().
# This is used to cache the return value of get_(un)sorted_tags.
self._valid_tags: Optional[List[Tag]] = None
self._valid_tags_set: Optional[Set[Tag]] = None
def format_given(self) -> str:
"""
......@@ -84,7 +86,7 @@ class TargetPython:
f"{key}={value!r}" for key, value in key_values if value is not None
)
def get_tags(self) -> List[Tag]:
def get_sorted_tags(self) -> List[Tag]:
"""
Return the supported PEP 425 tags to check wheel candidates against.
......@@ -108,3 +110,13 @@ class TargetPython:
self._valid_tags = tags
return self._valid_tags
def get_unsorted_tags(self) -> Set[Tag]:
"""Exactly the same as get_sorted_tags, but returns a set.
This is important for performance.
"""
if self._valid_tags_set is None:
self._valid_tags_set = set(self.get_sorted_tags())
return self._valid_tags_set
......@@ -51,10 +51,22 @@ def get_build_tracker() -> Generator["BuildTracker", None, None]:
yield tracker
class TrackerId(str):
"""Uniquely identifying string provided to the build tracker."""
class BuildTracker:
"""Ensure that an sdist cannot request itself as a setup requirement.
When an sdist is prepared, it identifies its setup requirements in the
context of ``BuildTracker.track()``. If a requirement shows up recursively, this
raises an exception.
This stops fork bombs embedded in malicious packages."""
def __init__(self, root: str) -> None:
self._root = root
self._entries: Set[InstallRequirement] = set()
self._entries: Dict[TrackerId, InstallRequirement] = {}
logger.debug("Created build tracker: %s", self._root)
def __enter__(self) -> "BuildTracker":
......@@ -69,16 +81,15 @@ class BuildTracker:
) -> None:
self.cleanup()
def _entry_path(self, link: Link) -> str:
hashed = hashlib.sha224(link.url_without_fragment.encode()).hexdigest()
def _entry_path(self, key: TrackerId) -> str:
hashed = hashlib.sha224(key.encode()).hexdigest()
return os.path.join(self._root, hashed)
def add(self, req: InstallRequirement) -> None:
def add(self, req: InstallRequirement, key: TrackerId) -> None:
"""Add an InstallRequirement to build tracking."""
assert req.link
# Get the file to write information about this requirement.
entry_path = self._entry_path(req.link)
entry_path = self._entry_path(key)
# Try reading from the file. If it exists and can be read from, a build
# is already in progress, so a LookupError is raised.
......@@ -92,33 +103,37 @@ class BuildTracker:
raise LookupError(message)
# If we're here, req should really not be building already.
assert req not in self._entries
assert key not in self._entries
# Start tracking this requirement.
with open(entry_path, "w", encoding="utf-8") as fp:
fp.write(str(req))
self._entries.add(req)
self._entries[key] = req
logger.debug("Added %s to build tracker %r", req, self._root)
def remove(self, req: InstallRequirement) -> None:
def remove(self, req: InstallRequirement, key: TrackerId) -> None:
"""Remove an InstallRequirement from build tracking."""
assert req.link
# Delete the created file and the corresponding entries.
os.unlink(self._entry_path(req.link))
self._entries.remove(req)
# Delete the created file and the corresponding entry.
os.unlink(self._entry_path(key))
del self._entries[key]
logger.debug("Removed %s from build tracker %r", req, self._root)
def cleanup(self) -> None:
for req in set(self._entries):
self.remove(req)
for key, req in list(self._entries.items()):
self.remove(req, key)
logger.debug("Removed build tracker: %r", self._root)
@contextlib.contextmanager
def track(self, req: InstallRequirement) -> Generator[None, None, None]:
self.add(req)
def track(self, req: InstallRequirement, key: str) -> Generator[None, None, None]:
"""Ensure that `key` cannot install itself as a setup requirement.
:raises LookupError: If `key` was already provided in a parent invocation of
the context introduced by this method."""
tracker_id = TrackerId(key)
self.add(req, tracker_id)
yield
self.remove(req)
self.remove(req, tracker_id)
......@@ -4,7 +4,6 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import mimetypes
import os
import shutil
......@@ -37,6 +36,7 @@ from pip._internal.network.lazy_wheel import (
from pip._internal.network.session import PipSession
from pip._internal.operations.build.build_tracker import BuildTracker
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils._log import getLogger
from pip._internal.utils.direct_url_helpers import (
direct_url_for_editable,
direct_url_from_link,
......@@ -53,7 +53,7 @@ from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.unpacking import unpack_file
from pip._internal.vcs import vcs
logger = logging.getLogger(__name__)
logger = getLogger(__name__)
def _get_prepared_distribution(
......@@ -65,10 +65,12 @@ def _get_prepared_distribution(
) -> BaseDistribution:
"""Prepare a distribution for installation."""
abstract_dist = make_distribution_for_install_requirement(req)
with build_tracker.track(req):
abstract_dist.prepare_distribution_metadata(
finder, build_isolation, check_build_deps
)
tracker_id = abstract_dist.build_tracker_id
if tracker_id is not None:
with build_tracker.track(req, tracker_id):
abstract_dist.prepare_distribution_metadata(
finder, build_isolation, check_build_deps
)
return abstract_dist.get_metadata_distribution()
......@@ -394,7 +396,7 @@ class RequirementPreparer:
if metadata_link is None:
return None
assert req.req is not None
logger.info(
logger.verbose(
"Obtaining dependency information for %s from %s",
req.req,
metadata_link,
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import functools
import logging
import os
......@@ -244,6 +241,7 @@ class InstallRequirement:
@property
def specifier(self) -> SpecifierSet:
assert self.req is not None
return self.req.specifier
@property
......@@ -257,7 +255,8 @@ class InstallRequirement:
For example, some-package==1.2 is pinned; some-package>1.2 is not.
"""
specifiers = self.specifier
assert self.req is not None
specifiers = self.req.specifier
return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
......@@ -305,6 +304,7 @@ class InstallRequirement:
else:
link = None
if link and link.hash:
assert link.hash_name is not None
good_hashes.setdefault(link.hash_name, []).append(link.hash)
return Hashes(good_hashes)
......@@ -314,6 +314,7 @@ class InstallRequirement:
return None
s = str(self.req)
if self.comes_from:
comes_from: Optional[str]
if isinstance(self.comes_from, str):
comes_from = self.comes_from
else:
......@@ -345,7 +346,7 @@ class InstallRequirement:
# When parallel builds are enabled, add a UUID to the build directory
# name so multiple builds do not interfere with each other.
dir_name: str = canonicalize_name(self.name)
dir_name: str = canonicalize_name(self.req.name)
if parallel_builds:
dir_name = f"{dir_name}_{uuid.uuid4().hex}"
......@@ -388,6 +389,7 @@ class InstallRequirement:
)
def warn_on_mismatching_name(self) -> None:
assert self.req is not None
metadata_name = canonicalize_name(self.metadata["Name"])
if canonicalize_name(self.req.name) == metadata_name:
# Everything is fine.
......@@ -457,6 +459,7 @@ class InstallRequirement:
# Things valid for sdists
@property
def unpacked_source_directory(self) -> str:
assert self.source_dir, f"No source dir for {self}"
return os.path.join(
self.source_dir, self.link and self.link.subdirectory_fragment or ""
)
......@@ -543,7 +546,7 @@ class InstallRequirement:
Under PEP 517 and PEP 660, call the backend hook to prepare the metadata.
Under legacy processing, call setup.py egg-info.
"""
assert self.source_dir
assert self.source_dir, f"No source dir for {self}"
details = self.name or f"from {self.link}"
if self.use_pep517:
......@@ -592,8 +595,10 @@ class InstallRequirement:
if self.metadata_directory:
return get_directory_distribution(self.metadata_directory)
elif self.local_file_path and self.is_wheel:
assert self.req is not None
return get_wheel_distribution(
FilesystemWheel(self.local_file_path), canonicalize_name(self.name)
FilesystemWheel(self.local_file_path),
canonicalize_name(self.req.name),
)
raise AssertionError(
f"InstallRequirement {self} has no metadata directory and no wheel: "
......@@ -601,9 +606,9 @@ class InstallRequirement:
)
def assert_source_matches_version(self) -> None:
assert self.source_dir
assert self.source_dir, f"No source dir for {self}"
version = self.metadata["version"]
if self.req.specifier and version not in self.req.specifier:
if self.req and self.req.specifier and version not in self.req.specifier:
logger.warning(
"Requested %s, but installing version %s",
self,
......@@ -696,9 +701,10 @@ class InstallRequirement:
name = name.replace(os.path.sep, "/")
return name
assert self.req is not None
path = os.path.join(parentdir, path)
name = _clean_zip_name(path, rootdir)
return self.name + "/" + name
return self.req.name + "/" + name
def archive(self, build_dir: Optional[str]) -> None:
"""Saves archive to provided build_dir.
......@@ -777,8 +783,9 @@ class InstallRequirement:
use_user_site: bool = False,
pycompile: bool = True,
) -> None:
assert self.req is not None
scheme = get_scheme(
self.name,
self.req.name,
user=use_user_site,
home=home,
root=root,
......@@ -792,7 +799,7 @@ class InstallRequirement:
prefix=prefix,
home=home,
use_user_site=use_user_site,
name=self.name,
name=self.req.name,
setup_py_path=self.setup_py_path,
isolated=self.isolated,
build_env=self.build_env,
......@@ -805,7 +812,7 @@ class InstallRequirement:
assert self.local_file_path
install_wheel(
self.name,
self.req.name,
self.local_file_path,
scheme=scheme,
req_description=str(self.req),
......
......@@ -132,7 +132,7 @@ class Factory:
if not link.is_wheel:
return
wheel = Wheel(link.filename)
if wheel.supported(self._finder.target_python.get_tags()):
if wheel.supported(self._finder.target_python.get_unsorted_tags()):
return
msg = f"{link.filename} is not a supported wheel on this platform."
raise UnsupportedWheel(msg)
......
......@@ -14,7 +14,8 @@ import urllib.parse
from functools import partial
from io import StringIO
from itertools import filterfalse, tee, zip_longest
from types import TracebackType
from pathlib import Path
from types import FunctionType, TracebackType
from typing import (
Any,
BinaryIO,
......@@ -67,6 +68,8 @@ T = TypeVar("T")
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
VersionInfo = Tuple[int, int, int]
NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
OnExc = Callable[[FunctionType, Path, BaseException], Any]
OnErr = Callable[[FunctionType, Path, ExcInfo], Any]
def get_pip_version() -> str:
......@@ -127,16 +130,23 @@ def get_prog() -> str:
def rmtree(
dir: str,
ignore_errors: bool = False,
onexc: Optional[Callable[[Any, Any, Any], Any]] = None,
onexc: Optional[OnExc] = None,
) -> None:
if ignore_errors:
onexc = _onerror_ignore
elif onexc is None:
if onexc is None:
onexc = _onerror_reraise
handler: OnErr = partial(
# `[func, path, Union[ExcInfo, BaseException]] -> Any` is equivalent to
# `Union[([func, path, ExcInfo] -> Any), ([func, path, BaseException] -> Any)]`.
cast(Union[OnExc, OnErr], rmtree_errorhandler),
onexc=onexc,
)
if sys.version_info >= (3, 12):
shutil.rmtree(dir, onexc=partial(rmtree_errorhandler, onexc=onexc))
# See https://docs.python.org/3.12/whatsnew/3.12.html#shutil.
shutil.rmtree(dir, onexc=handler)
else:
shutil.rmtree(dir, onerror=partial(rmtree_errorhandler, onexc=onexc))
shutil.rmtree(dir, onerror=handler)
def _onerror_ignore(*_args: Any) -> None:
......@@ -148,11 +158,11 @@ def _onerror_reraise(*_args: Any) -> None:
def rmtree_errorhandler(
func: Callable[..., Any],
path: str,
func: FunctionType,
path: Path,
exc_info: Union[ExcInfo, BaseException],
*,
onexc: Callable[..., Any] = _onerror_reraise,
onexc: OnExc = _onerror_reraise,
) -> None:
"""
`rmtree` error handler to 'force' a file remove (i.e. like `rm -f`).
......@@ -183,6 +193,8 @@ def rmtree_errorhandler(
except OSError:
pass
if not isinstance(exc_info, BaseException):
_, exc_info, _ = exc_info
onexc(func, path, exc_info)
......
......@@ -5,15 +5,14 @@ import os.path
import tempfile
import traceback
from contextlib import ExitStack, contextmanager
from pathlib import Path
from types import FunctionType
from typing import (
Any,
Callable,
Dict,
Generator,
List,
Optional,
Tuple,
Type,
TypeVar,
Union,
)
......@@ -188,22 +187,24 @@ class TempDirectory:
errors: List[BaseException] = []
def onerror(
func: Callable[[str], Any],
path: str,
exc_info: Tuple[Type[BaseException], BaseException, Any],
func: FunctionType,
path: Path,
exc_val: BaseException,
) -> None:
"""Log a warning for a `rmtree` error and continue"""
exc_val = "\n".join(traceback.format_exception_only(*exc_info[:2]))
exc_val = exc_val.rstrip() # remove trailing new line
formatted_exc = "\n".join(
traceback.format_exception_only(type(exc_val), exc_val)
)
formatted_exc = formatted_exc.rstrip() # remove trailing new line
if func in (os.unlink, os.remove, os.rmdir):
logger.debug(
"Failed to remove a temporary file '%s' due to %s.\n",
path,
exc_val,
formatted_exc,
)
else:
logger.debug("%s failed with %s.", func.__qualname__, exc_val)
errors.append(exc_info[1])
logger.debug("%s failed with %s.", func.__qualname__, formatted_exc)
errors.append(exc_val)
if self.ignore_cleanup_errors:
try:
......
from .core import contents, where
__all__ = ["contents", "where"]
__version__ = "2023.05.07"
__version__ = "2023.07.22"
此差异已折叠。
......@@ -8,7 +8,7 @@ platformdirs==3.8.1
pyparsing==3.1.0
pyproject-hooks==1.0.0
requests==2.31.0
certifi==2023.5.7
certifi==2023.7.22
chardet==5.1.0
idna==3.4
urllib3==1.26.16
......
......@@ -1187,7 +1187,7 @@ def create_basic_wheel_for_package(
# Fix wheel distribution name by replacing runs of non-alphanumeric
# characters with an underscore _ as per PEP 491
name = re.sub(r"[^\w\d.]+", "_", name, re.UNICODE)
name = re.sub(r"[^\w\d.]+", "_", name)
archive_name = f"{name}-{version}-py2.py3-none-any.whl"
archive_path = script.scratch_path / archive_name
......
......@@ -88,12 +88,12 @@ class TestTargetPython:
((3, 7, 3), "37"),
# Check a minor version with two digits.
((3, 10, 1), "310"),
# Check that versions=None is passed to get_tags().
# Check that versions=None is passed to get_sorted_tags().
(None, None),
],
)
@mock.patch("pip._internal.models.target_python.get_supported")
def test_get_tags(
def test_get_sorted_tags(
self,
mock_get_supported: mock.Mock,
py_version_info: Optional[Tuple[int, ...]],
......@@ -102,7 +102,7 @@ class TestTargetPython:
mock_get_supported.return_value = ["tag-1", "tag-2"]
target_python = TargetPython(py_version_info=py_version_info)
actual = target_python.get_tags()
actual = target_python.get_sorted_tags()
assert actual == ["tag-1", "tag-2"]
actual = mock_get_supported.call_args[1]["version"]
......@@ -111,14 +111,14 @@ class TestTargetPython:
# Check that the value was cached.
assert target_python._valid_tags == ["tag-1", "tag-2"]
def test_get_tags__uses_cached_value(self) -> None:
def test_get_unsorted_tags__uses_cached_value(self) -> None:
"""
Test that get_tags() uses the cached value.
Test that get_unsorted_tags() uses the cached value.
"""
target_python = TargetPython(py_version_info=None)
target_python._valid_tags = [
target_python._valid_tags_set = {
Tag("py2", "none", "any"),
Tag("py3", "none", "any"),
]
actual = target_python.get_tags()
assert actual == [Tag("py2", "none", "any"), Tag("py3", "none", "any")]
}
actual = target_python.get_unsorted_tags()
assert actual == {Tag("py2", "none", "any"), Tag("py3", "none", "any")}