diff --git a/news/certifi.vendor.rst b/news/certifi.vendor.rst new file mode 100644 index 00000000000..bc4ad30e7f9 --- /dev/null +++ b/news/certifi.vendor.rst @@ -0,0 +1 @@ +Upgrade certifi to 2024.7.4 diff --git a/news/platformdirs.vendor.rst b/news/platformdirs.vendor.rst new file mode 100644 index 00000000000..b2007b95b09 --- /dev/null +++ b/news/platformdirs.vendor.rst @@ -0,0 +1 @@ +Upgrade platformdirs to 4.2.2 diff --git a/news/pygments.vendor.rst b/news/pygments.vendor.rst new file mode 100644 index 00000000000..5232695fa02 --- /dev/null +++ b/news/pygments.vendor.rst @@ -0,0 +1 @@ +Upgrade pygments to 2.18.0 diff --git a/news/setuptools.vendor.rst b/news/setuptools.vendor.rst new file mode 100644 index 00000000000..530d8912a07 --- /dev/null +++ b/news/setuptools.vendor.rst @@ -0,0 +1 @@ +Upgrade setuptools to 70.2.0 diff --git a/news/tenacity.vendor.rst b/news/tenacity.vendor.rst new file mode 100644 index 00000000000..bf973a69d6f --- /dev/null +++ b/news/tenacity.vendor.rst @@ -0,0 +1 @@ +Upgrade tenacity to 8.5.0 diff --git a/noxfile.py b/noxfile.py index 370e2fe630e..51bce160490 100644 --- a/noxfile.py +++ b/noxfile.py @@ -190,14 +190,14 @@ def vendoring(session: nox.Session) -> None: "python", "-c", "import sys; sys.exit(1 if sys.version_info < (3, 10) else 0)" ) - session.install("vendoring~=1.2.0") - parser = argparse.ArgumentParser(prog="nox -s vendoring") parser.add_argument("--upgrade-all", action="store_true") parser.add_argument("--upgrade", action="append", default=[]) parser.add_argument("--skip", action="append", default=[]) args = parser.parse_args(session.posargs) + session.install("vendoring~=1.2.0") + if not (args.upgrade or args.upgrade_all): session.run("vendoring", "sync", "-v") return diff --git a/src/pip/_vendor/certifi/__init__.py b/src/pip/_vendor/certifi/__init__.py index 1c91f3ec932..d321f1bc3ab 100644 --- a/src/pip/_vendor/certifi/__init__.py +++ b/src/pip/_vendor/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2024.02.02" +__version__ = "2024.07.04" diff --git a/src/pip/_vendor/certifi/cacert.pem b/src/pip/_vendor/certifi/cacert.pem index fac3c31909b..a6581589ba1 100644 --- a/src/pip/_vendor/certifi/cacert.pem +++ b/src/pip/_vendor/certifi/cacert.pem @@ -3485,46 +3485,6 @@ DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= -----END CERTIFICATE----- -# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH -# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH -# Label: "GLOBALTRUST 2020" -# Serial: 109160994242082918454945253 -# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 -# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 -# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a ------BEGIN CERTIFICATE----- -MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG -A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw -FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx -MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u -aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b -RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z -YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 -QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw -yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ -BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ -SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH -r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 -4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me -dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw -q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 -nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu -H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA -VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC -XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd -6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf -+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi -kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 -wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB -TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C -MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn -4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I -aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy -qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== ------END CERTIFICATE----- - # Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz # Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz # Label: "ANF Secure Server Root CA" @@ -4812,3 +4772,27 @@ X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= -----END CERTIFICATE----- + +# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA +# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA +# Label: "FIRMAPROFESIONAL CA ROOT-A WEB" +# Serial: 65916896770016886708751106294915943533 +# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3 +# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5 +# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf +e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C +cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O +BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw +hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG +XSaQpYXFuXqUPoeovQA= +-----END CERTIFICATE----- diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py index 417a537d6f6..d5b4601439b 100644 --- a/src/pip/_vendor/pkg_resources/__init__.py +++ b/src/pip/_vendor/pkg_resources/__init__.py @@ -1,3 +1,6 @@ +# TODO: Add Generic type annotations to initialized collections. +# For now we'd simply use implicit Any/Unknown which would add redundant annotations +# mypy: disable-error-code="var-annotated" """ Package resource API -------------------- @@ -17,9 +20,11 @@ :mod:`importlib.metadata` and :pypi:`packaging` instead. """ +from __future__ import annotations + import sys -if sys.version_info < (3, 8): +if sys.version_info < (3, 8): # noqa: UP036 # Check for unsupported versions raise RuntimeError("Python 3.8 or later is required") import os @@ -27,7 +32,24 @@ import time import re import types -from typing import List, Protocol +from typing import ( + Any, + Literal, + Dict, + Iterator, + Mapping, + MutableSequence, + NamedTuple, + NoReturn, + Tuple, + Union, + TYPE_CHECKING, + Protocol, + Callable, + Iterable, + TypeVar, + overload, +) import zipfile import zipimport import warnings @@ -46,6 +68,7 @@ import ntpath import posixpath import importlib +import importlib.abc import importlib.machinery from pkgutil import get_importer @@ -53,6 +76,8 @@ # capture these to bypass sandboxing from os import utime +from os import open as os_open +from os.path import isdir, split try: from os import mkdir, rename, unlink @@ -62,42 +87,20 @@ # no write support, probably under GAE WRITE_SUPPORT = False -from os import open as os_open -from os.path import isdir, split - from pip._internal.utils._jaraco_text import ( yield_lines, drop_comment, join_continuation, ) +from pip._vendor.packaging import markers as _packaging_markers +from pip._vendor.packaging import requirements as _packaging_requirements +from pip._vendor.packaging import utils as _packaging_utils +from pip._vendor.packaging import version as _packaging_version +from pip._vendor.platformdirs import user_cache_dir as _user_cache_dir -from pip._vendor import platformdirs -from pip._vendor import packaging - -__import__('pip._vendor.packaging.version') -__import__('pip._vendor.packaging.specifiers') -__import__('pip._vendor.packaging.requirements') -__import__('pip._vendor.packaging.markers') -__import__('pip._vendor.packaging.utils') - -# declare some globals that will be defined later to -# satisfy the linters. -require = None -working_set = None -add_activation_listener = None -cleanup_resources = None -resource_stream = None -set_extraction_path = None -resource_isdir = None -resource_string = None -iter_entry_points = None -resource_listdir = None -resource_filename = None -resource_exists = None -_distribution_finders = None -_namespace_handlers = None -_namespace_packages = None - +if TYPE_CHECKING: + from _typeshed import BytesPath, StrPath, StrOrBytesPath + from pip._vendor.typing_extensions import Self warnings.warn( "pkg_resources is deprecated as an API. " @@ -107,6 +110,37 @@ ) +_T = TypeVar("_T") +_DistributionT = TypeVar("_DistributionT", bound="Distribution") +# Type aliases +_NestedStr = Union[str, Iterable[Union[str, Iterable["_NestedStr"]]]] +_InstallerTypeT = Callable[["Requirement"], "_DistributionT"] +_InstallerType = Callable[["Requirement"], Union["Distribution", None]] +_PkgReqType = Union[str, "Requirement"] +_EPDistType = Union["Distribution", _PkgReqType] +_MetadataType = Union["IResourceProvider", None] +_ResolvedEntryPoint = Any # Can be any attribute in the module +_ResourceStream = Any # TODO / Incomplete: A readable file-like object +# Any object works, but let's indicate we expect something like a module (optionally has __loader__ or __file__) +_ModuleLike = Union[object, types.ModuleType] +# Any: Should be _ModuleLike but we end up with issues where _ModuleLike doesn't have _ZipLoaderModule's __loader__ +_ProviderFactoryType = Callable[[Any], "IResourceProvider"] +_DistFinderType = Callable[[_T, str, bool], Iterable["Distribution"]] +_NSHandlerType = Callable[[_T, str, str, types.ModuleType], Union[str, None]] +_AdapterT = TypeVar( + "_AdapterT", _DistFinderType[Any], _ProviderFactoryType, _NSHandlerType[Any] +) + + +# Use _typeshed.importlib.LoaderProtocol once available https://github.com/python/typeshed/pull/11890 +class _LoaderProtocol(Protocol): + def load_module(self, fullname: str, /) -> types.ModuleType: ... + + +class _ZipLoaderModule(Protocol): + __loader__: zipimport.zipimporter + + _PEP440_FALLBACK = re.compile(r"^v?(?P(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I) @@ -117,18 +151,18 @@ class PEP440Warning(RuntimeWarning): """ -parse_version = packaging.version.Version +parse_version = _packaging_version.Version -_state_vars = {} +_state_vars: dict[str, str] = {} -def _declare_state(vartype, **kw): - globals().update(kw) - _state_vars.update(dict.fromkeys(kw, vartype)) +def _declare_state(vartype: str, varname: str, initial_value: _T) -> _T: + _state_vars[varname] = vartype + return initial_value -def __getstate__(): +def __getstate__() -> dict[str, Any]: state = {} g = globals() for k, v in _state_vars.items(): @@ -136,7 +170,7 @@ def __getstate__(): return state -def __setstate__(state): +def __setstate__(state: dict[str, Any]) -> dict[str, Any]: g = globals() for k, v in state.items(): g['_sset_' + _state_vars[k]](k, g[k], v) @@ -291,17 +325,17 @@ class VersionConflict(ResolutionError): _template = "{self.dist} is installed but {self.req} is required" @property - def dist(self): + def dist(self) -> Distribution: return self.args[0] @property - def req(self): + def req(self) -> Requirement: return self.args[1] def report(self): return self._template.format(**locals()) - def with_context(self, required_by): + def with_context(self, required_by: set[Distribution | str]): """ If required_by is non-empty, return a version of self that is a ContextualVersionConflict. @@ -321,7 +355,7 @@ class ContextualVersionConflict(VersionConflict): _template = VersionConflict._template + ' by {self.required_by}' @property - def required_by(self): + def required_by(self) -> set[str]: return self.args[2] @@ -334,11 +368,11 @@ class DistributionNotFound(ResolutionError): ) @property - def req(self): + def req(self) -> Requirement: return self.args[0] @property - def requirers(self): + def requirers(self) -> set[str] | None: return self.args[1] @property @@ -358,7 +392,7 @@ class UnknownExtra(ResolutionError): """Distribution doesn't have an "extra feature" of the given name""" -_provider_factories = {} +_provider_factories: dict[type[_ModuleLike], _ProviderFactoryType] = {} PY_MAJOR = '{}.{}'.format(*sys.version_info) EGG_DIST = 3 @@ -368,7 +402,9 @@ class UnknownExtra(ResolutionError): DEVELOP_DIST = -1 -def register_loader_type(loader_type, provider_factory): +def register_loader_type( + loader_type: type[_ModuleLike], provider_factory: _ProviderFactoryType +): """Register `provider_factory` to make providers for `loader_type` `loader_type` is the type or class of a PEP 302 ``module.__loader__``, @@ -378,7 +414,11 @@ def register_loader_type(loader_type, provider_factory): _provider_factories[loader_type] = provider_factory -def get_provider(moduleOrReq): +@overload +def get_provider(moduleOrReq: str) -> IResourceProvider: ... +@overload +def get_provider(moduleOrReq: Requirement) -> Distribution: ... +def get_provider(moduleOrReq: str | Requirement) -> IResourceProvider | Distribution: """Return an IResourceProvider for the named module or requirement""" if isinstance(moduleOrReq, Requirement): return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] @@ -440,7 +480,7 @@ def get_build_platform(): get_platform = get_build_platform -def compatible_platforms(provided, required): +def compatible_platforms(provided: str | None, required: str | None): """Can code for the `provided` platform run on the `required` platform? Returns true if either platform is ``None``, or the platforms are equal. @@ -489,89 +529,106 @@ def compatible_platforms(provided, required): return False -def get_distribution(dist): +@overload +def get_distribution(dist: _DistributionT) -> _DistributionT: ... +@overload +def get_distribution(dist: _PkgReqType) -> Distribution: ... +def get_distribution(dist: Distribution | _PkgReqType) -> Distribution: """Return a current distribution object for a Requirement or string""" if isinstance(dist, str): dist = Requirement.parse(dist) if isinstance(dist, Requirement): - dist = get_provider(dist) + # Bad type narrowing, dist has to be a Requirement here, so get_provider has to return Distribution + dist = get_provider(dist) # type: ignore[assignment] if not isinstance(dist, Distribution): - raise TypeError("Expected string, Requirement, or Distribution", dist) + raise TypeError("Expected str, Requirement, or Distribution", dist) return dist -def load_entry_point(dist, group, name): +def load_entry_point(dist: _EPDistType, group: str, name: str) -> _ResolvedEntryPoint: """Return `name` entry point of `group` for `dist` or raise ImportError""" return get_distribution(dist).load_entry_point(group, name) -def get_entry_map(dist, group=None): +@overload +def get_entry_map( + dist: _EPDistType, group: None = None +) -> dict[str, dict[str, EntryPoint]]: ... +@overload +def get_entry_map(dist: _EPDistType, group: str) -> dict[str, EntryPoint]: ... +def get_entry_map(dist: _EPDistType, group: str | None = None): """Return the entry point map for `group`, or the full entry map""" return get_distribution(dist).get_entry_map(group) -def get_entry_info(dist, group, name): +def get_entry_info(dist: _EPDistType, group: str, name: str): """Return the EntryPoint object for `group`+`name`, or ``None``""" return get_distribution(dist).get_entry_info(group, name) class IMetadataProvider(Protocol): - def has_metadata(self, name) -> bool: + def has_metadata(self, name: str) -> bool: """Does the package's distribution contain the named metadata?""" - def get_metadata(self, name): + def get_metadata(self, name: str) -> str: """The named metadata resource as a string""" - def get_metadata_lines(self, name): + def get_metadata_lines(self, name: str) -> Iterator[str]: """Yield named metadata resource as list of non-blank non-comment lines Leading and trailing whitespace is stripped from each line, and lines with ``#`` as the first non-blank character are omitted.""" - def metadata_isdir(self, name) -> bool: + def metadata_isdir(self, name: str) -> bool: """Is the named metadata a directory? (like ``os.path.isdir()``)""" - def metadata_listdir(self, name): + def metadata_listdir(self, name: str) -> list[str]: """List of metadata names in the directory (like ``os.listdir()``)""" - def run_script(self, script_name, namespace): + def run_script(self, script_name: str, namespace: dict[str, Any]) -> None: """Execute the named script in the supplied namespace dictionary""" class IResourceProvider(IMetadataProvider, Protocol): """An object that provides access to package resources""" - def get_resource_filename(self, manager, resource_name): + def get_resource_filename( + self, manager: ResourceManager, resource_name: str + ) -> str: """Return a true filesystem path for `resource_name` - `manager` must be an ``IResourceManager``""" + `manager` must be a ``ResourceManager``""" - def get_resource_stream(self, manager, resource_name): + def get_resource_stream( + self, manager: ResourceManager, resource_name: str + ) -> _ResourceStream: """Return a readable file-like object for `resource_name` - `manager` must be an ``IResourceManager``""" + `manager` must be a ``ResourceManager``""" - def get_resource_string(self, manager, resource_name) -> bytes: + def get_resource_string( + self, manager: ResourceManager, resource_name: str + ) -> bytes: """Return the contents of `resource_name` as :obj:`bytes` - `manager` must be an ``IResourceManager``""" + `manager` must be a ``ResourceManager``""" - def has_resource(self, resource_name): + def has_resource(self, resource_name: str) -> bool: """Does the package contain the named resource?""" - def resource_isdir(self, resource_name): + def resource_isdir(self, resource_name: str) -> bool: """Is the named resource a directory? (like ``os.path.isdir()``)""" - def resource_listdir(self, resource_name): + def resource_listdir(self, resource_name: str) -> list[str]: """List of resource names in the directory (like ``os.listdir()``)""" class WorkingSet: """A collection of active distributions on sys.path (or a similar list)""" - def __init__(self, entries=None): + def __init__(self, entries: Iterable[str] | None = None): """Create working set from list of path entries (default=sys.path)""" - self.entries = [] + self.entries: list[str] = [] self.entry_keys = {} self.by_key = {} self.normalized_to_canonical_keys = {} @@ -625,7 +682,7 @@ def _build_from_requirements(cls, req_spec): sys.path[:] = ws.entries return ws - def add_entry(self, entry): + def add_entry(self, entry: str): """Add a path item to ``.entries``, finding any distributions on it ``find_distributions(entry, True)`` is used to find distributions @@ -640,11 +697,11 @@ def add_entry(self, entry): for dist in find_distributions(entry, True): self.add(dist, entry, False) - def __contains__(self, dist): + def __contains__(self, dist: Distribution) -> bool: """True if `dist` is the active distribution for its project""" return self.by_key.get(dist.key) == dist - def find(self, req): + def find(self, req: Requirement) -> Distribution | None: """Find a distribution matching requirement `req` If there is an active distribution for the requested project, this @@ -668,7 +725,7 @@ def find(self, req): raise VersionConflict(dist, req) return dist - def iter_entry_points(self, group, name=None): + def iter_entry_points(self, group: str, name: str | None = None): """Yield entry point objects from `group` matching `name` If `name` is None, yields all entry points in `group` from all @@ -682,7 +739,7 @@ def iter_entry_points(self, group, name=None): if name is None or name == entry.name ) - def run_script(self, requires, script_name): + def run_script(self, requires: str, script_name: str): """Locate distribution for `requires` and run `script_name` script""" ns = sys._getframe(1).f_globals name = ns['__name__'] @@ -690,13 +747,13 @@ def run_script(self, requires, script_name): ns['__name__'] = name self.require(requires)[0].run_script(script_name, ns) - def __iter__(self): + def __iter__(self) -> Iterator[Distribution]: """Yield distributions for non-duplicate projects in the working set The yield order is the order in which the items' path entries were added to the working set. """ - seen = {} + seen = set() for item in self.entries: if item not in self.entry_keys: # workaround a cache issue @@ -704,10 +761,16 @@ def __iter__(self): for key in self.entry_keys[item]: if key not in seen: - seen[key] = 1 + seen.add(key) yield self.by_key[key] - def add(self, dist, entry=None, insert=True, replace=False): + def add( + self, + dist: Distribution, + entry: str | None = None, + insert: bool = True, + replace: bool = False, + ): """Add `dist` to working set, associated with `entry` If `entry` is unspecified, it defaults to the ``.location`` of `dist`. @@ -731,7 +794,7 @@ def add(self, dist, entry=None, insert=True, replace=False): return self.by_key[dist.key] = dist - normalized_name = packaging.utils.canonicalize_name(dist.key) + normalized_name = _packaging_utils.canonicalize_name(dist.key) self.normalized_to_canonical_keys[normalized_name] = dist.key if dist.key not in keys: keys.append(dist.key) @@ -739,14 +802,42 @@ def add(self, dist, entry=None, insert=True, replace=False): keys2.append(dist.key) self._added_new(dist) + @overload def resolve( self, - requirements, - env=None, - installer=None, - replace_conflicting=False, - extras=None, - ): + requirements: Iterable[Requirement], + env: Environment | None, + installer: _InstallerTypeT[_DistributionT], + replace_conflicting: bool = False, + extras: tuple[str, ...] | None = None, + ) -> list[_DistributionT]: ... + @overload + def resolve( + self, + requirements: Iterable[Requirement], + env: Environment | None = None, + *, + installer: _InstallerTypeT[_DistributionT], + replace_conflicting: bool = False, + extras: tuple[str, ...] | None = None, + ) -> list[_DistributionT]: ... + @overload + def resolve( + self, + requirements: Iterable[Requirement], + env: Environment | None = None, + installer: _InstallerType | None = None, + replace_conflicting: bool = False, + extras: tuple[str, ...] | None = None, + ) -> list[Distribution]: ... + def resolve( + self, + requirements: Iterable[Requirement], + env: Environment | None = None, + installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None, + replace_conflicting: bool = False, + extras: tuple[str, ...] | None = None, + ) -> list[Distribution] | list[_DistributionT]: """List all distributions needed to (recursively) meet `requirements` `requirements` must be a sequence of ``Requirement`` objects. `env`, @@ -774,7 +865,7 @@ def resolve( # set up the stack requirements = list(requirements)[::-1] # set of processed requirements - processed = {} + processed = set() # key -> dist best = {} to_activate = [] @@ -808,14 +899,14 @@ def resolve( required_by[new_requirement].add(req.project_name) req_extras[new_requirement] = req.extras - processed[req] = True + processed.add(req) # return list of distros to activate return to_activate def _resolve_dist( self, req, best, replace_conflicting, env, installer, required_by, to_activate - ): + ) -> Distribution: dist = best.get(req.key) if dist is None: # Find the best distribution and add it to the map @@ -844,7 +935,41 @@ def _resolve_dist( raise VersionConflict(dist, req).with_context(dependent_req) return dist - def find_plugins(self, plugin_env, full_env=None, installer=None, fallback=True): + @overload + def find_plugins( + self, + plugin_env: Environment, + full_env: Environment | None, + installer: _InstallerTypeT[_DistributionT], + fallback: bool = True, + ) -> tuple[list[_DistributionT], dict[Distribution, Exception]]: ... + @overload + def find_plugins( + self, + plugin_env: Environment, + full_env: Environment | None = None, + *, + installer: _InstallerTypeT[_DistributionT], + fallback: bool = True, + ) -> tuple[list[_DistributionT], dict[Distribution, Exception]]: ... + @overload + def find_plugins( + self, + plugin_env: Environment, + full_env: Environment | None = None, + installer: _InstallerType | None = None, + fallback: bool = True, + ) -> tuple[list[Distribution], dict[Distribution, Exception]]: ... + def find_plugins( + self, + plugin_env: Environment, + full_env: Environment | None = None, + installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None, + fallback: bool = True, + ) -> tuple[ + list[Distribution] | list[_DistributionT], + dict[Distribution, Exception], + ]: """Find all activatable distributions in `plugin_env` Example usage:: @@ -883,8 +1008,8 @@ def find_plugins(self, plugin_env, full_env=None, installer=None, fallback=True) # scan project names in alphabetic order plugin_projects.sort() - error_info = {} - distributions = {} + error_info: dict[Distribution, Exception] = {} + distributions: dict[Distribution, Exception | None] = {} if full_env is None: env = Environment(self.entries) @@ -920,12 +1045,12 @@ def find_plugins(self, plugin_env, full_env=None, installer=None, fallback=True) # success, no need to try any more versions of this project break - distributions = list(distributions) - distributions.sort() + sorted_distributions = list(distributions) + sorted_distributions.sort() - return distributions, error_info + return sorted_distributions, error_info - def require(self, *requirements): + def require(self, *requirements: _NestedStr): """Ensure that distributions matching `requirements` are activated `requirements` must be a string or a (possibly-nested) sequence @@ -941,7 +1066,9 @@ def require(self, *requirements): return needed - def subscribe(self, callback, existing=True): + def subscribe( + self, callback: Callable[[Distribution], object], existing: bool = True + ): """Invoke `callback` for all distributions If `existing=True` (default), @@ -977,12 +1104,12 @@ def __setstate__(self, e_k_b_n_c): self.callbacks = callbacks[:] -class _ReqExtras(dict): +class _ReqExtras(Dict["Requirement", Tuple[str, ...]]): """ Map each requirement to the extras that demanded it. """ - def markers_pass(self, req, extras=None): + def markers_pass(self, req: Requirement, extras: tuple[str, ...] | None = None): """ Evaluate markers for req against each extra that demanded it. @@ -1001,7 +1128,10 @@ class Environment: """Searchable snapshot of distributions on a search path""" def __init__( - self, search_path=None, platform=get_supported_platform(), python=PY_MAJOR + self, + search_path: Iterable[str] | None = None, + platform: str | None = get_supported_platform(), + python: str | None = PY_MAJOR, ): """Snapshot distributions available on a search path @@ -1024,7 +1154,7 @@ def __init__( self.python = python self.scan(search_path) - def can_add(self, dist): + def can_add(self, dist: Distribution): """Is distribution `dist` acceptable for this environment? The distribution must match the platform and python version @@ -1038,11 +1168,11 @@ def can_add(self, dist): ) return py_compat and compatible_platforms(dist.platform, self.platform) - def remove(self, dist): + def remove(self, dist: Distribution): """Remove `dist` from the environment""" self._distmap[dist.key].remove(dist) - def scan(self, search_path=None): + def scan(self, search_path: Iterable[str] | None = None): """Scan `search_path` for distributions usable in this environment Any distributions found are added to the environment. @@ -1057,7 +1187,7 @@ def scan(self, search_path=None): for dist in find_distributions(item): self.add(dist) - def __getitem__(self, project_name): + def __getitem__(self, project_name: str) -> list[Distribution]: """Return a newest-to-oldest list of distributions for `project_name` Uses case-insensitive `project_name` comparison, assuming all the @@ -1068,7 +1198,7 @@ def __getitem__(self, project_name): distribution_key = project_name.lower() return self._distmap.get(distribution_key, []) - def add(self, dist): + def add(self, dist: Distribution): """Add `dist` if we ``can_add()`` it and it has not already been added""" if self.can_add(dist) and dist.has_version(): dists = self._distmap.setdefault(dist.key, []) @@ -1076,7 +1206,29 @@ def add(self, dist): dists.append(dist) dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) - def best_match(self, req, working_set, installer=None, replace_conflicting=False): + @overload + def best_match( + self, + req: Requirement, + working_set: WorkingSet, + installer: _InstallerTypeT[_DistributionT], + replace_conflicting: bool = False, + ) -> _DistributionT: ... + @overload + def best_match( + self, + req: Requirement, + working_set: WorkingSet, + installer: _InstallerType | None = None, + replace_conflicting: bool = False, + ) -> Distribution | None: ... + def best_match( + self, + req: Requirement, + working_set: WorkingSet, + installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None, + replace_conflicting: bool = False, + ) -> Distribution | None: """Find distribution best matching `req` and usable on `working_set` This calls the ``find(req)`` method of the `working_set` to see if a @@ -1103,7 +1255,32 @@ def best_match(self, req, working_set, installer=None, replace_conflicting=False # try to download/install return self.obtain(req, installer) - def obtain(self, requirement, installer=None): + @overload + def obtain( + self, + requirement: Requirement, + installer: _InstallerTypeT[_DistributionT], + ) -> _DistributionT: ... + @overload + def obtain( + self, + requirement: Requirement, + installer: Callable[[Requirement], None] | None = None, + ) -> None: ... + @overload + def obtain( + self, + requirement: Requirement, + installer: _InstallerType | None = None, + ) -> Distribution | None: ... + def obtain( + self, + requirement: Requirement, + installer: Callable[[Requirement], None] + | _InstallerType + | None + | _InstallerTypeT[_DistributionT] = None, + ) -> Distribution | None: """Obtain a distribution matching `requirement` (e.g. via download) Obtain a distro that matches requirement (e.g. via download). In the @@ -1114,13 +1291,13 @@ def obtain(self, requirement, installer=None): to the `installer` argument.""" return installer(requirement) if installer else None - def __iter__(self): + def __iter__(self) -> Iterator[str]: """Yield the unique project names of the available distributions""" for key in self._distmap.keys(): if self[key]: yield key - def __iadd__(self, other): + def __iadd__(self, other: Distribution | Environment): """In-place addition of a distribution or environment""" if isinstance(other, Distribution): self.add(other) @@ -1132,7 +1309,7 @@ def __iadd__(self, other): raise TypeError("Can't add %r to environment" % (other,)) return self - def __add__(self, other): + def __add__(self, other: Distribution | Environment): """Add an environment or distribution to an environment""" new = self.__class__([], platform=None, python=None) for env in self, other: @@ -1159,46 +1336,54 @@ class ExtractionError(RuntimeError): The exception instance that caused extraction to fail """ + manager: ResourceManager + cache_path: str + original_error: BaseException | None + class ResourceManager: """Manage resource extraction and packages""" - extraction_path = None + extraction_path: str | None = None def __init__(self): self.cached_files = {} - def resource_exists(self, package_or_requirement, resource_name): + def resource_exists(self, package_or_requirement: _PkgReqType, resource_name: str): """Does the named resource exist?""" return get_provider(package_or_requirement).has_resource(resource_name) - def resource_isdir(self, package_or_requirement, resource_name): + def resource_isdir(self, package_or_requirement: _PkgReqType, resource_name: str): """Is the named resource an existing directory?""" return get_provider(package_or_requirement).resource_isdir(resource_name) - def resource_filename(self, package_or_requirement, resource_name): + def resource_filename( + self, package_or_requirement: _PkgReqType, resource_name: str + ): """Return a true filesystem path for specified resource""" return get_provider(package_or_requirement).get_resource_filename( self, resource_name ) - def resource_stream(self, package_or_requirement, resource_name): + def resource_stream(self, package_or_requirement: _PkgReqType, resource_name: str): """Return a readable file-like object for specified resource""" return get_provider(package_or_requirement).get_resource_stream( self, resource_name ) - def resource_string(self, package_or_requirement, resource_name) -> bytes: + def resource_string( + self, package_or_requirement: _PkgReqType, resource_name: str + ) -> bytes: """Return specified resource as :obj:`bytes`""" return get_provider(package_or_requirement).get_resource_string( self, resource_name ) - def resource_listdir(self, package_or_requirement, resource_name): + def resource_listdir(self, package_or_requirement: _PkgReqType, resource_name: str): """List the contents of the named resource directory""" return get_provider(package_or_requirement).resource_listdir(resource_name) - def extraction_error(self): + def extraction_error(self) -> NoReturn: """Give an error message for problems extracting file(s)""" old_exc = sys.exc_info()[1] @@ -1228,7 +1413,7 @@ def extraction_error(self): err.original_error = old_exc raise err - def get_cache_path(self, archive_name, names=()): + def get_cache_path(self, archive_name: str, names: Iterable[StrPath] = ()): """Return absolute location in cache for `archive_name` and `names` The parent directory of the resulting path will be created if it does @@ -1250,7 +1435,7 @@ def get_cache_path(self, archive_name, names=()): self._warn_unsafe_extraction_path(extract_path) - self.cached_files[target_path] = 1 + self.cached_files[target_path] = True return target_path @staticmethod @@ -1280,7 +1465,7 @@ def _warn_unsafe_extraction_path(path): ).format(**locals()) warnings.warn(msg, UserWarning) - def postprocess(self, tempname, filename): + def postprocess(self, tempname: StrOrBytesPath, filename: StrOrBytesPath): """Perform any platform-specific postprocessing of `tempname` This is where Mac header rewrites should be done; other platforms don't @@ -1300,7 +1485,7 @@ def postprocess(self, tempname, filename): mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777 os.chmod(tempname, mode) - def set_extraction_path(self, path): + def set_extraction_path(self, path: str): """Set the base path where resources will be extracted to, if needed. If you do not call this routine before any extractions take place, the @@ -1324,7 +1509,7 @@ def set_extraction_path(self, path): self.extraction_path = path - def cleanup_resources(self, force=False) -> List[str]: + def cleanup_resources(self, force: bool = False) -> list[str]: """ Delete all extracted resource files and directories, returning a list of the file and directory names that could not be successfully removed. @@ -1339,18 +1524,16 @@ def cleanup_resources(self, force=False) -> List[str]: return [] -def get_default_cache(): +def get_default_cache() -> str: """ Return the ``PYTHON_EGG_CACHE`` environment variable or a platform-relevant user cache dir for an app named "Python-Eggs". """ - return os.environ.get('PYTHON_EGG_CACHE') or platformdirs.user_cache_dir( - appname='Python-Eggs' - ) + return os.environ.get('PYTHON_EGG_CACHE') or _user_cache_dir(appname='Python-Eggs') -def safe_name(name): +def safe_name(name: str): """Convert an arbitrary string to a standard distribution name Any runs of non-alphanumeric/. characters are replaced with a single '-'. @@ -1358,14 +1541,14 @@ def safe_name(name): return re.sub('[^A-Za-z0-9.]+', '-', name) -def safe_version(version): +def safe_version(version: str): """ Convert an arbitrary string to a standard version string """ try: # normalize the version - return str(packaging.version.Version(version)) - except packaging.version.InvalidVersion: + return str(_packaging_version.Version(version)) + except _packaging_version.InvalidVersion: version = version.replace(' ', '.') return re.sub('[^A-Za-z0-9.]+', '-', version) @@ -1402,7 +1585,7 @@ def _safe_segment(segment): return re.sub(r'\.[^A-Za-z0-9]+', '.', segment).strip(".-") -def safe_extra(extra): +def safe_extra(extra: str): """Convert an arbitrary string to a standard 'extra' name Any runs of non-alphanumeric characters are replaced with a single '_', @@ -1411,7 +1594,7 @@ def safe_extra(extra): return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() -def to_filename(name): +def to_filename(name: str): """Convert a project or version name to its filename-escaped form Any '-' characters are currently replaced with '_'. @@ -1419,7 +1602,7 @@ def to_filename(name): return name.replace('-', '_') -def invalid_marker(text): +def invalid_marker(text: str): """ Validate text as a PEP 508 environment marker; return an exception if invalid or False otherwise. @@ -1433,7 +1616,7 @@ def invalid_marker(text): return False -def evaluate_marker(text, extra=None): +def evaluate_marker(text: str, extra: str | None = None) -> bool: """ Evaluate a PEP 508 environment marker. Return a boolean indicating the marker result in this environment. @@ -1442,46 +1625,48 @@ def evaluate_marker(text, extra=None): This implementation uses the 'pyparsing' module. """ try: - marker = packaging.markers.Marker(text) + marker = _packaging_markers.Marker(text) return marker.evaluate() - except packaging.markers.InvalidMarker as e: + except _packaging_markers.InvalidMarker as e: raise SyntaxError(e) from e class NullProvider: """Try to implement resources and metadata for arbitrary PEP 302 loaders""" - egg_name = None - egg_info = None - loader = None + egg_name: str | None = None + egg_info: str | None = None + loader: _LoaderProtocol | None = None - def __init__(self, module): + def __init__(self, module: _ModuleLike): self.loader = getattr(module, '__loader__', None) self.module_path = os.path.dirname(getattr(module, '__file__', '')) - def get_resource_filename(self, manager, resource_name): + def get_resource_filename(self, manager: ResourceManager, resource_name: str): return self._fn(self.module_path, resource_name) - def get_resource_stream(self, manager, resource_name): + def get_resource_stream(self, manager: ResourceManager, resource_name: str): return io.BytesIO(self.get_resource_string(manager, resource_name)) - def get_resource_string(self, manager, resource_name) -> bytes: + def get_resource_string( + self, manager: ResourceManager, resource_name: str + ) -> bytes: return self._get(self._fn(self.module_path, resource_name)) - def has_resource(self, resource_name): + def has_resource(self, resource_name: str): return self._has(self._fn(self.module_path, resource_name)) def _get_metadata_path(self, name): return self._fn(self.egg_info, name) - def has_metadata(self, name) -> bool: + def has_metadata(self, name: str) -> bool: if not self.egg_info: return False path = self._get_metadata_path(name) return self._has(path) - def get_metadata(self, name): + def get_metadata(self, name: str): if not self.egg_info: return "" path = self._get_metadata_path(name) @@ -1494,24 +1679,24 @@ def get_metadata(self, name): exc.reason += ' in {} file at path: {}'.format(name, path) raise - def get_metadata_lines(self, name): + def get_metadata_lines(self, name: str) -> Iterator[str]: return yield_lines(self.get_metadata(name)) - def resource_isdir(self, resource_name): + def resource_isdir(self, resource_name: str): return self._isdir(self._fn(self.module_path, resource_name)) - def metadata_isdir(self, name) -> bool: + def metadata_isdir(self, name: str) -> bool: return bool(self.egg_info and self._isdir(self._fn(self.egg_info, name))) - def resource_listdir(self, resource_name): + def resource_listdir(self, resource_name: str): return self._listdir(self._fn(self.module_path, resource_name)) - def metadata_listdir(self, name): + def metadata_listdir(self, name: str) -> list[str]: if self.egg_info: return self._listdir(self._fn(self.egg_info, name)) return [] - def run_script(self, script_name, namespace): + def run_script(self, script_name: str, namespace: dict[str, Any]): script = 'scripts/' + script_name if not self.has_metadata(script): raise ResolutionError( @@ -1519,13 +1704,13 @@ def run_script(self, script_name, namespace): **locals() ), ) + script_text = self.get_metadata(script).replace('\r\n', '\n') script_text = script_text.replace('\r', '\n') script_filename = self._fn(self.egg_info, script) namespace['__file__'] = script_filename if os.path.exists(script_filename): - with open(script_filename) as fid: - source = fid.read() + source = _read_utf8_with_fallback(script_filename) code = compile(source, script_filename, 'exec') exec(code, namespace, namespace) else: @@ -1550,12 +1735,16 @@ def _isdir(self, path) -> bool: "Can't perform this operation for unregistered loader type" ) - def _listdir(self, path): + def _listdir(self, path) -> list[str]: raise NotImplementedError( "Can't perform this operation for unregistered loader type" ) - def _fn(self, base, resource_name): + def _fn(self, base: str | None, resource_name: str): + if base is None: + raise TypeError( + "`base` parameter in `_fn` is `None`. Either override this method or check the parameter first." + ) self._validate_resource_path(resource_name) if resource_name: return os.path.join(base, *resource_name.split('/')) @@ -1618,6 +1807,7 @@ def _validate_resource_path(path): os.path.pardir in path.split(posixpath.sep) or posixpath.isabs(path) or ntpath.isabs(path) + or path.startswith("\\") ) if not invalid: return @@ -1625,7 +1815,7 @@ def _validate_resource_path(path): msg = "Use of .. or absolute path in a resource path is not allowed." # Aggressively disallow Windows absolute paths - if ntpath.isabs(path) and not posixpath.isabs(path): + if (path.startswith("\\") or ntpath.isabs(path)) and not posixpath.isabs(path): raise ValueError(msg) # for compatibility, warn; in future @@ -1636,8 +1826,9 @@ def _validate_resource_path(path): ) def _get(self, path) -> bytes: - if hasattr(self.loader, 'get_data'): - return self.loader.get_data(path) + if hasattr(self.loader, 'get_data') and self.loader: + # Already checked get_data exists + return self.loader.get_data(path) # type: ignore[attr-defined] raise NotImplementedError( "Can't perform this operation for loaders without 'get_data()'" ) @@ -1660,7 +1851,7 @@ def _parents(path): class EggProvider(NullProvider): """Provider based on a virtual filesystem""" - def __init__(self, module): + def __init__(self, module: _ModuleLike): super().__init__(module) self._setup_prefix() @@ -1671,7 +1862,7 @@ def _setup_prefix(self): egg = next(eggs, None) egg and self._set_egg(egg) - def _set_egg(self, path): + def _set_egg(self, path: str): self.egg_name = os.path.basename(path) self.egg_info = os.path.join(path, 'EGG-INFO') self.egg_root = path @@ -1689,7 +1880,7 @@ def _isdir(self, path) -> bool: def _listdir(self, path): return os.listdir(path) - def get_resource_stream(self, manager, resource_name): + def get_resource_stream(self, manager: object, resource_name: str): return open(self._fn(self.module_path, resource_name), 'rb') def _get(self, path) -> bytes: @@ -1713,7 +1904,8 @@ def _register(cls): class EmptyProvider(NullProvider): """Provider that returns nothing for all requests""" - module_path = None + # A special case, we don't want all Providers inheriting from NullProvider to have a potentially None module_path + module_path: str | None = None # type: ignore[assignment] _isdir = _has = lambda self, path: False @@ -1730,13 +1922,14 @@ def __init__(self): empty_provider = EmptyProvider() -class ZipManifests(dict): +class ZipManifests(Dict[str, "MemoizedZipManifests.manifest_mod"]): """ zip manifest builder """ + # `path` could be `StrPath | IO[bytes]` but that violates the LSP for `MemoizedZipManifests.load` @classmethod - def build(cls, path): + def build(cls, path: str): """ Build a dictionary similar to the zipimport directory caches, except instead of tuples, store ZipInfo objects. @@ -1762,9 +1955,11 @@ class MemoizedZipManifests(ZipManifests): Memoized zipfile manifests. """ - manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') + class manifest_mod(NamedTuple): + manifest: dict[str, zipfile.ZipInfo] + mtime: float - def load(self, path): + def load(self, path: str) -> dict[str, zipfile.ZipInfo]: # type: ignore[override] # ZipManifests.load is a classmethod """ Load a manifest at path or return a suitable manifest already loaded. """ @@ -1781,10 +1976,12 @@ def load(self, path): class ZipProvider(EggProvider): """Resource support for zips and eggs""" - eagers = None + eagers: list[str] | None = None _zip_manifests = MemoizedZipManifests() + # ZipProvider's loader should always be a zipimporter or equivalent + loader: zipimport.zipimporter - def __init__(self, module): + def __init__(self, module: _ZipLoaderModule): super().__init__(module) self.zip_pre = self.loader.archive + os.sep @@ -1810,7 +2007,7 @@ def _parts(self, zip_path): def zipinfo(self): return self._zip_manifests.load(self.loader.archive) - def get_resource_filename(self, manager, resource_name): + def get_resource_filename(self, manager: ResourceManager, resource_name: str): if not self.egg_name: raise NotImplementedError( "resource_filename() only supported for .egg, not .zip" @@ -1833,7 +2030,7 @@ def _get_date_and_size(zip_stat): return timestamp, size # FIXME: 'ZipProvider._extract_resource' is too complex (12) - def _extract_resource(self, manager, zip_path): # noqa: C901 + def _extract_resource(self, manager: ResourceManager, zip_path) -> str: # noqa: C901 if zip_path in self._index(): for name in self._index()[zip_path]: last = self._extract_resource(manager, os.path.join(zip_path, name)) @@ -1844,9 +2041,13 @@ def _extract_resource(self, manager, zip_path): # noqa: C901 if not WRITE_SUPPORT: raise OSError( - '"os.rename" and "os.unlink" are not supported ' 'on this platform' + '"os.rename" and "os.unlink" are not supported on this platform' ) try: + if not self.egg_name: + raise OSError( + '"egg_name" is empty. This likely means no egg could be found from the "module_path".' + ) real_path = manager.get_cache_path(self.egg_name, self._parts(zip_path)) if self._is_current(real_path, zip_path): @@ -1935,10 +2136,10 @@ def _isdir(self, fspath) -> bool: def _listdir(self, fspath): return list(self._index().get(self._zipinfo_name(fspath), ())) - def _eager_to_zip(self, resource_name): + def _eager_to_zip(self, resource_name: str): return self._zipinfo_name(self._fn(self.egg_root, resource_name)) - def _resource_to_zip(self, resource_name): + def _resource_to_zip(self, resource_name: str): return self._zipinfo_name(self._fn(self.module_path, resource_name)) @@ -1957,16 +2158,16 @@ class FileMetadata(EmptyProvider): the provided location. """ - def __init__(self, path): + def __init__(self, path: StrPath): self.path = path def _get_metadata_path(self, name): return self.path - def has_metadata(self, name) -> bool: + def has_metadata(self, name: str) -> bool: return name == 'PKG-INFO' and os.path.isfile(self.path) - def get_metadata(self, name): + def get_metadata(self, name: str): if name != 'PKG-INFO': raise KeyError("No metadata except PKG-INFO is available") @@ -1982,7 +2183,7 @@ def _warn_on_replacement(self, metadata): msg = tmpl.format(**locals()) warnings.warn(msg) - def get_metadata_lines(self, name): + def get_metadata_lines(self, name: str) -> Iterator[str]: return yield_lines(self.get_metadata(name)) @@ -2006,7 +2207,7 @@ class PathMetadata(DefaultProvider): dist = Distribution.from_filename(egg_path, metadata=metadata) """ - def __init__(self, path, egg_info): + def __init__(self, path: str, egg_info: str): self.module_path = path self.egg_info = egg_info @@ -2014,7 +2215,7 @@ def __init__(self, path, egg_info): class EggMetadata(ZipProvider): """Metadata provider for .egg files""" - def __init__(self, importer): + def __init__(self, importer: zipimport.zipimporter): """Create a metadata provider from a zipimporter""" self.zip_pre = importer.archive + os.sep @@ -2026,10 +2227,12 @@ def __init__(self, importer): self._setup_prefix() -_declare_state('dict', _distribution_finders={}) +_distribution_finders: dict[type, _DistFinderType[Any]] = _declare_state( + 'dict', '_distribution_finders', {} +) -def register_finder(importer_type, distribution_finder): +def register_finder(importer_type: type[_T], distribution_finder: _DistFinderType[_T]): """Register `distribution_finder` to find distributions in sys.path items `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item @@ -2039,14 +2242,16 @@ def register_finder(importer_type, distribution_finder): _distribution_finders[importer_type] = distribution_finder -def find_distributions(path_item, only=False): +def find_distributions(path_item: str, only: bool = False): """Yield distributions accessible via `path_item`""" importer = get_importer(path_item) finder = _find_adapter(_distribution_finders, importer) return finder(importer, path_item, only) -def find_eggs_in_zip(importer, path_item, only=False): +def find_eggs_in_zip( + importer: zipimport.zipimporter, path_item: str, only: bool = False +) -> Iterator[Distribution]: """ Find eggs in zip files; possibly multiple nested eggs. """ @@ -2075,14 +2280,16 @@ def find_eggs_in_zip(importer, path_item, only=False): register_finder(zipimport.zipimporter, find_eggs_in_zip) -def find_nothing(importer, path_item, only=False): +def find_nothing( + importer: object | None, path_item: str | None, only: bool | None = False +): return () register_finder(object, find_nothing) -def find_on_path(importer, path_item, only=False): +def find_on_path(importer: object | None, path_item, only=False): """Yield distributions accessible on a sys.path directory""" path_item = _normalize_cached(path_item) @@ -2137,7 +2344,7 @@ def __call__(self, fullpath): return iter(()) -def safe_listdir(path): +def safe_listdir(path: StrOrBytesPath): """ Attempt to list contents of path, but suppress some exceptions. """ @@ -2153,13 +2360,13 @@ def safe_listdir(path): return () -def distributions_from_metadata(path): +def distributions_from_metadata(path: str): root = os.path.dirname(path) if os.path.isdir(path): if len(os.listdir(path)) == 0: # empty metadata dir; skip return - metadata = PathMetadata(root, path) + metadata: _MetadataType = PathMetadata(root, path) else: metadata = FileMetadata(path) entry = os.path.basename(path) @@ -2175,11 +2382,10 @@ def non_empty_lines(path): """ Yield non-empty lines from file at path """ - with open(path) as f: - for line in f: - line = line.strip() - if line: - yield line + for line in _read_utf8_with_fallback(path).splitlines(): + line = line.strip() + if line: + yield line def resolve_egg_link(path): @@ -2200,11 +2406,17 @@ def resolve_egg_link(path): register_finder(importlib.machinery.FileFinder, find_on_path) -_declare_state('dict', _namespace_handlers={}) -_declare_state('dict', _namespace_packages={}) +_namespace_handlers: dict[type, _NSHandlerType[Any]] = _declare_state( + 'dict', '_namespace_handlers', {} +) +_namespace_packages: dict[str | None, list[str]] = _declare_state( + 'dict', '_namespace_packages', {} +) -def register_namespace_handler(importer_type, namespace_handler): +def register_namespace_handler( + importer_type: type[_T], namespace_handler: _NSHandlerType[_T] +): """Register `namespace_handler` to declare namespace packages `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item @@ -2259,7 +2471,7 @@ def _handle_ns(packageName, path_item): return subpath -def _rebuild_mod_path(orig_path, package_name, module): +def _rebuild_mod_path(orig_path, package_name, module: types.ModuleType): """ Rebuild module.__path__ ensuring that all entries are ordered corresponding to their sys.path order @@ -2293,7 +2505,7 @@ def position_in_sys_path(path): module.__path__ = new_path -def declare_namespace(packageName): +def declare_namespace(packageName: str): """Declare that package 'packageName' is a namespace package""" msg = ( @@ -2310,7 +2522,7 @@ def declare_namespace(packageName): if packageName in _namespace_packages: return - path = sys.path + path: MutableSequence[str] = sys.path parent, _, _ = packageName.rpartition('.') if parent: @@ -2336,7 +2548,7 @@ def declare_namespace(packageName): _imp.release_lock() -def fixup_namespace_packages(path_item, parent=None): +def fixup_namespace_packages(path_item: str, parent: str | None = None): """Ensure that previously-declared namespace packages include path_item""" _imp.acquire_lock() try: @@ -2348,7 +2560,12 @@ def fixup_namespace_packages(path_item, parent=None): _imp.release_lock() -def file_ns_handler(importer, path_item, packageName, module): +def file_ns_handler( + importer: object, + path_item: StrPath, + packageName: str, + module: types.ModuleType, +): """Compute an ns-package subpath for a filesystem or zipfile importer""" subpath = os.path.join(path_item, packageName.split('.')[-1]) @@ -2368,19 +2585,28 @@ def file_ns_handler(importer, path_item, packageName, module): register_namespace_handler(importlib.machinery.FileFinder, file_ns_handler) -def null_ns_handler(importer, path_item, packageName, module): +def null_ns_handler( + importer: object, + path_item: str | None, + packageName: str | None, + module: _ModuleLike | None, +): return None register_namespace_handler(object, null_ns_handler) -def normalize_path(filename): +@overload +def normalize_path(filename: StrPath) -> str: ... +@overload +def normalize_path(filename: BytesPath) -> bytes: ... +def normalize_path(filename: StrOrBytesPath): """Normalize a file/dir name for comparison purposes""" return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) -def _cygwin_patch(filename): # pragma: nocover +def _cygwin_patch(filename: StrOrBytesPath): # pragma: nocover """ Contrary to POSIX 2008, on Cygwin, getcwd (3) contains symlink components. Using @@ -2391,9 +2617,19 @@ def _cygwin_patch(filename): # pragma: nocover return os.path.abspath(filename) if sys.platform == 'cygwin' else filename -@functools.lru_cache(maxsize=None) -def _normalize_cached(filename): - return normalize_path(filename) +if TYPE_CHECKING: + # https://github.com/python/mypy/issues/16261 + # https://github.com/python/typeshed/issues/6347 + @overload + def _normalize_cached(filename: StrPath) -> str: ... + @overload + def _normalize_cached(filename: BytesPath) -> bytes: ... + def _normalize_cached(filename: StrOrBytesPath) -> str | bytes: ... +else: + + @functools.lru_cache(maxsize=None) + def _normalize_cached(filename): + return normalize_path(filename) def _is_egg_path(path): @@ -2446,7 +2682,14 @@ def _set_parent_ns(packageName): class EntryPoint: """Object representing an advertised importable object""" - def __init__(self, name, module_name, attrs=(), extras=(), dist=None): + def __init__( + self, + name: str, + module_name: str, + attrs: Iterable[str] = (), + extras: Iterable[str] = (), + dist: Distribution | None = None, + ): if not MODULE(module_name): raise ValueError("Invalid module name", module_name) self.name = name @@ -2466,7 +2709,26 @@ def __str__(self): def __repr__(self): return "EntryPoint.parse(%r)" % str(self) - def load(self, require=True, *args, **kwargs): + @overload + def load( + self, + require: Literal[True] = True, + env: Environment | None = None, + installer: _InstallerType | None = None, + ) -> _ResolvedEntryPoint: ... + @overload + def load( + self, + require: Literal[False], + *args: Any, + **kwargs: Any, + ) -> _ResolvedEntryPoint: ... + def load( + self, + require: bool = True, + *args: Environment | _InstallerType | None, + **kwargs: Environment | _InstallerType | None, + ) -> _ResolvedEntryPoint: """ Require packages for this EntryPoint, then resolve it. """ @@ -2478,10 +2740,12 @@ def load(self, require=True, *args, **kwargs): stacklevel=2, ) if require: - self.require(*args, **kwargs) + # We could pass `env` and `installer` directly, + # but keeping `*args` and `**kwargs` for backwards compatibility + self.require(*args, **kwargs) # type: ignore return self.resolve() - def resolve(self): + def resolve(self) -> _ResolvedEntryPoint: """ Resolve the entry point from its module and attrs. """ @@ -2491,9 +2755,14 @@ def resolve(self): except AttributeError as exc: raise ImportError(str(exc)) from exc - def require(self, env=None, installer=None): - if self.extras and not self.dist: - raise UnknownExtra("Can't require() without a distribution", self) + def require( + self, + env: Environment | None = None, + installer: _InstallerType | None = None, + ): + if not self.dist: + error_cls = UnknownExtra if self.extras else AttributeError + raise error_cls("Can't require() without a distribution", self) # Get the requirements for this entry point with all its extras and # then resolve them. We have to pass `extras` along when resolving so @@ -2514,7 +2783,7 @@ def require(self, env=None, installer=None): ) @classmethod - def parse(cls, src, dist=None): + def parse(cls, src: str, dist: Distribution | None = None): """Parse a single entry point from string `src` Entry point syntax follows the form:: @@ -2539,15 +2808,20 @@ def _parse_extras(cls, extras_spec): return () req = Requirement.parse('x' + extras_spec) if req.specs: - raise ValueError() + raise ValueError return req.extras @classmethod - def parse_group(cls, group, lines, dist=None): + def parse_group( + cls, + group: str, + lines: _NestedStr, + dist: Distribution | None = None, + ): """Parse an entry point group""" if not MODULE(group): raise ValueError("Invalid group name", group) - this = {} + this: dict[str, Self] = {} for line in yield_lines(lines): ep = cls.parse(line, dist) if ep.name in this: @@ -2556,14 +2830,19 @@ def parse_group(cls, group, lines, dist=None): return this @classmethod - def parse_map(cls, data, dist=None): + def parse_map( + cls, + data: str | Iterable[str] | dict[str, str | Iterable[str]], + dist: Distribution | None = None, + ): """Parse a map of entry point groups""" + _data: Iterable[tuple[str | None, str | Iterable[str]]] if isinstance(data, dict): - data = data.items() + _data = data.items() else: - data = split_sections(data) - maps = {} - for group, lines in data: + _data = split_sections(data) + maps: dict[str, dict[str, Self]] = {} + for group, lines in _data: if group is None: if not lines: continue @@ -2597,13 +2876,13 @@ class Distribution: def __init__( self, - location=None, - metadata=None, - project_name=None, - version=None, - py_version=PY_MAJOR, - platform=None, - precedence=EGG_DIST, + location: str | None = None, + metadata: _MetadataType = None, + project_name: str | None = None, + version: str | None = None, + py_version: str | None = PY_MAJOR, + platform: str | None = None, + precedence: int = EGG_DIST, ): self.project_name = safe_name(project_name or 'Unknown') if version is not None: @@ -2615,7 +2894,13 @@ def __init__( self._provider = metadata or empty_provider @classmethod - def from_location(cls, location, basename, metadata=None, **kw): + def from_location( + cls, + location: str, + basename: StrPath, + metadata: _MetadataType = None, + **kw: int, # We could set `precedence` explicitly, but keeping this as `**kw` for full backwards and subclassing compatibility + ) -> Distribution: project_name, version, py_version, platform = [None] * 4 basename, ext = os.path.splitext(basename) if ext.lower() in _distributionImpl: @@ -2653,25 +2938,25 @@ def hashcmp(self): def __hash__(self): return hash(self.hashcmp) - def __lt__(self, other): + def __lt__(self, other: Distribution): return self.hashcmp < other.hashcmp - def __le__(self, other): + def __le__(self, other: Distribution): return self.hashcmp <= other.hashcmp - def __gt__(self, other): + def __gt__(self, other: Distribution): return self.hashcmp > other.hashcmp - def __ge__(self, other): + def __ge__(self, other: Distribution): return self.hashcmp >= other.hashcmp - def __eq__(self, other): + def __eq__(self, other: object): if not isinstance(other, self.__class__): # It's not a Distribution, so they are not equal return False return self.hashcmp == other.hashcmp - def __ne__(self, other): + def __ne__(self, other: object): return not self == other # These properties have to be lazy so that we don't have to load any @@ -2691,12 +2976,12 @@ def parsed_version(self): if not hasattr(self, "_parsed_version"): try: self._parsed_version = parse_version(self.version) - except packaging.version.InvalidVersion as ex: + except _packaging_version.InvalidVersion as ex: info = f"(package: {self.project_name})" if hasattr(ex, "add_note"): ex.add_note(info) # PEP 678 raise - raise packaging.version.InvalidVersion(f"{str(ex)} {info}") from None + raise _packaging_version.InvalidVersion(f"{str(ex)} {info}") from None return self._parsed_version @@ -2704,7 +2989,7 @@ def parsed_version(self): def _forgiving_parsed_version(self): try: return self.parsed_version - except packaging.version.InvalidVersion as ex: + except _packaging_version.InvalidVersion as ex: self._parsed_version = parse_version(_forgiving_version(self.version)) notes = "\n".join(getattr(ex, "__notes__", [])) # PEP 678 @@ -2754,14 +3039,14 @@ def _dep_map(self): return self.__dep_map @staticmethod - def _filter_extras(dm): + def _filter_extras(dm: dict[str | None, list[Requirement]]): """ Given a mapping of extras to dependencies, strip off environment markers and filter out any dependencies not matching the markers. """ for extra in list(filter(None, dm)): - new_extra = extra + new_extra: str | None = extra reqs = dm.pop(extra) new_extra, _, marker = extra.partition(':') fails_marker = marker and ( @@ -2781,10 +3066,10 @@ def _build_dep_map(self): dm.setdefault(extra, []).extend(parse_requirements(reqs)) return dm - def requires(self, extras=()): + def requires(self, extras: Iterable[str] = ()): """List of Requirements needed for this distro if `extras` are used""" dm = self._dep_map - deps = [] + deps: list[Requirement] = [] deps.extend(dm.get(None, ())) for ext in extras: try: @@ -2820,12 +3105,12 @@ def _get_version(self): lines = self._get_metadata(self.PKG_INFO) return _version_from_file(lines) - def activate(self, path=None, replace=False): + def activate(self, path: list[str] | None = None, replace: bool = False): """Ensure distribution is importable on `path` (default=sys.path)""" if path is None: path = sys.path self.insert_on(path, replace=replace) - if path is sys.path: + if path is sys.path and self.location is not None: fixup_namespace_packages(self.location) for pkg in self._get_metadata('namespace_packages.txt'): if pkg in sys.modules: @@ -2870,45 +3155,57 @@ def __dir__(self): ) @classmethod - def from_filename(cls, filename, metadata=None, **kw): + def from_filename( + cls, + filename: StrPath, + metadata: _MetadataType = None, + **kw: int, # We could set `precedence` explicitly, but keeping this as `**kw` for full backwards and subclassing compatibility + ): return cls.from_location( _normalize_cached(filename), os.path.basename(filename), metadata, **kw ) def as_requirement(self): """Return a ``Requirement`` that matches this distribution exactly""" - if isinstance(self.parsed_version, packaging.version.Version): + if isinstance(self.parsed_version, _packaging_version.Version): spec = "%s==%s" % (self.project_name, self.parsed_version) else: spec = "%s===%s" % (self.project_name, self.parsed_version) return Requirement.parse(spec) - def load_entry_point(self, group, name): + def load_entry_point(self, group: str, name: str) -> _ResolvedEntryPoint: """Return the `name` entry point of `group` or raise ImportError""" ep = self.get_entry_info(group, name) if ep is None: raise ImportError("Entry point %r not found" % ((group, name),)) return ep.load() - def get_entry_map(self, group=None): + @overload + def get_entry_map(self, group: None = None) -> dict[str, dict[str, EntryPoint]]: ... + @overload + def get_entry_map(self, group: str) -> dict[str, EntryPoint]: ... + def get_entry_map(self, group: str | None = None): """Return the entry point map for `group`, or the full entry map""" - try: - ep_map = self._ep_map - except AttributeError: - ep_map = self._ep_map = EntryPoint.parse_map( + if not hasattr(self, "_ep_map"): + self._ep_map = EntryPoint.parse_map( self._get_metadata('entry_points.txt'), self ) if group is not None: - return ep_map.get(group, {}) - return ep_map + return self._ep_map.get(group, {}) + return self._ep_map - def get_entry_info(self, group, name): + def get_entry_info(self, group: str, name: str): """Return the EntryPoint object for `group`+`name`, or ``None``""" return self.get_entry_map(group).get(name) # FIXME: 'Distribution.insert_on' is too complex (13) - def insert_on(self, path, loc=None, replace=False): # noqa: C901 + def insert_on( # noqa: C901 + self, + path: list[str], + loc=None, + replace: bool = False, + ): """Ensure self.location is on path If replace=False (default): @@ -3013,13 +3310,14 @@ def has_version(self): return False return True - def clone(self, **kw): + def clone(self, **kw: str | int | IResourceProvider | None): """Copy this distribution, substituting in any changed keyword args""" names = 'project_name version py_version platform location precedence' for attr in names.split(): kw.setdefault(attr, getattr(self, attr, None)) kw.setdefault('metadata', self._provider) - return self.__class__(**kw) + # Unsafely unpacking. But keeping **kw for backwards and subclassing compatibility + return self.__class__(**kw) # type:ignore[arg-type] @property def extras(self): @@ -3072,11 +3370,11 @@ def _dep_map(self): self.__dep_map = self._compute_dependencies() return self.__dep_map - def _compute_dependencies(self): + def _compute_dependencies(self) -> dict[str | None, list[Requirement]]: """Recompute this distribution's dependencies.""" - dm = self.__dep_map = {None: []} + self.__dep_map: dict[str | None, list[Requirement]] = {None: []} - reqs = [] + reqs: list[Requirement] = [] # Including any condition expressions for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: reqs.extend(parse_requirements(req)) @@ -3087,13 +3385,15 @@ def reqs_for_extra(extra): yield req common = types.MappingProxyType(dict.fromkeys(reqs_for_extra(None))) - dm[None].extend(common) + self.__dep_map[None].extend(common) for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: s_extra = safe_extra(extra.strip()) - dm[s_extra] = [r for r in reqs_for_extra(extra) if r not in common] + self.__dep_map[s_extra] = [ + r for r in reqs_for_extra(extra) if r not in common + ] - return dm + return self.__dep_map _distributionImpl = { @@ -3116,7 +3416,7 @@ def issue_warning(*args, **kw): warnings.warn(stacklevel=level + 1, *args, **kw) -def parse_requirements(strs): +def parse_requirements(strs: _NestedStr): """ Yield ``Requirement`` objects for each specification in `strs`. @@ -3125,19 +3425,20 @@ def parse_requirements(strs): return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs)))) -class RequirementParseError(packaging.requirements.InvalidRequirement): +class RequirementParseError(_packaging_requirements.InvalidRequirement): "Compatibility wrapper for InvalidRequirement" -class Requirement(packaging.requirements.Requirement): - def __init__(self, requirement_string): +class Requirement(_packaging_requirements.Requirement): + def __init__(self, requirement_string: str): """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!""" super().__init__(requirement_string) self.unsafe_name = self.name project_name = safe_name(self.name) self.project_name, self.key = project_name, project_name.lower() self.specs = [(spec.operator, spec.version) for spec in self.specifier] - self.extras = tuple(map(safe_extra, self.extras)) + # packaging.requirements.Requirement uses a set for its extras. We use a variable-length tuple + self.extras: tuple[str] = tuple(map(safe_extra, self.extras)) self.hashCmp = ( self.key, self.url, @@ -3147,13 +3448,13 @@ def __init__(self, requirement_string): ) self.__hash = hash(self.hashCmp) - def __eq__(self, other): + def __eq__(self, other: object): return isinstance(other, Requirement) and self.hashCmp == other.hashCmp def __ne__(self, other): return not self == other - def __contains__(self, item): + def __contains__(self, item: Distribution | str | tuple[str, ...]) -> bool: if isinstance(item, Distribution): if item.key != self.key: return False @@ -3172,7 +3473,7 @@ def __repr__(self): return "Requirement.parse(%r)" % str(self) @staticmethod - def parse(s): + def parse(s: str | Iterable[str]): (req,) = parse_requirements(s) return req @@ -3187,7 +3488,7 @@ def _always_object(classes): return classes -def _find_adapter(registry, ob): +def _find_adapter(registry: Mapping[type, _AdapterT], ob: object) -> _AdapterT: """Return an adapter factory for `ob` from `registry`""" types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob)))) for t in types: @@ -3198,7 +3499,7 @@ def _find_adapter(registry, ob): raise TypeError(f"Could not find adapter for {registry} and {ob}") -def ensure_directory(path): +def ensure_directory(path: StrOrBytesPath): """Ensure that the parent directory of `path` exists""" dirname = os.path.dirname(path) os.makedirs(dirname, exist_ok=True) @@ -3217,7 +3518,7 @@ def _bypass_ensure_directory(path): pass -def split_sections(s): +def split_sections(s: _NestedStr) -> Iterator[tuple[str | None, list[str]]]: """Split a string or iterable thereof into (section, content) pairs Each ``section`` is a stripped version of the section header ("[section]") @@ -3261,6 +3562,47 @@ def _mkstemp(*args, **kw): warnings.filterwarnings("ignore", category=PEP440Warning, append=True) +class PkgResourcesDeprecationWarning(Warning): + """ + Base class for warning about deprecations in ``pkg_resources`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ + + +# Ported from ``setuptools`` to avoid introducing an import inter-dependency: +_LOCALE_ENCODING = "locale" if sys.version_info >= (3, 10) else None + + +def _read_utf8_with_fallback(file: str, fallback_encoding=_LOCALE_ENCODING) -> str: + """See setuptools.unicode_utils._read_utf8_with_fallback""" + try: + with open(file, "r", encoding="utf-8") as f: + return f.read() + except UnicodeDecodeError: # pragma: no cover + msg = f"""\ + ******************************************************************************** + `encoding="utf-8"` fails with {file!r}, trying `encoding={fallback_encoding!r}`. + + This fallback behaviour is considered **deprecated** and future versions of + `setuptools/pkg_resources` may not implement it. + + Please encode {file!r} with "utf-8" to ensure future builds will succeed. + + If this file was produced by `setuptools` itself, cleaning up the cached files + and re-building/re-installing the package with a newer version of `setuptools` + (e.g. by updating `build-system.requires` in its `pyproject.toml`) + might solve the problem. + ******************************************************************************** + """ + # TODO: Add a deadline? + # See comment in setuptools.unicode_utils._Utf8EncodingNeeded + warnings.warn(msg, PkgResourcesDeprecationWarning, stacklevel=2) + with open(file, "r", encoding=fallback_encoding) as f: + return f.read() + + # from jaraco.functools 1.3 def _call_aside(f, *args, **kwargs): f(*args, **kwargs) @@ -3279,15 +3621,6 @@ def _initialize(g=globals()): ) -class PkgResourcesDeprecationWarning(Warning): - """ - Base class for warning about deprecations in ``pkg_resources`` - - This class is not derived from ``DeprecationWarning``, and as such is - visible by default. - """ - - @_call_aside def _initialize_master_working_set(): """ @@ -3301,8 +3634,7 @@ def _initialize_master_working_set(): Invocation by other packages is unsupported and done at their own risk. """ - working_set = WorkingSet._build_master() - _declare_state('object', working_set=working_set) + working_set = _declare_state('object', 'working_set', WorkingSet._build_master()) require = working_set.require iter_entry_points = working_set.iter_entry_points @@ -3323,3 +3655,23 @@ def _initialize_master_working_set(): # match order list(map(working_set.add_entry, sys.path)) globals().update(locals()) + + +if TYPE_CHECKING: + # All of these are set by the @_call_aside methods above + __resource_manager = ResourceManager() # Won't exist at runtime + resource_exists = __resource_manager.resource_exists + resource_isdir = __resource_manager.resource_isdir + resource_filename = __resource_manager.resource_filename + resource_stream = __resource_manager.resource_stream + resource_string = __resource_manager.resource_string + resource_listdir = __resource_manager.resource_listdir + set_extraction_path = __resource_manager.set_extraction_path + cleanup_resources = __resource_manager.cleanup_resources + + working_set = WorkingSet() + require = working_set.require + iter_entry_points = working_set.iter_entry_points + add_activation_listener = working_set.subscribe + run_script = working_set.run_script + run_main = run_script diff --git a/src/pip/_vendor/platformdirs/android.py b/src/pip/_vendor/platformdirs/android.py index fefafd32977..afd3141c725 100644 --- a/src/pip/_vendor/platformdirs/android.py +++ b/src/pip/_vendor/platformdirs/android.py @@ -6,7 +6,7 @@ import re import sys from functools import lru_cache -from typing import cast +from typing import TYPE_CHECKING, cast from .api import PlatformDirsABC @@ -117,16 +117,33 @@ def site_runtime_dir(self) -> str: @lru_cache(maxsize=1) -def _android_folder() -> str | None: +def _android_folder() -> str | None: # noqa: C901, PLR0912 """:return: base folder for the Android OS or None if it cannot be found""" - try: - # First try to get a path to android app via pyjnius - from jnius import autoclass # noqa: PLC0415 - - context = autoclass("android.content.Context") - result: str | None = context.getFilesDir().getParentFile().getAbsolutePath() - except Exception: # noqa: BLE001 - # if fails find an android folder looking a path on the sys.path + result: str | None = None + # type checker isn't happy with our "import android", just don't do this when type checking see + # https://stackoverflow.com/a/61394121 + if not TYPE_CHECKING: + try: + # First try to get a path to android app using python4android (if available)... + from android import mActivity # noqa: PLC0415 + + context = cast("android.content.Context", mActivity.getApplicationContext()) # noqa: F821 + result = context.getFilesDir().getParentFile().getAbsolutePath() + except Exception: # noqa: BLE001 + result = None + if result is None: + try: + # ...and fall back to using plain pyjnius, if python4android isn't available or doesn't deliver any useful + # result... + from jnius import autoclass # noqa: PLC0415 + + context = autoclass("android.content.Context") + result = context.getFilesDir().getParentFile().getAbsolutePath() + except Exception: # noqa: BLE001 + result = None + if result is None: + # and if that fails, too, find an android folder looking at path on the sys.path + # warning: only works for apps installed under /data, not adopted storage etc. pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files") for path in sys.path: if pattern.match(path): @@ -134,6 +151,16 @@ def _android_folder() -> str | None: break else: result = None + if result is None: + # one last try: find an android folder looking at path on the sys.path taking adopted storage paths into + # account + pattern = re.compile(r"/mnt/expand/[a-fA-F0-9-]{36}/(data|user/\d+)/(.+)/files") + for path in sys.path: + if pattern.match(path): + result = path.split("/files")[0] + break + else: + result = None return result diff --git a/src/pip/_vendor/platformdirs/version.py b/src/pip/_vendor/platformdirs/version.py index c418cd0c9af..6483ddce0bc 100644 --- a/src/pip/_vendor/platformdirs/version.py +++ b/src/pip/_vendor/platformdirs/version.py @@ -12,5 +12,5 @@ __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = '4.2.1' -__version_tuple__ = version_tuple = (4, 2, 1) +__version__ = version = '4.2.2' +__version_tuple__ = version_tuple = (4, 2, 2) diff --git a/src/pip/_vendor/pygments/__init__.py b/src/pip/_vendor/pygments/__init__.py index 5b8a3f95483..60ae9bb8508 100644 --- a/src/pip/_vendor/pygments/__init__.py +++ b/src/pip/_vendor/pygments/__init__.py @@ -21,12 +21,12 @@ .. _Pygments master branch: https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ from io import StringIO, BytesIO -__version__ = '2.17.2' +__version__ = '2.18.0' __docformat__ = 'restructuredtext' __all__ = ['lex', 'format', 'highlight'] diff --git a/src/pip/_vendor/pygments/__main__.py b/src/pip/_vendor/pygments/__main__.py index 2f7f8cbad05..dcc6e5add71 100644 --- a/src/pip/_vendor/pygments/__main__.py +++ b/src/pip/_vendor/pygments/__main__.py @@ -4,7 +4,7 @@ Main entry point for ``python -m pygments``. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/src/pip/_vendor/pygments/cmdline.py b/src/pip/_vendor/pygments/cmdline.py index 29b5608f331..0a7072eff3e 100644 --- a/src/pip/_vendor/pygments/cmdline.py +++ b/src/pip/_vendor/pygments/cmdline.py @@ -4,7 +4,7 @@ Command line interface. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -68,19 +68,19 @@ def _print_help(what, name): try: if what == 'lexer': cls = get_lexer_by_name(name) - print("Help on the %s lexer:" % cls.name) + print(f"Help on the {cls.name} lexer:") print(dedent(cls.__doc__)) elif what == 'formatter': cls = find_formatter_class(name) - print("Help on the %s formatter:" % cls.name) + print(f"Help on the {cls.name} formatter:") print(dedent(cls.__doc__)) elif what == 'filter': cls = find_filter_class(name) - print("Help on the %s filter:" % name) + print(f"Help on the {name} filter:") print(dedent(cls.__doc__)) return 0 except (AttributeError, ValueError): - print("%s not found!" % what, file=sys.stderr) + print(f"{what} not found!", file=sys.stderr) return 1 @@ -97,7 +97,7 @@ def _print_list(what): info.append(tup) info.sort() for i in info: - print(('* %s\n %s %s') % i) + print(('* {}\n {} {}').format(*i)) elif what == 'formatter': print() @@ -112,7 +112,7 @@ def _print_list(what): info.append(tup) info.sort() for i in info: - print(('* %s\n %s %s') % i) + print(('* {}\n {} {}').format(*i)) elif what == 'filter': print() @@ -122,7 +122,7 @@ def _print_list(what): for name in get_all_filters(): cls = find_filter_class(name) print("* " + name + ':') - print(" %s" % docstring_headline(cls)) + print(f" {docstring_headline(cls)}") elif what == 'style': print() @@ -132,7 +132,7 @@ def _print_list(what): for name in get_all_styles(): cls = get_style_by_name(name) print("* " + name + ':') - print(" %s" % docstring_headline(cls)) + print(f" {docstring_headline(cls)}") def _print_list_as_json(requested_items): @@ -185,8 +185,8 @@ def main_inner(parser, argns): return 0 if argns.V: - print('Pygments version %s, (c) 2006-2023 by Georg Brandl, Matthäus ' - 'Chajdas and contributors.' % __version__) + print(f'Pygments version {__version__}, (c) 2006-2024 by Georg Brandl, Matthäus ' + 'Chajdas and contributors.') return 0 def is_only_option(opt): @@ -659,7 +659,7 @@ def main(args=sys.argv): msg = info[-1].strip() if len(info) >= 3: # extract relevant file and position info - msg += '\n (f%s)' % info[-2].split('\n')[0].strip()[1:] + msg += '\n (f{})'.format(info[-2].split('\n')[0].strip()[1:]) print(file=sys.stderr) print('*** Error while highlighting:', file=sys.stderr) print(msg, file=sys.stderr) diff --git a/src/pip/_vendor/pygments/console.py b/src/pip/_vendor/pygments/console.py index deb4937f74f..4c1a06219ca 100644 --- a/src/pip/_vendor/pygments/console.py +++ b/src/pip/_vendor/pygments/console.py @@ -4,7 +4,7 @@ Format colored console output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -27,12 +27,12 @@ "brightmagenta", "brightcyan", "white"] x = 30 -for d, l in zip(dark_colors, light_colors): - codes[d] = esc + "%im" % x - codes[l] = esc + "%im" % (60 + x) +for dark, light in zip(dark_colors, light_colors): + codes[dark] = esc + "%im" % x + codes[light] = esc + "%im" % (60 + x) x += 1 -del d, l, x +del dark, light, x codes["white"] = codes["bold"] diff --git a/src/pip/_vendor/pygments/filter.py b/src/pip/_vendor/pygments/filter.py index dafa08d1569..aa6f76041b6 100644 --- a/src/pip/_vendor/pygments/filter.py +++ b/src/pip/_vendor/pygments/filter.py @@ -4,7 +4,7 @@ Module that implements the default filter. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -62,8 +62,7 @@ class FunctionFilter(Filter): def __init__(self, **options): if not hasattr(self, 'function'): - raise TypeError('%r used without bound function' % - self.__class__.__name__) + raise TypeError(f'{self.__class__.__name__!r} used without bound function') Filter.__init__(self, **options) def filter(self, lexer, stream): diff --git a/src/pip/_vendor/pygments/filters/__init__.py b/src/pip/_vendor/pygments/filters/__init__.py index 5aa9ecbb80c..9255ca224db 100644 --- a/src/pip/_vendor/pygments/filters/__init__.py +++ b/src/pip/_vendor/pygments/filters/__init__.py @@ -5,7 +5,7 @@ Module containing filter lookup functions and default filters. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -39,7 +39,7 @@ def get_filter_by_name(filtername, **options): if cls: return cls(**options) else: - raise ClassNotFound('filter %r not found' % filtername) + raise ClassNotFound(f'filter {filtername!r} not found') def get_all_filters(): @@ -79,9 +79,9 @@ def __init__(self, **options): Filter.__init__(self, **options) tags = get_list_opt(options, 'codetags', ['XXX', 'TODO', 'FIXME', 'BUG', 'NOTE']) - self.tag_re = re.compile(r'\b(%s)\b' % '|'.join([ + self.tag_re = re.compile(r'\b({})\b'.format('|'.join([ re.escape(tag) for tag in tags if tag - ])) + ]))) def filter(self, lexer, stream): regex = self.tag_re diff --git a/src/pip/_vendor/pygments/formatter.py b/src/pip/_vendor/pygments/formatter.py index 3ca4892fa31..d2666037f7a 100644 --- a/src/pip/_vendor/pygments/formatter.py +++ b/src/pip/_vendor/pygments/formatter.py @@ -4,7 +4,7 @@ Base formatter class. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -122,3 +122,8 @@ def format(self, tokensource, outfile): # wrap the outfile in a StreamWriter outfile = codecs.lookup(self.encoding)[3](outfile) return self.format_unencoded(tokensource, outfile) + + # Allow writing Formatter[str] or Formatter[bytes]. That's equivalent to + # Formatter. This helps when using third-party type stubs from typeshed. + def __class_getitem__(cls, name): + return cls diff --git a/src/pip/_vendor/pygments/formatters/__init__.py b/src/pip/_vendor/pygments/formatters/__init__.py index 6abb45ac71f..f19e9931f07 100644 --- a/src/pip/_vendor/pygments/formatters/__init__.py +++ b/src/pip/_vendor/pygments/formatters/__init__.py @@ -4,7 +4,7 @@ Pygments formatters. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -77,7 +77,7 @@ def get_formatter_by_name(_alias, **options): """ cls = find_formatter_class(_alias) if cls is None: - raise ClassNotFound("no formatter found for name %r" % _alias) + raise ClassNotFound(f"no formatter found for name {_alias!r}") return cls(**options) @@ -103,17 +103,16 @@ def load_formatter_from_file(filename, formattername="CustomFormatter", **option exec(f.read(), custom_namespace) # Retrieve the class `formattername` from that namespace if formattername not in custom_namespace: - raise ClassNotFound('no valid %s class found in %s' % - (formattername, filename)) + raise ClassNotFound(f'no valid {formattername} class found in {filename}') formatter_class = custom_namespace[formattername] # And finally instantiate it with the options return formatter_class(**options) except OSError as err: - raise ClassNotFound('cannot read %s: %s' % (filename, err)) + raise ClassNotFound(f'cannot read {filename}: {err}') except ClassNotFound: raise except Exception as err: - raise ClassNotFound('error when loading custom formatter: %s' % err) + raise ClassNotFound(f'error when loading custom formatter: {err}') def get_formatter_for_filename(fn, **options): @@ -135,7 +134,7 @@ def get_formatter_for_filename(fn, **options): for filename in cls.filenames: if _fn_matches(fn, filename): return cls(**options) - raise ClassNotFound("no formatter found for file name %r" % fn) + raise ClassNotFound(f"no formatter found for file name {fn!r}") class _automodule(types.ModuleType): diff --git a/src/pip/_vendor/pygments/formatters/bbcode.py b/src/pip/_vendor/pygments/formatters/bbcode.py index c4db8f4ef21..5a05bd961de 100644 --- a/src/pip/_vendor/pygments/formatters/bbcode.py +++ b/src/pip/_vendor/pygments/formatters/bbcode.py @@ -4,7 +4,7 @@ BBcode formatter. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -60,7 +60,7 @@ def _make_styles(self): for ttype, ndef in self.style: start = end = '' if ndef['color']: - start += '[color=#%s]' % ndef['color'] + start += '[color=#{}]'.format(ndef['color']) end = '[/color]' + end if ndef['bold']: start += '[b]' diff --git a/src/pip/_vendor/pygments/formatters/groff.py b/src/pip/_vendor/pygments/formatters/groff.py index 30a528e668f..5c8a958f8d7 100644 --- a/src/pip/_vendor/pygments/formatters/groff.py +++ b/src/pip/_vendor/pygments/formatters/groff.py @@ -4,7 +4,7 @@ Formatter for groff output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -63,7 +63,7 @@ def _make_styles(self): for ttype, ndef in self.style: start = end = '' if ndef['color']: - start += '\\m[%s]' % ndef['color'] + start += '\\m[{}]'.format(ndef['color']) end = '\\m[]' + end if ndef['bold']: start += bold @@ -72,7 +72,7 @@ def _make_styles(self): start += italic end = regular + end if ndef['bgcolor']: - start += '\\M[%s]' % ndef['bgcolor'] + start += '\\M[{}]'.format(ndef['bgcolor']) end = '\\M[]' + end self.styles[ttype] = start, end diff --git a/src/pip/_vendor/pygments/formatters/html.py b/src/pip/_vendor/pygments/formatters/html.py index 0cadcb228e7..7aa938f5119 100644 --- a/src/pip/_vendor/pygments/formatters/html.py +++ b/src/pip/_vendor/pygments/formatters/html.py @@ -4,7 +4,7 @@ Formatter for HTML output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -62,7 +62,7 @@ def _get_ttype_class(ttype): CSSFILE_TEMPLATE = '''\ /* generated by Pygments -Copyright 2006-2023 by the Pygments team. +Copyright 2006-2024 by the Pygments team. Licensed under the BSD license, see LICENSE for details. */ %(styledefs)s @@ -73,7 +73,7 @@ def _get_ttype_class(ttype): "http://www.w3.org/TR/html4/strict.dtd"> @@ -488,7 +488,7 @@ def _create_stylesheet(self): name = self._get_css_class(ttype) style = '' if ndef['color']: - style += 'color: %s; ' % webify(ndef['color']) + style += 'color: {}; '.format(webify(ndef['color'])) if ndef['bold']: style += 'font-weight: bold; ' if ndef['italic']: @@ -496,9 +496,9 @@ def _create_stylesheet(self): if ndef['underline']: style += 'text-decoration: underline; ' if ndef['bgcolor']: - style += 'background-color: %s; ' % webify(ndef['bgcolor']) + style += 'background-color: {}; '.format(webify(ndef['bgcolor'])) if ndef['border']: - style += 'border: 1px solid %s; ' % webify(ndef['border']) + style += 'border: 1px solid {}; '.format(webify(ndef['border'])) if style: t2c[ttype] = name # save len(ttype) to enable ordering the styles by @@ -530,7 +530,7 @@ def get_token_style_defs(self, arg=None): styles.sort() lines = [ - '%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:]) + f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */' for (level, ttype, cls, style) in styles ] @@ -548,24 +548,24 @@ def get_background_style_defs(self, arg=None): if Text in self.ttype2class: text_style = ' ' + self.class2style[self.ttype2class[Text]][0] lines.insert( - 0, '%s{ background: %s;%s }' % ( + 0, '{}{{ background: {};{} }}'.format( prefix(''), bg_color, text_style ) ) if hl_color is not None: lines.insert( - 0, '%s { background-color: %s }' % (prefix('hll'), hl_color) + 0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color) ) return lines def get_linenos_style_defs(self): lines = [ - 'pre { %s }' % self._pre_style, - 'td.linenos .normal { %s }' % self._linenos_style, - 'span.linenos { %s }' % self._linenos_style, - 'td.linenos .special { %s }' % self._linenos_special_style, - 'span.linenos.special { %s }' % self._linenos_special_style, + f'pre {{ {self._pre_style} }}', + f'td.linenos .normal {{ {self._linenos_style} }}', + f'span.linenos {{ {self._linenos_style} }}', + f'td.linenos .special {{ {self._linenos_special_style} }}', + f'span.linenos.special {{ {self._linenos_special_style} }}', ] return lines @@ -594,17 +594,15 @@ def _pre_style(self): @property def _linenos_style(self): - return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % ( - self.style.line_number_color, - self.style.line_number_background_color - ) + color = self.style.line_number_color + background_color = self.style.line_number_background_color + return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;' @property def _linenos_special_style(self): - return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % ( - self.style.line_number_special_color, - self.style.line_number_special_background_color - ) + color = self.style.line_number_special_color + background_color = self.style.line_number_special_background_color + return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;' def _decodeifneeded(self, value): if isinstance(value, bytes): @@ -685,9 +683,9 @@ def _wrap_tablelinenos(self, inner): if nocls: if special_line: - style = ' style="%s"' % self._linenos_special_style + style = f' style="{self._linenos_special_style}"' else: - style = ' style="%s"' % self._linenos_style + style = f' style="{self._linenos_style}"' else: if special_line: style = ' class="special"' @@ -695,7 +693,7 @@ def _wrap_tablelinenos(self, inner): style = ' class="normal"' if style: - line = '%s' % (style, line) + line = f'{line}' lines.append(line) @@ -744,9 +742,9 @@ def _wrap_inlinelinenos(self, inner): if nocls: if special_line: - style = ' style="%s"' % self._linenos_special_style + style = f' style="{self._linenos_special_style}"' else: - style = ' style="%s"' % self._linenos_style + style = f' style="{self._linenos_style}"' else: if special_line: style = ' class="linenos special"' @@ -754,7 +752,7 @@ def _wrap_inlinelinenos(self, inner): style = ' class="linenos"' if style: - linenos = '%s' % (style, line) + linenos = f'{line}' else: linenos = line @@ -791,13 +789,13 @@ def _wrap_div(self, inner): style = [] if (self.noclasses and not self.nobackground and self.style.background_color is not None): - style.append('background: %s' % (self.style.background_color,)) + style.append(f'background: {self.style.background_color}') if self.cssstyles: style.append(self.cssstyles) style = '; '.join(style) - yield 0, ('') + yield 0, ('') yield from inner yield 0, '\n' @@ -814,7 +812,7 @@ def _wrap_pre(self, inner): # the empty span here is to keep leading empty lines from being # ignored by HTML parsers - yield 0, ('') + yield 0, ('') yield from inner yield 0, '' @@ -843,18 +841,18 @@ def _format_lines(self, tokensource): try: cspan = self.span_element_openers[ttype] except KeyError: - title = ' title="%s"' % '.'.join(ttype) if self.debug_token_types else '' + title = ' title="{}"'.format('.'.join(ttype)) if self.debug_token_types else '' if nocls: css_style = self._get_css_inline_styles(ttype) if css_style: css_style = self.class2style[css_style][0] - cspan = '' % (css_style, title) + cspan = f'' else: cspan = '' else: css_class = self._get_css_classes(ttype) if css_class: - cspan = '' % (css_class, title) + cspan = f'' else: cspan = '' self.span_element_openers[ttype] = cspan @@ -927,11 +925,10 @@ def _highlight_lines(self, tokensource): if self.noclasses: style = '' if self.style.highlight_color is not None: - style = (' style="background-color: %s"' % - (self.style.highlight_color,)) - yield 1, '%s' % (style, value) + style = (f' style="background-color: {self.style.highlight_color}"') + yield 1, f'{value}' else: - yield 1, '%s' % value + yield 1, f'{value}' else: yield 1, value diff --git a/src/pip/_vendor/pygments/formatters/img.py b/src/pip/_vendor/pygments/formatters/img.py index 9e66b669162..7542cfad9da 100644 --- a/src/pip/_vendor/pygments/formatters/img.py +++ b/src/pip/_vendor/pygments/formatters/img.py @@ -4,7 +4,7 @@ Formatter for Pixmap output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os @@ -90,7 +90,7 @@ def __init__(self, font_name, font_size=14): self._create_nix() def _get_nix_font_path(self, name, style): - proc = subprocess.Popen(['fc-list', "%s:style=%s" % (name, style), 'file'], + proc = subprocess.Popen(['fc-list', f"{name}:style={style}", 'file'], stdout=subprocess.PIPE, stderr=None) stdout, _ = proc.communicate() if proc.returncode == 0: @@ -110,8 +110,7 @@ def _create_nix(self): self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size) break else: - raise FontNotFound('No usable fonts named: "%s"' % - self.font_name) + raise FontNotFound(f'No usable fonts named: "{self.font_name}"') for style in ('ITALIC', 'BOLD', 'BOLDITALIC'): for stylename in STYLES[style]: path = self._get_nix_font_path(self.font_name, stylename) @@ -142,8 +141,7 @@ def _create_mac(self): self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size) break else: - raise FontNotFound('No usable fonts named: "%s"' % - self.font_name) + raise FontNotFound(f'No usable fonts named: "{self.font_name}"') for style in ('ITALIC', 'BOLD', 'BOLDITALIC'): for stylename in STYLES[style]: path = self._get_mac_font_path(font_map, self.font_name, stylename) @@ -160,15 +158,14 @@ def _lookup_win(self, key, basename, styles, fail=False): for suffix in ('', ' (TrueType)'): for style in styles: try: - valname = '%s%s%s' % (basename, style and ' '+style, suffix) + valname = '{}{}{}'.format(basename, style and ' '+style, suffix) val, _ = _winreg.QueryValueEx(key, valname) return val except OSError: continue else: if fail: - raise FontNotFound('Font %s (%s) not found in registry' % - (basename, styles[0])) + raise FontNotFound(f'Font {basename} ({styles[0]}) not found in registry') return None def _create_win(self): @@ -633,7 +630,11 @@ def format(self, tokensource, outfile): fill=self.hl_color) for pos, value, font, text_fg, text_bg in self.drawables: if text_bg: - text_size = draw.textsize(text=value, font=font) + # see deprecations https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods + if hasattr(draw, 'textsize'): + text_size = draw.textsize(text=value, font=font) + else: + text_size = font.getbbox(value)[2:] draw.rectangle([pos[0], pos[1], pos[0] + text_size[0], pos[1] + text_size[1]], fill=text_bg) draw.text(pos, value, font=font, fill=text_fg) im.save(outfile, self.image_format.upper()) diff --git a/src/pip/_vendor/pygments/formatters/irc.py b/src/pip/_vendor/pygments/formatters/irc.py index 2144d439e0f..468c2876053 100644 --- a/src/pip/_vendor/pygments/formatters/irc.py +++ b/src/pip/_vendor/pygments/formatters/irc.py @@ -4,7 +4,7 @@ Formatter for IRC output - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/src/pip/_vendor/pygments/formatters/latex.py b/src/pip/_vendor/pygments/formatters/latex.py index ca539b40f6a..0ec9089b937 100644 --- a/src/pip/_vendor/pygments/formatters/latex.py +++ b/src/pip/_vendor/pygments/formatters/latex.py @@ -4,7 +4,7 @@ Formatter for LaTeX fancyvrb output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -23,21 +23,21 @@ def escape_tex(text, commandprefix): return text.replace('\\', '\x00'). \ replace('{', '\x01'). \ replace('}', '\x02'). \ - replace('\x00', r'\%sZbs{}' % commandprefix). \ - replace('\x01', r'\%sZob{}' % commandprefix). \ - replace('\x02', r'\%sZcb{}' % commandprefix). \ - replace('^', r'\%sZca{}' % commandprefix). \ - replace('_', r'\%sZus{}' % commandprefix). \ - replace('&', r'\%sZam{}' % commandprefix). \ - replace('<', r'\%sZlt{}' % commandprefix). \ - replace('>', r'\%sZgt{}' % commandprefix). \ - replace('#', r'\%sZsh{}' % commandprefix). \ - replace('%', r'\%sZpc{}' % commandprefix). \ - replace('$', r'\%sZdl{}' % commandprefix). \ - replace('-', r'\%sZhy{}' % commandprefix). \ - replace("'", r'\%sZsq{}' % commandprefix). \ - replace('"', r'\%sZdq{}' % commandprefix). \ - replace('~', r'\%sZti{}' % commandprefix) + replace('\x00', rf'\{commandprefix}Zbs{{}}'). \ + replace('\x01', rf'\{commandprefix}Zob{{}}'). \ + replace('\x02', rf'\{commandprefix}Zcb{{}}'). \ + replace('^', rf'\{commandprefix}Zca{{}}'). \ + replace('_', rf'\{commandprefix}Zus{{}}'). \ + replace('&', rf'\{commandprefix}Zam{{}}'). \ + replace('<', rf'\{commandprefix}Zlt{{}}'). \ + replace('>', rf'\{commandprefix}Zgt{{}}'). \ + replace('#', rf'\{commandprefix}Zsh{{}}'). \ + replace('%', rf'\{commandprefix}Zpc{{}}'). \ + replace('$', rf'\{commandprefix}Zdl{{}}'). \ + replace('-', rf'\{commandprefix}Zhy{{}}'). \ + replace("'", rf'\{commandprefix}Zsq{{}}'). \ + replace('"', rf'\{commandprefix}Zdq{{}}'). \ + replace('~', rf'\{commandprefix}Zti{{}}') DOC_TEMPLATE = r''' @@ -304,17 +304,14 @@ def rgbcolor(col): if ndef['mono']: cmndef += r'\let\$$@ff=\textsf' if ndef['color']: - cmndef += (r'\def\$$@tc##1{\textcolor[rgb]{%s}{##1}}' % - rgbcolor(ndef['color'])) + cmndef += (r'\def\$$@tc##1{{\textcolor[rgb]{{{}}}{{##1}}}}'.format(rgbcolor(ndef['color']))) if ndef['border']: - cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{\string -\fboxrule}' - r'\fcolorbox[rgb]{%s}{%s}{\strut ##1}}}' % - (rgbcolor(ndef['border']), + cmndef += (r'\def\$$@bc##1{{{{\setlength{{\fboxsep}}{{\string -\fboxrule}}' + r'\fcolorbox[rgb]{{{}}}{{{}}}{{\strut ##1}}}}}}'.format(rgbcolor(ndef['border']), rgbcolor(ndef['bgcolor']))) elif ndef['bgcolor']: - cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{0pt}' - r'\colorbox[rgb]{%s}{\strut ##1}}}' % - rgbcolor(ndef['bgcolor'])) + cmndef += (r'\def\$$@bc##1{{{{\setlength{{\fboxsep}}{{0pt}}' + r'\colorbox[rgb]{{{}}}{{\strut ##1}}}}}}'.format(rgbcolor(ndef['bgcolor']))) if cmndef == '': continue cmndef = cmndef.replace('$$', cp) @@ -329,7 +326,7 @@ def get_style_defs(self, arg=''): cp = self.commandprefix styles = [] for name, definition in self.cmd2def.items(): - styles.append(r'\@namedef{%s@tok@%s}{%s}' % (cp, name, definition)) + styles.append(rf'\@namedef{{{cp}@tok@{name}}}{{{definition}}}') return STYLE_TEMPLATE % {'cp': self.commandprefix, 'styles': '\n'.join(styles)} @@ -410,10 +407,10 @@ def format_unencoded(self, tokensource, outfile): spl = value.split('\n') for line in spl[:-1]: if line: - outfile.write("\\%s{%s}{%s}" % (cp, styleval, line)) + outfile.write(f"\\{cp}{{{styleval}}}{{{line}}}") outfile.write('\n') if spl[-1]: - outfile.write("\\%s{%s}{%s}" % (cp, styleval, spl[-1])) + outfile.write(f"\\{cp}{{{styleval}}}{{{spl[-1]}}}") else: outfile.write(value) diff --git a/src/pip/_vendor/pygments/formatters/other.py b/src/pip/_vendor/pygments/formatters/other.py index 990ead48021..de8d9dcf896 100644 --- a/src/pip/_vendor/pygments/formatters/other.py +++ b/src/pip/_vendor/pygments/formatters/other.py @@ -4,7 +4,7 @@ Other formatters: NullFormatter, RawTokenFormatter. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -74,8 +74,7 @@ def __init__(self, **options): try: colorize(self.error_color, '') except KeyError: - raise ValueError("Invalid color %r specified" % - self.error_color) + raise ValueError(f"Invalid color {self.error_color!r} specified") def format(self, tokensource, outfile): try: @@ -147,7 +146,7 @@ def format(self, tokensource, outfile): outbuf = [] for ttype, value in tokensource: rawbuf.append(value) - outbuf.append('%s(%s, %r),\n' % (indentation, ttype, value)) + outbuf.append(f'{indentation}({ttype}, {value!r}),\n') before = TESTCASE_BEFORE % (''.join(rawbuf),) during = ''.join(outbuf) diff --git a/src/pip/_vendor/pygments/formatters/pangomarkup.py b/src/pip/_vendor/pygments/formatters/pangomarkup.py index 6bb325d0788..dfed53ab768 100644 --- a/src/pip/_vendor/pygments/formatters/pangomarkup.py +++ b/src/pip/_vendor/pygments/formatters/pangomarkup.py @@ -4,7 +4,7 @@ Formatter for Pango markup output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -45,7 +45,7 @@ def __init__(self, **options): start = '' end = '' if style['color']: - start += '' % style['color'] + start += ''.format(style['color']) end = '' + end if style['bold']: start += '' diff --git a/src/pip/_vendor/pygments/formatters/rtf.py b/src/pip/_vendor/pygments/formatters/rtf.py index 125189c6fa5..eca2a41a1cd 100644 --- a/src/pip/_vendor/pygments/formatters/rtf.py +++ b/src/pip/_vendor/pygments/formatters/rtf.py @@ -4,12 +4,14 @@ A formatter that generates RTF files. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from collections import OrderedDict from pip._vendor.pygments.formatter import Formatter -from pip._vendor.pygments.util import get_int_opt, surrogatepair +from pip._vendor.pygments.style import _ansimap +from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, surrogatepair __all__ = ['RtfFormatter'] @@ -42,6 +44,59 @@ class RtfFormatter(Formatter): default is 24 half-points, giving a size 12 font. .. versionadded:: 2.0 + + `linenos` + Turn on line numbering (default: ``False``). + + .. versionadded:: 2.18 + + `lineno_fontsize` + Font size for line numbers. Size is specified in half points + (default: `fontsize`). + + .. versionadded:: 2.18 + + `lineno_padding` + Number of spaces between the (inline) line numbers and the + source code (default: ``2``). + + .. versionadded:: 2.18 + + `linenostart` + The line number for the first line (default: ``1``). + + .. versionadded:: 2.18 + + `linenostep` + If set to a number n > 1, only every nth line number is printed. + + .. versionadded:: 2.18 + + `lineno_color` + Color for line numbers specified as a hex triplet, e.g. ``'5e5e5e'``. + Defaults to the style's line number color if it is a hex triplet, + otherwise ansi bright black. + + .. versionadded:: 2.18 + + `hl_lines` + Specify a list of lines to be highlighted, as line numbers separated by + spaces, e.g. ``'3 7 8'``. The line numbers are relative to the input + (i.e. the first line is line 1) unless `hl_linenostart` is set. + + .. versionadded:: 2.18 + + `hl_color` + Color for highlighting the lines specified in `hl_lines`, specified as + a hex triplet (default: style's `highlight_color`). + + .. versionadded:: 2.18 + + `hl_linenostart` + If set to ``True`` line numbers in `hl_lines` are specified + relative to `linenostart` (default ``False``). + + .. versionadded:: 2.18 """ name = 'RTF' aliases = ['rtf'] @@ -62,6 +117,40 @@ def __init__(self, **options): Formatter.__init__(self, **options) self.fontface = options.get('fontface') or '' self.fontsize = get_int_opt(options, 'fontsize', 0) + self.linenos = get_bool_opt(options, 'linenos', False) + self.lineno_fontsize = get_int_opt(options, 'lineno_fontsize', + self.fontsize) + self.lineno_padding = get_int_opt(options, 'lineno_padding', 2) + self.linenostart = abs(get_int_opt(options, 'linenostart', 1)) + self.linenostep = abs(get_int_opt(options, 'linenostep', 1)) + self.hl_linenostart = get_bool_opt(options, 'hl_linenostart', False) + + self.hl_color = options.get('hl_color', '') + if not self.hl_color: + self.hl_color = self.style.highlight_color + + self.hl_lines = [] + for lineno in get_list_opt(options, 'hl_lines', []): + try: + lineno = int(lineno) + if self.hl_linenostart: + lineno = lineno - self.linenostart + 1 + self.hl_lines.append(lineno) + except ValueError: + pass + + self.lineno_color = options.get('lineno_color', '') + if not self.lineno_color: + if self.style.line_number_color == 'inherit': + # style color is the css value 'inherit' + # default to ansi bright-black + self.lineno_color = _ansimap['ansibrightblack'] + else: + # style color is assumed to be a hex triplet as other + # colors in pygments/style.py + self.lineno_color = self.style.line_number_color + + self.color_mapping = self._create_color_mapping() def _escape(self, text): return text.replace('\\', '\\\\') \ @@ -90,43 +179,145 @@ def _escape_text(self, text): # Force surrogate pairs buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn)) - return ''.join(buf).replace('\n', '\\par\n') + return ''.join(buf).replace('\n', '\\par') - def format_unencoded(self, tokensource, outfile): - # rtf 1.8 header - outfile.write('{\\rtf1\\ansi\\uc0\\deff0' - '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}' - '{\\colortbl;' % (self.fontface and - ' ' + self._escape(self.fontface) or - '')) - - # convert colors and save them in a mapping to access them later. - color_mapping = {} + @staticmethod + def hex_to_rtf_color(hex_color): + if hex_color[0] == "#": + hex_color = hex_color[1:] + + return '\\red%d\\green%d\\blue%d;' % ( + int(hex_color[0:2], 16), + int(hex_color[2:4], 16), + int(hex_color[4:6], 16) + ) + + def _split_tokens_on_newlines(self, tokensource): + """ + Split tokens containing newline characters into multiple token + each representing a line of the input file. Needed for numbering + lines of e.g. multiline comments. + """ + for ttype, value in tokensource: + if value == '\n': + yield (ttype, value) + elif "\n" in value: + lines = value.split("\n") + for line in lines[:-1]: + yield (ttype, line+"\n") + if lines[-1]: + yield (ttype, lines[-1]) + else: + yield (ttype, value) + + def _create_color_mapping(self): + """ + Create a mapping of style hex colors to index/offset in + the RTF color table. + """ + color_mapping = OrderedDict() offset = 1 + + if self.linenos: + color_mapping[self.lineno_color] = offset + offset += 1 + + if self.hl_lines: + color_mapping[self.hl_color] = offset + offset += 1 + for _, style in self.style: for color in style['color'], style['bgcolor'], style['border']: if color and color not in color_mapping: color_mapping[color] = offset - outfile.write('\\red%d\\green%d\\blue%d;' % ( - int(color[0:2], 16), - int(color[2:4], 16), - int(color[4:6], 16) - )) offset += 1 - outfile.write('}\\f0 ') + + return color_mapping + + @property + def _lineno_template(self): + if self.lineno_fontsize != self.fontsize: + return '{{\\fs{} \\cf{} %s{}}}'.format(self.lineno_fontsize, + self.color_mapping[self.lineno_color], + " " * self.lineno_padding) + + return '{{\\cf{} %s{}}}'.format(self.color_mapping[self.lineno_color], + " " * self.lineno_padding) + + @property + def _hl_open_str(self): + return rf'{{\highlight{self.color_mapping[self.hl_color]} ' + + @property + def _rtf_header(self): + lines = [] + # rtf 1.8 header + lines.append('{\\rtf1\\ansi\\uc0\\deff0' + '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}' + % (self.fontface and ' ' + + self._escape(self.fontface) or '')) + + # color table + lines.append('{\\colortbl;') + for color, _ in self.color_mapping.items(): + lines.append(self.hex_to_rtf_color(color)) + lines.append('}') + + # font and fontsize + lines.append('\\f0\\sa0') if self.fontsize: - outfile.write('\\fs%d' % self.fontsize) + lines.append('\\fs%d' % self.fontsize) + + # ensure Libre Office Writer imports and renders consecutive + # space characters the same width, needed for line numbering. + # https://bugs.documentfoundation.org/show_bug.cgi?id=144050 + lines.append('\\dntblnsbdb') + + return lines + + def format_unencoded(self, tokensource, outfile): + for line in self._rtf_header: + outfile.write(line + "\n") + + tokensource = self._split_tokens_on_newlines(tokensource) + + # first pass of tokens to count lines, needed for line numbering + if self.linenos: + line_count = 0 + tokens = [] # for copying the token source generator + for ttype, value in tokensource: + tokens.append((ttype, value)) + if value.endswith("\n"): + line_count += 1 + + # width of line number strings (for padding with spaces) + linenos_width = len(str(line_count+self.linenostart-1)) + + tokensource = tokens # highlight stream + lineno = 1 + start_new_line = True for ttype, value in tokensource: + if start_new_line and lineno in self.hl_lines: + outfile.write(self._hl_open_str) + + if start_new_line and self.linenos: + if (lineno-self.linenostart+1)%self.linenostep == 0: + current_lineno = lineno + self.linenostart - 1 + lineno_str = str(current_lineno).rjust(linenos_width) + else: + lineno_str = "".rjust(linenos_width) + outfile.write(self._lineno_template % lineno_str) + while not self.style.styles_token(ttype) and ttype.parent: ttype = ttype.parent style = self.style.style_for_token(ttype) buf = [] if style['bgcolor']: - buf.append('\\cb%d' % color_mapping[style['bgcolor']]) + buf.append('\\cb%d' % self.color_mapping[style['bgcolor']]) if style['color']: - buf.append('\\cf%d' % color_mapping[style['color']]) + buf.append('\\cf%d' % self.color_mapping[style['color']]) if style['bold']: buf.append('\\b') if style['italic']: @@ -135,12 +326,24 @@ def format_unencoded(self, tokensource, outfile): buf.append('\\ul') if style['border']: buf.append('\\chbrdr\\chcfpat%d' % - color_mapping[style['border']]) + self.color_mapping[style['border']]) start = ''.join(buf) if start: - outfile.write('{%s ' % start) + outfile.write(f'{{{start} ') outfile.write(self._escape_text(value)) if start: outfile.write('}') + start_new_line = False + + # complete line of input + if value.endswith("\n"): + # close line highlighting + if lineno in self.hl_lines: + outfile.write('}') + # newline in RTF file after closing } + outfile.write("\n") + + start_new_line = True + lineno += 1 - outfile.write('}') + outfile.write('}\n') diff --git a/src/pip/_vendor/pygments/formatters/svg.py b/src/pip/_vendor/pygments/formatters/svg.py index a8727ed8592..d3e018ffd80 100644 --- a/src/pip/_vendor/pygments/formatters/svg.py +++ b/src/pip/_vendor/pygments/formatters/svg.py @@ -4,7 +4,7 @@ Formatter for SVG output. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -60,11 +60,11 @@ class SvgFormatter(Formatter): `linenostep` If set to a number n > 1, only every nth line number is printed. - + `linenowidth` Maximum width devoted to line numbers (default: ``3*ystep``, sufficient - for up to 4-digit line numbers. Increase width for longer code blocks). - + for up to 4-digit line numbers. Increase width for longer code blocks). + `xoffset` Starting offset in X direction, defaults to ``0``. @@ -97,10 +97,11 @@ def __init__(self, **options): self.fontsize = options.get('fontsize', '14px') self.xoffset = get_int_opt(options, 'xoffset', 0) fs = self.fontsize.strip() - if fs.endswith('px'): fs = fs[:-2].strip() + if fs.endswith('px'): + fs = fs[:-2].strip() try: int_fs = int(fs) - except: + except ValueError: int_fs = 20 self.yoffset = get_int_opt(options, 'yoffset', int_fs) self.ystep = get_int_opt(options, 'ystep', int_fs + 5) @@ -122,30 +123,27 @@ def format_unencoded(self, tokensource, outfile): y = self.yoffset if not self.nowrap: if self.encoding: - outfile.write('\n' % - self.encoding) + outfile.write(f'\n') else: outfile.write('\n') outfile.write('\n') outfile.write('\n') - outfile.write('\n' % - (self.fontfamily, self.fontsize)) - - counter = self.linenostart + outfile.write(f'\n') + + counter = self.linenostart counter_step = self.linenostep counter_style = self._get_style(Comment) line_x = x - + if self.linenos: if counter % counter_step == 0: - outfile.write('%s' % - (x+self.linenowidth,y,counter_style,counter)) + outfile.write(f'{counter}') line_x += self.linenowidth + self.ystep counter += 1 - outfile.write('' % (line_x, y)) + outfile.write(f'') for ttype, value in tokensource: style = self._get_style(ttype) tspan = style and '' or '' @@ -159,11 +157,10 @@ def format_unencoded(self, tokensource, outfile): y += self.ystep outfile.write('\n') if self.linenos and counter % counter_step == 0: - outfile.write('%s' % - (x+self.linenowidth,y,counter_style,counter)) - + outfile.write(f'{counter}') + counter += 1 - outfile.write('' % (line_x,y)) + outfile.write(f'') outfile.write(tspan + parts[-1] + tspanend) outfile.write('') diff --git a/src/pip/_vendor/pygments/formatters/terminal.py b/src/pip/_vendor/pygments/formatters/terminal.py index abb8770811f..51b902d3e24 100644 --- a/src/pip/_vendor/pygments/formatters/terminal.py +++ b/src/pip/_vendor/pygments/formatters/terminal.py @@ -4,7 +4,7 @@ Formatter for terminal output with ANSI sequences. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/src/pip/_vendor/pygments/formatters/terminal256.py b/src/pip/_vendor/pygments/formatters/terminal256.py index 0cfe5d1612e..5f254051a80 100644 --- a/src/pip/_vendor/pygments/formatters/terminal256.py +++ b/src/pip/_vendor/pygments/formatters/terminal256.py @@ -10,7 +10,7 @@ Formatter version 1. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/src/pip/_vendor/pygments/lexer.py b/src/pip/_vendor/pygments/lexer.py index 26c5fb31ffa..1348be58782 100644 --- a/src/pip/_vendor/pygments/lexer.py +++ b/src/pip/_vendor/pygments/lexer.py @@ -4,7 +4,7 @@ Base lexer classes. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -67,10 +67,12 @@ class Lexer(metaclass=LexerMeta): :no-value: .. autoattribute:: priority - Lexers included in Pygments should have an additional attribute: + Lexers included in Pygments should have two additional attributes: .. autoattribute:: url :no-value: + .. autoattribute:: version_added + :no-value: Lexers included in Pygments may have additional attributes: @@ -130,9 +132,12 @@ class Lexer(metaclass=LexerMeta): priority = 0 #: URL of the language specification/definition. Used in the Pygments - #: documentation. + #: documentation. Set to an empty string to disable. url = None + #: Version of Pygments in which the lexer was added. + version_added = None + #: Example file name. Relative to the ``tests/examplefiles`` directory. #: This is used by the documentation generator to show an example. _example = None @@ -169,10 +174,9 @@ def __init__(self, **options): def __repr__(self): if self.options: - return '' % (self.__class__.__name__, - self.options) + return f'' else: - return '' % self.__class__.__name__ + return f'' def add_filter(self, filter_, **options): """ @@ -508,7 +512,7 @@ def _process_regex(cls, regex, rflags, state): def _process_token(cls, token): """Preprocess the token component of a token definition.""" assert type(token) is _TokenType or callable(token), \ - 'token type must be simple type or callable, not %r' % (token,) + f'token type must be simple type or callable, not {token!r}' return token def _process_new_state(cls, new_state, unprocessed, processed): @@ -524,14 +528,14 @@ def _process_new_state(cls, new_state, unprocessed, processed): elif new_state[:5] == '#pop:': return -int(new_state[5:]) else: - assert False, 'unknown new state %r' % new_state + assert False, f'unknown new state {new_state!r}' elif isinstance(new_state, combined): # combine a new state from existing ones tmp_state = '_tmp_%d' % cls._tmpname cls._tmpname += 1 itokens = [] for istate in new_state: - assert istate != new_state, 'circular state ref %r' % istate + assert istate != new_state, f'circular state ref {istate!r}' itokens.extend(cls._process_state(unprocessed, processed, istate)) processed[tmp_state] = itokens @@ -544,12 +548,12 @@ def _process_new_state(cls, new_state, unprocessed, processed): 'unknown new state ' + istate return new_state else: - assert False, 'unknown new state def %r' % new_state + assert False, f'unknown new state def {new_state!r}' def _process_state(cls, unprocessed, processed, state): """Preprocess a single state definition.""" - assert type(state) is str, "wrong state name %r" % state - assert state[0] != '#', "invalid state name %r" % state + assert isinstance(state, str), f"wrong state name {state!r}" + assert state[0] != '#', f"invalid state name {state!r}" if state in processed: return processed[state] tokens = processed[state] = [] @@ -557,7 +561,7 @@ def _process_state(cls, unprocessed, processed, state): for tdef in unprocessed[state]: if isinstance(tdef, include): # it's a state reference - assert tdef != state, "circular state reference %r" % state + assert tdef != state, f"circular state reference {state!r}" tokens.extend(cls._process_state(unprocessed, processed, str(tdef))) continue @@ -571,13 +575,12 @@ def _process_state(cls, unprocessed, processed, state): tokens.append((re.compile('').match, None, new_state)) continue - assert type(tdef) is tuple, "wrong rule def %r" % tdef + assert type(tdef) is tuple, f"wrong rule def {tdef!r}" try: rex = cls._process_regex(tdef[0], rflags, state) except Exception as err: - raise ValueError("uncompilable regex %r in state %r of %r: %s" % - (tdef[0], state, cls, err)) from err + raise ValueError(f"uncompilable regex {tdef[0]!r} in state {state!r} of {cls!r}: {err}") from err token = cls._process_token(tdef[1]) @@ -738,7 +741,7 @@ def get_tokens_unprocessed(self, text, stack=('root',)): elif new_state == '#push': statestack.append(statestack[-1]) else: - assert False, "wrong state def: %r" % new_state + assert False, f"wrong state def: {new_state!r}" statetokens = tokendefs[statestack[-1]] break else: @@ -770,8 +773,7 @@ def __init__(self, text, pos, stack=None, end=None): self.stack = stack or ['root'] def __repr__(self): - return 'LexerContext(%r, %r, %r)' % ( - self.text, self.pos, self.stack) + return f'LexerContext({self.text!r}, {self.pos!r}, {self.stack!r})' class ExtendedRegexLexer(RegexLexer): @@ -826,7 +828,7 @@ def get_tokens_unprocessed(self, text=None, context=None): elif new_state == '#push': ctx.stack.append(ctx.stack[-1]) else: - assert False, "wrong state def: %r" % new_state + assert False, f"wrong state def: {new_state!r}" statetokens = tokendefs[ctx.stack[-1]] break else: diff --git a/src/pip/_vendor/pygments/lexers/__init__.py b/src/pip/_vendor/pygments/lexers/__init__.py index 0c176dfbfd6..ac88645a1b0 100644 --- a/src/pip/_vendor/pygments/lexers/__init__.py +++ b/src/pip/_vendor/pygments/lexers/__init__.py @@ -4,7 +4,7 @@ Pygments lexers. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -93,7 +93,7 @@ def find_lexer_class_by_name(_alias): .. versionadded:: 2.2 """ if not _alias: - raise ClassNotFound('no lexer for alias %r found' % _alias) + raise ClassNotFound(f'no lexer for alias {_alias!r} found') # lookup builtin lexers for module_name, name, aliases, _, _ in LEXERS.values(): if _alias.lower() in aliases: @@ -104,7 +104,7 @@ def find_lexer_class_by_name(_alias): for cls in find_plugin_lexers(): if _alias.lower() in cls.aliases: return cls - raise ClassNotFound('no lexer for alias %r found' % _alias) + raise ClassNotFound(f'no lexer for alias {_alias!r} found') def get_lexer_by_name(_alias, **options): @@ -117,7 +117,7 @@ def get_lexer_by_name(_alias, **options): found. """ if not _alias: - raise ClassNotFound('no lexer for alias %r found' % _alias) + raise ClassNotFound(f'no lexer for alias {_alias!r} found') # lookup builtin lexers for module_name, name, aliases, _, _ in LEXERS.values(): @@ -129,7 +129,7 @@ def get_lexer_by_name(_alias, **options): for cls in find_plugin_lexers(): if _alias.lower() in cls.aliases: return cls(**options) - raise ClassNotFound('no lexer for alias %r found' % _alias) + raise ClassNotFound(f'no lexer for alias {_alias!r} found') def load_lexer_from_file(filename, lexername="CustomLexer", **options): @@ -154,17 +154,16 @@ def load_lexer_from_file(filename, lexername="CustomLexer", **options): exec(f.read(), custom_namespace) # Retrieve the class `lexername` from that namespace if lexername not in custom_namespace: - raise ClassNotFound('no valid %s class found in %s' % - (lexername, filename)) + raise ClassNotFound(f'no valid {lexername} class found in {filename}') lexer_class = custom_namespace[lexername] # And finally instantiate it with the options return lexer_class(**options) except OSError as err: - raise ClassNotFound('cannot read %s: %s' % (filename, err)) + raise ClassNotFound(f'cannot read {filename}: {err}') except ClassNotFound: raise except Exception as err: - raise ClassNotFound('error when loading custom lexer: %s' % err) + raise ClassNotFound(f'error when loading custom lexer: {err}') def find_lexer_class_for_filename(_fn, code=None): @@ -225,7 +224,7 @@ def get_lexer_for_filename(_fn, code=None, **options): """ res = find_lexer_class_for_filename(_fn, code) if not res: - raise ClassNotFound('no lexer for filename %r found' % _fn) + raise ClassNotFound(f'no lexer for filename {_fn!r} found') return res(**options) @@ -245,7 +244,7 @@ def get_lexer_for_mimetype(_mime, **options): for cls in find_plugin_lexers(): if _mime in cls.mimetypes: return cls(**options) - raise ClassNotFound('no lexer for mimetype %r found' % _mime) + raise ClassNotFound(f'no lexer for mimetype {_mime!r} found') def _iter_lexerclasses(plugins=True): @@ -280,7 +279,7 @@ def guess_lexer_for_filename(_fn, _text, **options): matching_lexers.add(lexer) primary[lexer] = False if not matching_lexers: - raise ClassNotFound('no lexer for filename %r found' % fn) + raise ClassNotFound(f'no lexer for filename {fn!r} found') if len(matching_lexers) == 1: return matching_lexers.pop()(**options) result = [] diff --git a/src/pip/_vendor/pygments/lexers/_mapping.py b/src/pip/_vendor/pygments/lexers/_mapping.py index 1ff2b282a12..f3e5c460db3 100644 --- a/src/pip/_vendor/pygments/lexers/_mapping.py +++ b/src/pip/_vendor/pygments/lexers/_mapping.py @@ -46,7 +46,7 @@ 'BSTLexer': ('pip._vendor.pygments.lexers.bibtex', 'BST', ('bst', 'bst-pybtex'), ('*.bst',), ()), 'BareLexer': ('pip._vendor.pygments.lexers.bare', 'BARE', ('bare',), ('*.bare',), ()), 'BaseMakefileLexer': ('pip._vendor.pygments.lexers.make', 'Base Makefile', ('basemake',), (), ()), - 'BashLexer': ('pip._vendor.pygments.lexers.shell', 'Bash', ('bash', 'sh', 'ksh', 'zsh', 'shell'), ('*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', '*.exheres-0', '*.exlib', '*.zsh', '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', '.kshrc', 'kshrc', 'PKGBUILD'), ('application/x-sh', 'application/x-shellscript', 'text/x-shellscript')), + 'BashLexer': ('pip._vendor.pygments.lexers.shell', 'Bash', ('bash', 'sh', 'ksh', 'zsh', 'shell', 'openrc'), ('*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', '*.exheres-0', '*.exlib', '*.zsh', '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', '.kshrc', 'kshrc', 'PKGBUILD'), ('application/x-sh', 'application/x-shellscript', 'text/x-shellscript')), 'BashSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Bash Session', ('console', 'shell-session'), ('*.sh-session', '*.shell-session'), ('application/x-shell-session', 'application/x-sh-session')), 'BatchLexer': ('pip._vendor.pygments.lexers.shell', 'Batchfile', ('batch', 'bat', 'dosbatch', 'winbatch'), ('*.bat', '*.cmd'), ('application/x-dos-batch',)), 'BddLexer': ('pip._vendor.pygments.lexers.bdd', 'Bdd', ('bdd',), ('*.feature',), ('text/x-bdd',)), @@ -128,7 +128,7 @@ 'DaxLexer': ('pip._vendor.pygments.lexers.dax', 'Dax', ('dax',), ('*.dax',), ()), 'DebianControlLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Control file', ('debcontrol', 'control'), ('control',), ()), 'DelphiLexer': ('pip._vendor.pygments.lexers.pascal', 'Delphi', ('delphi', 'pas', 'pascal', 'objectpascal'), ('*.pas', '*.dpr'), ('text/x-pascal',)), - 'DesktopLexer': ('pip._vendor.pygments.lexers.configs', 'Desktop file', ('desktop',), ('*.desktop',), ()), + 'DesktopLexer': ('pip._vendor.pygments.lexers.configs', 'Desktop file', ('desktop',), ('*.desktop',), ('application/x-desktop',)), 'DevicetreeLexer': ('pip._vendor.pygments.lexers.devicetree', 'Devicetree', ('devicetree', 'dts'), ('*.dts', '*.dtsi'), ('text/x-c',)), 'DgLexer': ('pip._vendor.pygments.lexers.python', 'dg', ('dg',), ('*.dg',), ('text/x-dg',)), 'DiffLexer': ('pip._vendor.pygments.lexers.diff', 'Diff', ('diff', 'udiff'), ('*.diff', '*.patch'), ('text/x-diff', 'text/x-patch')), @@ -216,8 +216,8 @@ 'HtmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Smarty', ('html+smarty',), (), ('text/html+smarty',)), 'HttpLexer': ('pip._vendor.pygments.lexers.textfmts', 'HTTP', ('http',), (), ()), 'HxmlLexer': ('pip._vendor.pygments.lexers.haxe', 'Hxml', ('haxeml', 'hxml'), ('*.hxml',), ()), - 'HyLexer': ('pip._vendor.pygments.lexers.lisp', 'Hy', ('hylang',), ('*.hy',), ('text/x-hy', 'application/x-hy')), - 'HybrisLexer': ('pip._vendor.pygments.lexers.scripting', 'Hybris', ('hybris', 'hy'), ('*.hy', '*.hyb'), ('text/x-hybris', 'application/x-hybris')), + 'HyLexer': ('pip._vendor.pygments.lexers.lisp', 'Hy', ('hylang', 'hy'), ('*.hy',), ('text/x-hy', 'application/x-hy')), + 'HybrisLexer': ('pip._vendor.pygments.lexers.scripting', 'Hybris', ('hybris',), ('*.hyb',), ('text/x-hybris', 'application/x-hybris')), 'IDLLexer': ('pip._vendor.pygments.lexers.idl', 'IDL', ('idl',), ('*.pro',), ('text/idl',)), 'IconLexer': ('pip._vendor.pygments.lexers.unicon', 'Icon', ('icon',), ('*.icon', '*.ICON'), ()), 'IdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Idris', ('idris', 'idr'), ('*.idr',), ('text/x-idris',)), @@ -234,6 +234,7 @@ 'JMESPathLexer': ('pip._vendor.pygments.lexers.jmespath', 'JMESPath', ('jmespath', 'jp'), ('*.jp',), ()), 'JSLTLexer': ('pip._vendor.pygments.lexers.jslt', 'JSLT', ('jslt',), ('*.jslt',), ('text/x-jslt',)), 'JagsLexer': ('pip._vendor.pygments.lexers.modeling', 'JAGS', ('jags',), ('*.jag', '*.bug'), ()), + 'JanetLexer': ('pip._vendor.pygments.lexers.lisp', 'Janet', ('janet',), ('*.janet', '*.jdn'), ('text/x-janet', 'application/x-janet')), 'JasminLexer': ('pip._vendor.pygments.lexers.jvm', 'Jasmin', ('jasmin', 'jasminxt'), ('*.j',), ()), 'JavaLexer': ('pip._vendor.pygments.lexers.jvm', 'Java', ('java',), ('*.java',), ('text/x-java',)), 'JavascriptDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Django/Jinja', ('javascript+django', 'js+django', 'javascript+jinja', 'js+jinja'), ('*.js.j2', '*.js.jinja2'), ('application/x-javascript+django', 'application/x-javascript+jinja', 'text/x-javascript+django', 'text/x-javascript+jinja', 'text/javascript+django', 'text/javascript+jinja')), @@ -271,6 +272,7 @@ 'LdaprcLexer': ('pip._vendor.pygments.lexers.ldap', 'LDAP configuration file', ('ldapconf', 'ldaprc'), ('.ldaprc', 'ldaprc', 'ldap.conf'), ('text/x-ldapconf',)), 'LdifLexer': ('pip._vendor.pygments.lexers.ldap', 'LDIF', ('ldif',), ('*.ldif',), ('text/x-ldif',)), 'Lean3Lexer': ('pip._vendor.pygments.lexers.lean', 'Lean', ('lean', 'lean3'), ('*.lean',), ('text/x-lean', 'text/x-lean3')), + 'Lean4Lexer': ('pip._vendor.pygments.lexers.lean', 'Lean4', ('lean4',), ('*.lean',), ('text/x-lean4',)), 'LessCssLexer': ('pip._vendor.pygments.lexers.css', 'LessCss', ('less',), ('*.less',), ('text/x-less-css',)), 'LighttpdConfLexer': ('pip._vendor.pygments.lexers.configs', 'Lighttpd configuration file', ('lighttpd', 'lighty'), ('lighttpd.conf',), ('text/x-lighttpd-conf',)), 'LilyPondLexer': ('pip._vendor.pygments.lexers.lilypond', 'LilyPond', ('lilypond',), ('*.ly',), ()), @@ -287,6 +289,7 @@ 'LogosLexer': ('pip._vendor.pygments.lexers.objective', 'Logos', ('logos',), ('*.x', '*.xi', '*.xm', '*.xmi'), ('text/x-logos',)), 'LogtalkLexer': ('pip._vendor.pygments.lexers.prolog', 'Logtalk', ('logtalk',), ('*.lgt', '*.logtalk'), ('text/x-logtalk',)), 'LuaLexer': ('pip._vendor.pygments.lexers.scripting', 'Lua', ('lua',), ('*.lua', '*.wlua'), ('text/x-lua', 'application/x-lua')), + 'LuauLexer': ('pip._vendor.pygments.lexers.scripting', 'Luau', ('luau',), ('*.luau',), ()), 'MCFunctionLexer': ('pip._vendor.pygments.lexers.minecraft', 'MCFunction', ('mcfunction', 'mcf'), ('*.mcfunction',), ('text/mcfunction',)), 'MCSchemaLexer': ('pip._vendor.pygments.lexers.minecraft', 'MCSchema', ('mcschema',), ('*.mcschema',), ('text/mcschema',)), 'MIMELexer': ('pip._vendor.pygments.lexers.mime', 'MIME', ('mime',), (), ('multipart/mixed', 'multipart/related', 'multipart/alternative')), @@ -314,6 +317,7 @@ 'ModelicaLexer': ('pip._vendor.pygments.lexers.modeling', 'Modelica', ('modelica',), ('*.mo',), ('text/x-modelica',)), 'Modula2Lexer': ('pip._vendor.pygments.lexers.modula2', 'Modula-2', ('modula2', 'm2'), ('*.def', '*.mod'), ('text/x-modula2',)), 'MoinWikiLexer': ('pip._vendor.pygments.lexers.markup', 'MoinMoin/Trac Wiki markup', ('trac-wiki', 'moin'), (), ('text/x-trac-wiki',)), + 'MojoLexer': ('pip._vendor.pygments.lexers.mojo', 'Mojo', ('mojo', '🔥'), ('*.mojo', '*.🔥'), ('text/x-mojo', 'application/x-mojo')), 'MonkeyLexer': ('pip._vendor.pygments.lexers.basic', 'Monkey', ('monkey',), ('*.monkey',), ('text/x-monkey',)), 'MonteLexer': ('pip._vendor.pygments.lexers.monte', 'Monte', ('monte',), ('*.mt',), ()), 'MoonScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MoonScript', ('moonscript', 'moon'), ('*.moon',), ('text/x-moonscript', 'application/x-moonscript')), @@ -362,6 +366,7 @@ 'OpaLexer': ('pip._vendor.pygments.lexers.ml', 'Opa', ('opa',), ('*.opa',), ('text/x-opa',)), 'OpenEdgeLexer': ('pip._vendor.pygments.lexers.business', 'OpenEdge ABL', ('openedge', 'abl', 'progress'), ('*.p', '*.cls'), ('text/x-openedge', 'application/x-openedge')), 'OpenScadLexer': ('pip._vendor.pygments.lexers.openscad', 'OpenSCAD', ('openscad',), ('*.scad',), ('application/x-openscad',)), + 'OrgLexer': ('pip._vendor.pygments.lexers.markup', 'Org Mode', ('org', 'orgmode', 'org-mode'), ('*.org',), ('text/org',)), 'OutputLexer': ('pip._vendor.pygments.lexers.special', 'Text output', ('output',), (), ()), 'PacmanConfLexer': ('pip._vendor.pygments.lexers.configs', 'PacmanConf', ('pacmanconf',), ('pacman.conf',), ()), 'PanLexer': ('pip._vendor.pygments.lexers.dsls', 'Pan', ('pan',), ('*.pan',), ()), @@ -390,6 +395,7 @@ 'ProcfileLexer': ('pip._vendor.pygments.lexers.procfile', 'Procfile', ('procfile',), ('Procfile',), ()), 'PrologLexer': ('pip._vendor.pygments.lexers.prolog', 'Prolog', ('prolog',), ('*.ecl', '*.prolog', '*.pro', '*.pl'), ('text/x-prolog',)), 'PromQLLexer': ('pip._vendor.pygments.lexers.promql', 'PromQL', ('promql',), ('*.promql',), ()), + 'PromelaLexer': ('pip._vendor.pygments.lexers.c_like', 'Promela', ('promela',), ('*.pml', '*.prom', '*.prm', '*.promela', '*.pr', '*.pm'), ('text/x-promela',)), 'PropertiesLexer': ('pip._vendor.pygments.lexers.configs', 'Properties', ('properties', 'jproperties'), ('*.properties',), ('text/x-java-properties',)), 'ProtoBufLexer': ('pip._vendor.pygments.lexers.dsls', 'Protocol Buffer', ('protobuf', 'proto'), ('*.proto',), ()), 'PrqlLexer': ('pip._vendor.pygments.lexers.prql', 'PRQL', ('prql',), ('*.prql',), ('application/prql', 'application/x-prql')), @@ -400,7 +406,7 @@ 'PyPyLogLexer': ('pip._vendor.pygments.lexers.console', 'PyPy Log', ('pypylog', 'pypy'), ('*.pypylog',), ('application/x-pypylog',)), 'Python2Lexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x', ('python2', 'py2'), (), ('text/x-python2', 'application/x-python2')), 'Python2TracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x Traceback', ('py2tb',), ('*.py2tb',), ('text/x-python2-traceback',)), - 'PythonConsoleLexer': ('pip._vendor.pygments.lexers.python', 'Python console session', ('pycon',), (), ('text/x-python-doctest',)), + 'PythonConsoleLexer': ('pip._vendor.pygments.lexers.python', 'Python console session', ('pycon', 'python-console'), (), ('text/x-python-doctest',)), 'PythonLexer': ('pip._vendor.pygments.lexers.python', 'Python', ('python', 'py', 'sage', 'python3', 'py3', 'bazel', 'starlark'), ('*.py', '*.pyw', '*.pyi', '*.jy', '*.sage', '*.sc', 'SConstruct', 'SConscript', '*.bzl', 'BUCK', 'BUILD', 'BUILD.bazel', 'WORKSPACE', '*.tac'), ('text/x-python', 'application/x-python', 'text/x-python3', 'application/x-python3')), 'PythonTracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python Traceback', ('pytb', 'py3tb'), ('*.pytb', '*.py3tb'), ('text/x-python-traceback', 'text/x-python3-traceback')), 'PythonUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Python+UL4', ('py+ul4',), ('*.pyul4',), ()), @@ -473,6 +479,7 @@ 'SnobolLexer': ('pip._vendor.pygments.lexers.snobol', 'Snobol', ('snobol',), ('*.snobol',), ('text/x-snobol',)), 'SnowballLexer': ('pip._vendor.pygments.lexers.dsls', 'Snowball', ('snowball',), ('*.sbl',), ()), 'SolidityLexer': ('pip._vendor.pygments.lexers.solidity', 'Solidity', ('solidity',), ('*.sol',), ()), + 'SoongLexer': ('pip._vendor.pygments.lexers.soong', 'Soong', ('androidbp', 'bp', 'soong'), ('Android.bp',), ()), 'SophiaLexer': ('pip._vendor.pygments.lexers.sophia', 'Sophia', ('sophia',), ('*.aes',), ()), 'SourcePawnLexer': ('pip._vendor.pygments.lexers.pawn', 'SourcePawn', ('sp',), ('*.sp',), ('text/x-sourcepawn',)), 'SourcesListLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Sourcelist', ('debsources', 'sourceslist', 'sources.list'), ('sources.list',), ()), @@ -494,6 +501,7 @@ 'TAPLexer': ('pip._vendor.pygments.lexers.testing', 'TAP', ('tap',), ('*.tap',), ()), 'TNTLexer': ('pip._vendor.pygments.lexers.tnt', 'Typographic Number Theory', ('tnt',), ('*.tnt',), ()), 'TOMLLexer': ('pip._vendor.pygments.lexers.configs', 'TOML', ('toml',), ('*.toml', 'Pipfile', 'poetry.lock'), ('application/toml',)), + 'TactLexer': ('pip._vendor.pygments.lexers.tact', 'Tact', ('tact',), ('*.tact',), ()), 'Tads3Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'TADS 3', ('tads3',), ('*.t',), ()), 'TalLexer': ('pip._vendor.pygments.lexers.tal', 'Tal', ('tal', 'uxntal'), ('*.tal',), ('text/x-uxntal',)), 'TasmLexer': ('pip._vendor.pygments.lexers.asm', 'TASM', ('tasm',), ('*.asm', '*.ASM', '*.tasm'), ('text/x-tasm',)), @@ -523,6 +531,7 @@ 'TypoScriptCssDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptCssData', ('typoscriptcssdata',), (), ()), 'TypoScriptHtmlDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptHtmlData', ('typoscripthtmldata',), (), ()), 'TypoScriptLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScript', ('typoscript',), ('*.typoscript',), ('text/x-typoscript',)), + 'TypstLexer': ('pip._vendor.pygments.lexers.typst', 'Typst', ('typst',), ('*.typ',), ('text/x-typst',)), 'UL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'UL4', ('ul4',), ('*.ul4',), ()), 'UcodeLexer': ('pip._vendor.pygments.lexers.unicon', 'ucode', ('ucode',), ('*.u', '*.u1', '*.u2'), ()), 'UniconLexer': ('pip._vendor.pygments.lexers.unicon', 'Unicon', ('unicon',), ('*.icn',), ('text/unicon',)), @@ -537,7 +546,7 @@ 'VGLLexer': ('pip._vendor.pygments.lexers.dsls', 'VGL', ('vgl',), ('*.rpf',), ()), 'ValaLexer': ('pip._vendor.pygments.lexers.c_like', 'Vala', ('vala', 'vapi'), ('*.vala', '*.vapi'), ('text/x-vala',)), 'VbNetAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-vb', ('aspx-vb',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()), - 'VbNetLexer': ('pip._vendor.pygments.lexers.dotnet', 'VB.net', ('vb.net', 'vbnet', 'lobas', 'oobas', 'sobas'), ('*.vb', '*.bas'), ('text/x-vbnet', 'text/x-vba')), + 'VbNetLexer': ('pip._vendor.pygments.lexers.dotnet', 'VB.net', ('vb.net', 'vbnet', 'lobas', 'oobas', 'sobas', 'visual-basic', 'visualbasic'), ('*.vb', '*.bas'), ('text/x-vbnet', 'text/x-vba')), 'VelocityHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Velocity', ('html+velocity',), (), ('text/html+velocity',)), 'VelocityLexer': ('pip._vendor.pygments.lexers.templates', 'Velocity', ('velocity',), ('*.vm', '*.fhtml'), ()), 'VelocityXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Velocity', ('xml+velocity',), (), ('application/xml+velocity',)), diff --git a/src/pip/_vendor/pygments/lexers/python.py b/src/pip/_vendor/pygments/lexers/python.py index e2ce58f5a19..b2d07f20800 100644 --- a/src/pip/_vendor/pygments/lexers/python.py +++ b/src/pip/_vendor/pygments/lexers/python.py @@ -4,15 +4,14 @@ Lexers for Python and related languages. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import re import keyword -from pip._vendor.pygments.lexer import DelegatingLexer, Lexer, RegexLexer, include, \ - bygroups, using, default, words, combined, do_insertions, this, line_re +from pip._vendor.pygments.lexer import DelegatingLexer, RegexLexer, include, \ + bygroups, using, default, words, combined, this from pip._vendor.pygments.util import get_bool_opt, shebang_matches from pip._vendor.pygments.token import Text, Comment, Operator, Keyword, Name, String, \ Number, Punctuation, Generic, Other, Error, Whitespace @@ -27,8 +26,6 @@ class PythonLexer(RegexLexer): """ For Python source code (version 3.x). - .. versionadded:: 0.10 - .. versionchanged:: 2.5 This is now the default ``PythonLexer``. It is still available as the alias ``Python3Lexer``. @@ -61,8 +58,9 @@ class PythonLexer(RegexLexer): ] mimetypes = ['text/x-python', 'application/x-python', 'text/x-python3', 'application/x-python3'] + version_added = '0.10' - uni_name = "[%s][%s]*" % (uni.xid_start, uni.xid_continue) + uni_name = f"[{uni.xid_start}][{uni.xid_continue}]*" def innerstring_rules(ttype): return [ @@ -224,7 +222,8 @@ def fstring_rules(ttype): r'(match|case)\b' # a possible keyword r'(?![ \t]*(?:' # not followed by... r'[:,;=^&|@~)\]}]|(?:' + # characters and keywords that mean this isn't - r'|'.join(keyword.kwlist) + r')\b))', # pattern matching + # pattern matching (but None/True/False is ok) + r'|'.join(k for k in keyword.kwlist if k[0].islower()) + r')\b))', bygroups(Text, Keyword), 'soft-keywords-inner'), ], 'soft-keywords-inner': [ @@ -429,6 +428,7 @@ class Python2Lexer(RegexLexer): aliases = ['python2', 'py2'] filenames = [] # now taken over by PythonLexer (3.x) mimetypes = ['text/x-python2', 'application/x-python2'] + version_added = '' def innerstring_rules(ttype): return [ @@ -637,7 +637,7 @@ def analyse_text(text): class _PythonConsoleLexerBase(RegexLexer): name = 'Python console session' - aliases = ['pycon'] + aliases = ['pycon', 'python-console'] mimetypes = ['text/x-python-doctest'] """Auxiliary lexer for `PythonConsoleLexer`. @@ -696,8 +696,10 @@ class PythonConsoleLexer(DelegatingLexer): """ name = 'Python console session' - aliases = ['pycon'] + aliases = ['pycon', 'python-console'] mimetypes = ['text/x-python-doctest'] + url = 'https://python.org' + version_added = '' def __init__(self, **options): python3 = get_bool_opt(options, 'python3', True) @@ -721,8 +723,6 @@ class PythonTracebackLexer(RegexLexer): """ For Python 3.x tracebacks, with support for chained exceptions. - .. versionadded:: 1.0 - .. versionchanged:: 2.5 This is now the default ``PythonTracebackLexer``. It is still available as the alias ``Python3TracebackLexer``. @@ -732,6 +732,8 @@ class PythonTracebackLexer(RegexLexer): aliases = ['pytb', 'py3tb'] filenames = ['*.pytb', '*.py3tb'] mimetypes = ['text/x-python-traceback', 'text/x-python3-traceback'] + url = 'https://python.org' + version_added = '1.0' tokens = { 'root': [ @@ -778,8 +780,6 @@ class Python2TracebackLexer(RegexLexer): """ For Python tracebacks. - .. versionadded:: 0.7 - .. versionchanged:: 2.5 This class has been renamed from ``PythonTracebackLexer``. ``PythonTracebackLexer`` now refers to the Python 3 variant. @@ -789,6 +789,8 @@ class Python2TracebackLexer(RegexLexer): aliases = ['py2tb'] filenames = ['*.py2tb'] mimetypes = ['text/x-python2-traceback'] + url = 'https://python.org' + version_added = '0.7' tokens = { 'root': [ @@ -825,8 +827,6 @@ class Python2TracebackLexer(RegexLexer): class CythonLexer(RegexLexer): """ For Pyrex and Cython source code. - - .. versionadded:: 1.1 """ name = 'Cython' @@ -834,6 +834,7 @@ class CythonLexer(RegexLexer): aliases = ['cython', 'pyx', 'pyrex'] filenames = ['*.pyx', '*.pxd', '*.pxi'] mimetypes = ['text/x-cython', 'application/x-cython'] + version_added = '1.1' tokens = { 'root': [ @@ -1007,13 +1008,13 @@ class DgLexer(RegexLexer): Lexer for dg, a functional and object-oriented programming language running on the CPython 3 VM. - - .. versionadded:: 1.6 """ name = 'dg' aliases = ['dg'] filenames = ['*.dg'] mimetypes = ['text/x-dg'] + url = 'http://pyos.github.io/dg' + version_added = '1.6' tokens = { 'root': [ @@ -1104,13 +1105,12 @@ class DgLexer(RegexLexer): class NumPyLexer(PythonLexer): """ A Python lexer recognizing Numerical Python builtins. - - .. versionadded:: 0.10 """ name = 'NumPy' url = 'https://numpy.org/' aliases = ['numpy'] + version_added = '0.10' # override the mimetypes to not inherit them from python mimetypes = [] diff --git a/src/pip/_vendor/pygments/modeline.py b/src/pip/_vendor/pygments/modeline.py index 7b6f6a324ba..e4d9fe167bd 100644 --- a/src/pip/_vendor/pygments/modeline.py +++ b/src/pip/_vendor/pygments/modeline.py @@ -4,7 +4,7 @@ A simple modeline parser (based on pymodeline). - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -19,7 +19,7 @@ ''', re.VERBOSE) -def get_filetype_from_line(l): +def get_filetype_from_line(l): # noqa: E741 m = modeline_re.search(l) if m: return m.group(1) @@ -30,8 +30,8 @@ def get_filetype_from_buffer(buf, max_lines=5): Scan the buffer for modelines and return filetype if one is found. """ lines = buf.splitlines() - for l in lines[-1:-max_lines-1:-1]: - ret = get_filetype_from_line(l) + for line in lines[-1:-max_lines-1:-1]: + ret = get_filetype_from_line(line) if ret: return ret for i in range(max_lines, -1, -1): diff --git a/src/pip/_vendor/pygments/plugin.py b/src/pip/_vendor/pygments/plugin.py index 7b722d58db0..2e462f2c2f9 100644 --- a/src/pip/_vendor/pygments/plugin.py +++ b/src/pip/_vendor/pygments/plugin.py @@ -2,12 +2,7 @@ pygments.plugin ~~~~~~~~~~~~~~~ - Pygments plugin interface. By default, this tries to use - ``importlib.metadata``, which is in the Python standard - library since Python 3.8, or its ``importlib_metadata`` - backport for earlier versions of Python. It falls back on - ``pkg_resources`` if not found. Finally, if ``pkg_resources`` - is not found either, no plugins are loaded at all. + Pygments plugin interface. lexer plugins:: @@ -34,9 +29,10 @@ yourfilter = yourfilter:YourFilter - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from importlib.metadata import entry_points LEXER_ENTRY_POINT = 'pygments.lexers' FORMATTER_ENTRY_POINT = 'pygments.formatters' @@ -45,18 +41,6 @@ def iter_entry_points(group_name): - try: - from importlib.metadata import entry_points - except ImportError: - try: - from importlib_metadata import entry_points - except ImportError: - try: - from pip._vendor.pkg_resources import iter_entry_points - except (ImportError, OSError): - return [] - else: - return iter_entry_points(group_name) groups = entry_points() if hasattr(groups, 'select'): # New interface in Python 3.10 and newer versions of the diff --git a/src/pip/_vendor/pygments/regexopt.py b/src/pip/_vendor/pygments/regexopt.py index 45223eccc10..c44eedbf2ad 100644 --- a/src/pip/_vendor/pygments/regexopt.py +++ b/src/pip/_vendor/pygments/regexopt.py @@ -5,7 +5,7 @@ An algorithm that generates optimized regexes for matching long lists of literal strings. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/src/pip/_vendor/pygments/scanner.py b/src/pip/_vendor/pygments/scanner.py index 32a2f303296..112da34917e 100644 --- a/src/pip/_vendor/pygments/scanner.py +++ b/src/pip/_vendor/pygments/scanner.py @@ -11,7 +11,7 @@ Have a look at the `DelphiLexer` to get an idea of how to use this scanner. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re diff --git a/src/pip/_vendor/pygments/sphinxext.py b/src/pip/_vendor/pygments/sphinxext.py index fc0b0270bfd..34077a2aee8 100644 --- a/src/pip/_vendor/pygments/sphinxext.py +++ b/src/pip/_vendor/pygments/sphinxext.py @@ -5,7 +5,7 @@ Sphinx extension to generate automatic documentation of lexers, formatters and filters. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -33,6 +33,8 @@ %s + %s + ''' FMTERDOC = ''' @@ -119,11 +121,11 @@ def format_link(name, url): def write_row(*columns): """Format a table row""" out = [] - for l, c in zip(column_lengths, columns): - if c: - out.append(c.ljust(l)) + for length, col in zip(column_lengths, columns): + if col: + out.append(col.ljust(length)) else: - out.append(' '*l) + out.append(' '*length) return ' '.join(out) @@ -160,7 +162,7 @@ def document_lexers(self): self.filenames.add(mod.__file__) cls = getattr(mod, classname) if not cls.__doc__: - print("Warning: %s does not have a docstring." % classname) + print(f"Warning: {classname} does not have a docstring.") docstring = cls.__doc__ if isinstance(docstring, bytes): docstring = docstring.decode('utf8') @@ -182,12 +184,18 @@ def document_lexers(self): for line in content.splitlines(): docstring += f' {line}\n' + if cls.version_added: + version_line = f'.. versionadded:: {cls.version_added}' + else: + version_line = '' + modules.setdefault(module, []).append(( classname, ', '.join(data[2]) or 'None', ', '.join(data[3]).replace('*', '\\*').replace('_', '\\') or 'None', ', '.join(data[4]) or 'None', - docstring)) + docstring, + version_line)) if module not in moduledocstrings: moddoc = mod.__doc__ if isinstance(moddoc, bytes): @@ -196,7 +204,7 @@ def document_lexers(self): for module, lexers in sorted(modules.items(), key=lambda x: x[0]): if moduledocstrings[module] is None: - raise Exception("Missing docstring for %s" % (module,)) + raise Exception(f"Missing docstring for {module}") heading = moduledocstrings[module].splitlines()[4].strip().rstrip('.') out.append(MODULEDOC % (module, heading, '-'*len(heading))) for data in lexers: diff --git a/src/pip/_vendor/pygments/style.py b/src/pip/_vendor/pygments/style.py index f2f72d3bc56..076e63f831c 100644 --- a/src/pip/_vendor/pygments/style.py +++ b/src/pip/_vendor/pygments/style.py @@ -4,7 +4,7 @@ Basic style object. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -76,7 +76,7 @@ def colorformat(text): return '' elif text.startswith('var') or text.startswith('calc'): return text - assert False, "wrong color format %r" % text + assert False, f"wrong color format {text!r}" _styles = obj._styles = {} diff --git a/src/pip/_vendor/pygments/styles/__init__.py b/src/pip/_vendor/pygments/styles/__init__.py index 23b55468e2c..712f6e69932 100644 --- a/src/pip/_vendor/pygments/styles/__init__.py +++ b/src/pip/_vendor/pygments/styles/__init__.py @@ -4,7 +4,7 @@ Contains built-in styles. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -44,13 +44,13 @@ def get_style_by_name(name): try: mod = __import__(mod, None, None, [cls]) except ImportError: - raise ClassNotFound("Could not find style module %r" % mod + + raise ClassNotFound(f"Could not find style module {mod!r}" + (builtin and ", though it should be builtin") + ".") try: return getattr(mod, cls) except AttributeError: - raise ClassNotFound("Could not find style class %r in style module." % cls) + raise ClassNotFound(f"Could not find style class {cls!r} in style module.") def get_all_styles(): diff --git a/src/pip/_vendor/pygments/styles/_mapping.py b/src/pip/_vendor/pygments/styles/_mapping.py index 04c7ddfbb04..49a7fae92dc 100644 --- a/src/pip/_vendor/pygments/styles/_mapping.py +++ b/src/pip/_vendor/pygments/styles/_mapping.py @@ -9,6 +9,7 @@ 'AutumnStyle': ('pygments.styles.autumn', 'autumn', ()), 'BlackWhiteStyle': ('pygments.styles.bw', 'bw', ()), 'BorlandStyle': ('pygments.styles.borland', 'borland', ()), + 'CoffeeStyle': ('pygments.styles.coffee', 'coffee', ()), 'ColorfulStyle': ('pygments.styles.colorful', 'colorful', ()), 'DefaultStyle': ('pygments.styles.default', 'default', ()), 'DraculaStyle': ('pygments.styles.dracula', 'dracula', ()), diff --git a/src/pip/_vendor/pygments/token.py b/src/pip/_vendor/pygments/token.py index bdf2e8e2e12..f78018a7aa7 100644 --- a/src/pip/_vendor/pygments/token.py +++ b/src/pip/_vendor/pygments/token.py @@ -4,7 +4,7 @@ Basic token types and the standard tokens. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/src/pip/_vendor/pygments/unistring.py b/src/pip/_vendor/pygments/unistring.py index 39f6baeedfb..e2c3523e4bb 100644 --- a/src/pip/_vendor/pygments/unistring.py +++ b/src/pip/_vendor/pygments/unistring.py @@ -7,7 +7,7 @@ Inspired by chartypes_create.py from the MoinMoin project. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -104,7 +104,7 @@ def _handle_runs(char_list): # pragma: no cover if a == b: yield a else: - yield '%s-%s' % (a, b) + yield f'{a}-{b}' if __name__ == '__main__': # pragma: no cover @@ -141,13 +141,13 @@ def _handle_runs(char_list): # pragma: no cover for cat in sorted(categories): val = ''.join(_handle_runs(categories[cat])) - fp.write('%s = %a\n\n' % (cat, val)) + fp.write(f'{cat} = {val!a}\n\n') cats = sorted(categories) cats.remove('xid_start') cats.remove('xid_continue') - fp.write('cats = %r\n\n' % cats) + fp.write(f'cats = {cats!r}\n\n') - fp.write('# Generated from unidata %s\n\n' % (unicodedata.unidata_version,)) + fp.write(f'# Generated from unidata {unicodedata.unidata_version}\n\n') fp.write(footer) diff --git a/src/pip/_vendor/pygments/util.py b/src/pip/_vendor/pygments/util.py index 941fdb9ec7a..83cf1049253 100644 --- a/src/pip/_vendor/pygments/util.py +++ b/src/pip/_vendor/pygments/util.py @@ -4,7 +4,7 @@ Utility functions. - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -46,8 +46,7 @@ def get_choice_opt(options, optname, allowed, default=None, normcase=False): if normcase: string = string.lower() if string not in allowed: - raise OptionError('Value for option %s must be one of %s' % - (optname, ', '.join(map(str, allowed)))) + raise OptionError('Value for option {} must be one of {}'.format(optname, ', '.join(map(str, allowed)))) return string @@ -69,17 +68,15 @@ def get_bool_opt(options, optname, default=None): elif isinstance(string, int): return bool(string) elif not isinstance(string, str): - raise OptionError('Invalid type %r for option %s; use ' - '1/0, yes/no, true/false, on/off' % ( - string, optname)) + raise OptionError(f'Invalid type {string!r} for option {optname}; use ' + '1/0, yes/no, true/false, on/off') elif string.lower() in ('1', 'yes', 'true', 'on'): return True elif string.lower() in ('0', 'no', 'false', 'off'): return False else: - raise OptionError('Invalid value %r for option %s; use ' - '1/0, yes/no, true/false, on/off' % ( - string, optname)) + raise OptionError(f'Invalid value {string!r} for option {optname}; use ' + '1/0, yes/no, true/false, on/off') def get_int_opt(options, optname, default=None): @@ -88,13 +85,11 @@ def get_int_opt(options, optname, default=None): try: return int(string) except TypeError: - raise OptionError('Invalid type %r for option %s; you ' - 'must give an integer value' % ( - string, optname)) + raise OptionError(f'Invalid type {string!r} for option {optname}; you ' + 'must give an integer value') except ValueError: - raise OptionError('Invalid value %r for option %s; you ' - 'must give an integer value' % ( - string, optname)) + raise OptionError(f'Invalid value {string!r} for option {optname}; you ' + 'must give an integer value') def get_list_opt(options, optname, default=None): """ @@ -108,9 +103,8 @@ def get_list_opt(options, optname, default=None): elif isinstance(val, (list, tuple)): return list(val) else: - raise OptionError('Invalid type %r for option %s; you ' - 'must give a list value' % ( - val, optname)) + raise OptionError(f'Invalid type {val!r} for option {optname}; you ' + 'must give a list value') def docstring_headline(obj): @@ -181,7 +175,7 @@ def shebang_matches(text, regex): if x and not x.startswith('-')][-1] except IndexError: return False - regex = re.compile(r'^%s(\.(exe|cmd|bat|bin))?$' % regex, re.IGNORECASE) + regex = re.compile(rf'^{regex}(\.(exe|cmd|bat|bin))?$', re.IGNORECASE) if regex.search(found) is not None: return True return False diff --git a/src/pip/_vendor/tenacity/__init__.py b/src/pip/_vendor/tenacity/__init__.py index c1b0310bdfb..a735a19f09d 100644 --- a/src/pip/_vendor/tenacity/__init__.py +++ b/src/pip/_vendor/tenacity/__init__.py @@ -15,8 +15,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - +import dataclasses import functools import sys import threading @@ -25,7 +24,8 @@ import warnings from abc import ABC, abstractmethod from concurrent import futures -from inspect import iscoroutinefunction + +from . import _utils # Import all built-in retry strategies for easier usage. from .retry import retry_base # noqa @@ -50,6 +50,7 @@ # Import all built-in stop strategies for easier usage. from .stop import stop_after_attempt # noqa from .stop import stop_after_delay # noqa +from .stop import stop_before_delay # noqa from .stop import stop_all # noqa from .stop import stop_any # noqa from .stop import stop_never # noqa @@ -89,6 +90,7 @@ if t.TYPE_CHECKING: import types + from . import asyncio as tasyncio from .retry import RetryBaseT from .stop import StopBaseT from .wait import WaitBaseT @@ -98,6 +100,29 @@ WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any]) +dataclass_kwargs = {} +if sys.version_info >= (3, 10): + dataclass_kwargs.update({"slots": True}) + + +@dataclasses.dataclass(**dataclass_kwargs) +class IterState: + actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field( + default_factory=list + ) + retry_run_result: bool = False + delay_since_first_attempt: int = 0 + stop_run_result: bool = False + is_explicit_retry: bool = False + + def reset(self) -> None: + self.actions = [] + self.retry_run_result = False + self.delay_since_first_attempt = 0 + self.stop_run_result = False + self.is_explicit_retry = False + + class TryAgain(Exception): """Always retry the executed function when raised.""" @@ -126,7 +151,9 @@ class BaseAction: NAME: t.Optional[str] = None def __repr__(self) -> str: - state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS) + state_str = ", ".join( + f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS + ) return f"{self.__class__.__name__}({state_str})" def __str__(self) -> str: @@ -222,10 +249,14 @@ def copy( retry: t.Union[retry_base, object] = _unset, before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, - before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset, + before_sleep: t.Union[ + t.Optional[t.Callable[["RetryCallState"], None]], object + ] = _unset, reraise: t.Union[bool, object] = _unset, retry_error_cls: t.Union[t.Type[RetryError], object] = _unset, - retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset, + retry_error_callback: t.Union[ + t.Optional[t.Callable[["RetryCallState"], t.Any]], object + ] = _unset, ) -> "BaseRetrying": """Copy this object with some parameters changed if needed.""" return self.__class__( @@ -238,7 +269,9 @@ def copy( before_sleep=_first_set(before_sleep, self.before_sleep), reraise=_first_set(reraise, self.reraise), retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls), - retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback), + retry_error_callback=_first_set( + retry_error_callback, self.retry_error_callback + ), ) def __repr__(self) -> str: @@ -280,21 +313,37 @@ def statistics(self) -> t.Dict[str, t.Any]: self._local.statistics = t.cast(t.Dict[str, t.Any], {}) return self._local.statistics + @property + def iter_state(self) -> IterState: + try: + return self._local.iter_state # type: ignore[no-any-return] + except AttributeError: + self._local.iter_state = IterState() + return self._local.iter_state + def wraps(self, f: WrappedFn) -> WrappedFn: """Wrap a function for retrying. :param f: A function to wraps for retrying. """ - @functools.wraps(f) + @functools.wraps( + f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__") + ) def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any: - return self(f, *args, **kw) + # Always create a copy to prevent overwriting the local contexts when + # calling the same wrapped functions multiple times in the same stack + copy = self.copy() + wrapped_f.statistics = copy.statistics # type: ignore[attr-defined] + return copy(f, *args, **kw) def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn: return self.copy(*args, **kwargs).wraps(f) + # Preserve attributes wrapped_f.retry = self # type: ignore[attr-defined] wrapped_f.retry_with = retry_with # type: ignore[attr-defined] + wrapped_f.statistics = {} # type: ignore[attr-defined] return wrapped_f # type: ignore[return-value] @@ -304,42 +353,89 @@ def begin(self) -> None: self.statistics["attempt_number"] = 1 self.statistics["idle_for"] = 0 + def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None: + self.iter_state.actions.append(fn) + + def _run_retry(self, retry_state: "RetryCallState") -> None: + self.iter_state.retry_run_result = self.retry(retry_state) + + def _run_wait(self, retry_state: "RetryCallState") -> None: + if self.wait: + sleep = self.wait(retry_state) + else: + sleep = 0.0 + + retry_state.upcoming_sleep = sleep + + def _run_stop(self, retry_state: "RetryCallState") -> None: + self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start + self.iter_state.stop_run_result = self.stop(retry_state) + def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa + self._begin_iter(retry_state) + result = None + for action in self.iter_state.actions: + result = action(retry_state) + return result + + def _begin_iter(self, retry_state: "RetryCallState") -> None: # noqa + self.iter_state.reset() + fut = retry_state.outcome if fut is None: if self.before is not None: - self.before(retry_state) - return DoAttempt() + self._add_action_func(self.before) + self._add_action_func(lambda rs: DoAttempt()) + return - is_explicit_retry = fut.failed and isinstance(fut.exception(), TryAgain) - if not (is_explicit_retry or self.retry(retry_state)): - return fut.result() + self.iter_state.is_explicit_retry = fut.failed and isinstance( + fut.exception(), TryAgain + ) + if not self.iter_state.is_explicit_retry: + self._add_action_func(self._run_retry) + self._add_action_func(self._post_retry_check_actions) + + def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None: + if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result): + self._add_action_func(lambda rs: rs.outcome.result()) + return if self.after is not None: - self.after(retry_state) + self._add_action_func(self.after) - self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start - if self.stop(retry_state): + self._add_action_func(self._run_wait) + self._add_action_func(self._run_stop) + self._add_action_func(self._post_stop_check_actions) + + def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None: + if self.iter_state.stop_run_result: if self.retry_error_callback: - return self.retry_error_callback(retry_state) - retry_exc = self.retry_error_cls(fut) - if self.reraise: - raise retry_exc.reraise() - raise retry_exc from fut.exception() + self._add_action_func(self.retry_error_callback) + return - if self.wait: - sleep = self.wait(retry_state) - else: - sleep = 0.0 - retry_state.next_action = RetryAction(sleep) - retry_state.idle_for += sleep - self.statistics["idle_for"] += sleep - self.statistics["attempt_number"] += 1 + def exc_check(rs: "RetryCallState") -> None: + fut = t.cast(Future, rs.outcome) + retry_exc = self.retry_error_cls(fut) + if self.reraise: + raise retry_exc.reraise() + raise retry_exc from fut.exception() + + self._add_action_func(exc_check) + return + + def next_action(rs: "RetryCallState") -> None: + sleep = rs.upcoming_sleep + rs.next_action = RetryAction(sleep) + rs.idle_for += sleep + self.statistics["idle_for"] += sleep + self.statistics["attempt_number"] += 1 + + self._add_action_func(next_action) if self.before_sleep is not None: - self.before_sleep(retry_state) + self._add_action_func(self.before_sleep) - return DoSleep(sleep) + self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep)) def __iter__(self) -> t.Generator[AttemptManager, None, None]: self.begin() @@ -393,7 +489,7 @@ def __call__( return do # type: ignore[no-any-return] -if sys.version_info[1] >= 9: +if sys.version_info >= (3, 9): FutureGenericT = futures.Future[t.Any] else: FutureGenericT = futures.Future @@ -412,7 +508,9 @@ def failed(self) -> bool: return self.exception() is not None @classmethod - def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future": + def construct( + cls, attempt_number: int, value: t.Any, has_exception: bool + ) -> "Future": """Construct a new Future object.""" fut = cls(attempt_number) if has_exception: @@ -453,6 +551,8 @@ def __init__( self.idle_for: float = 0.0 #: Next action as decided by the retry manager self.next_action: t.Optional[RetryAction] = None + #: Next sleep time as decided by the retry manager. + self.upcoming_sleep: float = 0.0 @property def seconds_since_start(self) -> t.Optional[float]: @@ -473,7 +573,10 @@ def set_result(self, val: t.Any) -> None: self.outcome, self.outcome_timestamp = fut, ts def set_exception( - self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType| None"] + self, + exc_info: t.Tuple[ + t.Type[BaseException], BaseException, "types.TracebackType| None" + ], ) -> None: ts = time.monotonic() fut = Future(self.attempt_number) @@ -495,24 +598,30 @@ def __repr__(self) -> str: @t.overload -def retry(func: WrappedFn) -> WrappedFn: - ... +def retry(func: WrappedFn) -> WrappedFn: ... @t.overload def retry( - sleep: t.Callable[[t.Union[int, float]], t.Optional[t.Awaitable[None]]] = sleep, + sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep, stop: "StopBaseT" = stop_never, wait: "WaitBaseT" = wait_none(), - retry: "RetryBaseT" = retry_if_exception_type(), - before: t.Callable[["RetryCallState"], None] = before_nothing, - after: t.Callable[["RetryCallState"], None] = after_nothing, - before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None, + retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(), + before: t.Callable[ + ["RetryCallState"], t.Union[None, t.Awaitable[None]] + ] = before_nothing, + after: t.Callable[ + ["RetryCallState"], t.Union[None, t.Awaitable[None]] + ] = after_nothing, + before_sleep: t.Optional[ + t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]] + ] = None, reraise: bool = False, retry_error_cls: t.Type["RetryError"] = RetryError, - retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None, -) -> t.Callable[[WrappedFn], WrappedFn]: - ... + retry_error_callback: t.Optional[ + t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]] + ] = None, +) -> t.Callable[[WrappedFn], WrappedFn]: ... def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any: @@ -533,9 +642,13 @@ def wrap(f: WrappedFn) -> WrappedFn: f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)" ) r: "BaseRetrying" - if iscoroutinefunction(f): + if _utils.is_coroutine_callable(f): r = AsyncRetrying(*dargs, **dkw) - elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f): + elif ( + tornado + and hasattr(tornado.gen, "is_coroutine_function") + and tornado.gen.is_coroutine_function(f) + ): r = TornadoRetrying(*dargs, **dkw) else: r = Retrying(*dargs, **dkw) @@ -545,7 +658,7 @@ def wrap(f: WrappedFn) -> WrappedFn: return wrap -from pip._vendor.tenacity._asyncio import AsyncRetrying # noqa:E402,I100 +from pip._vendor.tenacity.asyncio import AsyncRetrying # noqa:E402,I100 if tornado: from pip._vendor.tenacity.tornadoweb import TornadoRetrying @@ -570,6 +683,7 @@ def wrap(f: WrappedFn) -> WrappedFn: "sleep_using_event", "stop_after_attempt", "stop_after_delay", + "stop_before_delay", "stop_all", "stop_any", "stop_never", diff --git a/src/pip/_vendor/tenacity/_asyncio.py b/src/pip/_vendor/tenacity/_asyncio.py deleted file mode 100644 index 2e50cd7b40e..00000000000 --- a/src/pip/_vendor/tenacity/_asyncio.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2016 Étienne Bersac -# Copyright 2016 Julien Danjou -# Copyright 2016 Joshua Harlow -# Copyright 2013-2014 Ray Holder -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import functools -import sys -import typing as t -from asyncio import sleep - -from pip._vendor.tenacity import AttemptManager -from pip._vendor.tenacity import BaseRetrying -from pip._vendor.tenacity import DoAttempt -from pip._vendor.tenacity import DoSleep -from pip._vendor.tenacity import RetryCallState - -WrappedFnReturnT = t.TypeVar("WrappedFnReturnT") -WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]]) - - -class AsyncRetrying(BaseRetrying): - sleep: t.Callable[[float], t.Awaitable[t.Any]] - - def __init__(self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any) -> None: - super().__init__(**kwargs) - self.sleep = sleep - - async def __call__( # type: ignore[override] - self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any - ) -> WrappedFnReturnT: - self.begin() - - retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) - while True: - do = self.iter(retry_state=retry_state) - if isinstance(do, DoAttempt): - try: - result = await fn(*args, **kwargs) - except BaseException: # noqa: B902 - retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type] - else: - retry_state.set_result(result) - elif isinstance(do, DoSleep): - retry_state.prepare_for_next_attempt() - await self.sleep(do) - else: - return do # type: ignore[no-any-return] - - def __iter__(self) -> t.Generator[AttemptManager, None, None]: - raise TypeError("AsyncRetrying object is not iterable") - - def __aiter__(self) -> "AsyncRetrying": - self.begin() - self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) - return self - - async def __anext__(self) -> AttemptManager: - while True: - do = self.iter(retry_state=self._retry_state) - if do is None: - raise StopAsyncIteration - elif isinstance(do, DoAttempt): - return AttemptManager(retry_state=self._retry_state) - elif isinstance(do, DoSleep): - self._retry_state.prepare_for_next_attempt() - await self.sleep(do) - else: - raise StopAsyncIteration - - def wraps(self, fn: WrappedFn) -> WrappedFn: - fn = super().wraps(fn) - # Ensure wrapper is recognized as a coroutine function. - - @functools.wraps(fn) - async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any: - return await fn(*args, **kwargs) - - # Preserve attributes - async_wrapped.retry = fn.retry # type: ignore[attr-defined] - async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined] - - return async_wrapped # type: ignore[return-value] diff --git a/src/pip/_vendor/tenacity/_utils.py b/src/pip/_vendor/tenacity/_utils.py index f14ff32096e..f11a088814d 100644 --- a/src/pip/_vendor/tenacity/_utils.py +++ b/src/pip/_vendor/tenacity/_utils.py @@ -13,7 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import functools +import inspect import sys import typing from datetime import timedelta @@ -73,4 +74,28 @@ def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str: def to_seconds(time_unit: time_unit_type) -> float: - return float(time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit) + return float( + time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit + ) + + +def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool: + if inspect.isclass(call): + return False + if inspect.iscoroutinefunction(call): + return True + partial_call = isinstance(call, functools.partial) and call.func + dunder_call = partial_call or getattr(call, "__call__", None) + return inspect.iscoroutinefunction(dunder_call) + + +def wrap_to_async_func( + call: typing.Callable[..., typing.Any], +) -> typing.Callable[..., typing.Awaitable[typing.Any]]: + if is_coroutine_callable(call): + return call + + async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: + return call(*args, **kwargs) + + return inner diff --git a/src/pip/_vendor/tenacity/asyncio/__init__.py b/src/pip/_vendor/tenacity/asyncio/__init__.py new file mode 100644 index 00000000000..c7baf20eb1f --- /dev/null +++ b/src/pip/_vendor/tenacity/asyncio/__init__.py @@ -0,0 +1,206 @@ +# Copyright 2016 Étienne Bersac +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +import sys +import typing as t + +from pip._vendor import tenacity +from pip._vendor.tenacity import AttemptManager +from pip._vendor.tenacity import BaseRetrying +from pip._vendor.tenacity import DoAttempt +from pip._vendor.tenacity import DoSleep +from pip._vendor.tenacity import RetryCallState +from pip._vendor.tenacity import RetryError +from pip._vendor.tenacity import after_nothing +from pip._vendor.tenacity import before_nothing +from pip._vendor.tenacity import _utils + +# Import all built-in retry strategies for easier usage. +from .retry import RetryBaseT +from .retry import retry_all # noqa +from .retry import retry_any # noqa +from .retry import retry_if_exception # noqa +from .retry import retry_if_result # noqa +from ..retry import RetryBaseT as SyncRetryBaseT + +if t.TYPE_CHECKING: + from pip._vendor.tenacity.stop import StopBaseT + from pip._vendor.tenacity.wait import WaitBaseT + +WrappedFnReturnT = t.TypeVar("WrappedFnReturnT") +WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]]) + + +def _portable_async_sleep(seconds: float) -> t.Awaitable[None]: + # If trio is already imported, then importing it is cheap. + # If trio isn't already imported, then it's definitely not running, so we + # can skip further checks. + if "trio" in sys.modules: + # If trio is available, then sniffio is too + import trio + import sniffio + + if sniffio.current_async_library() == "trio": + return trio.sleep(seconds) + # Otherwise, assume asyncio + # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead). + import asyncio + + return asyncio.sleep(seconds) + + +class AsyncRetrying(BaseRetrying): + def __init__( + self, + sleep: t.Callable[ + [t.Union[int, float]], t.Union[None, t.Awaitable[None]] + ] = _portable_async_sleep, + stop: "StopBaseT" = tenacity.stop.stop_never, + wait: "WaitBaseT" = tenacity.wait.wait_none(), + retry: "t.Union[SyncRetryBaseT, RetryBaseT]" = tenacity.retry_if_exception_type(), + before: t.Callable[ + ["RetryCallState"], t.Union[None, t.Awaitable[None]] + ] = before_nothing, + after: t.Callable[ + ["RetryCallState"], t.Union[None, t.Awaitable[None]] + ] = after_nothing, + before_sleep: t.Optional[ + t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]] + ] = None, + reraise: bool = False, + retry_error_cls: t.Type["RetryError"] = RetryError, + retry_error_callback: t.Optional[ + t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]] + ] = None, + ) -> None: + super().__init__( + sleep=sleep, # type: ignore[arg-type] + stop=stop, + wait=wait, + retry=retry, # type: ignore[arg-type] + before=before, # type: ignore[arg-type] + after=after, # type: ignore[arg-type] + before_sleep=before_sleep, # type: ignore[arg-type] + reraise=reraise, + retry_error_cls=retry_error_cls, + retry_error_callback=retry_error_callback, + ) + + async def __call__( # type: ignore[override] + self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any + ) -> WrappedFnReturnT: + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = await self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = await fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type] + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + await self.sleep(do) # type: ignore[misc] + else: + return do # type: ignore[no-any-return] + + def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None: + self.iter_state.actions.append(_utils.wrap_to_async_func(fn)) + + async def _run_retry(self, retry_state: "RetryCallState") -> None: # type: ignore[override] + self.iter_state.retry_run_result = await _utils.wrap_to_async_func(self.retry)( + retry_state + ) + + async def _run_wait(self, retry_state: "RetryCallState") -> None: # type: ignore[override] + if self.wait: + sleep = await _utils.wrap_to_async_func(self.wait)(retry_state) + else: + sleep = 0.0 + + retry_state.upcoming_sleep = sleep + + async def _run_stop(self, retry_state: "RetryCallState") -> None: # type: ignore[override] + self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start + self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)( + retry_state + ) + + async def iter( + self, retry_state: "RetryCallState" + ) -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa: A003 + self._begin_iter(retry_state) + result = None + for action in self.iter_state.actions: + result = await action(retry_state) + return result + + def __iter__(self) -> t.Generator[AttemptManager, None, None]: + raise TypeError("AsyncRetrying object is not iterable") + + def __aiter__(self) -> "AsyncRetrying": + self.begin() + self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) + return self + + async def __anext__(self) -> AttemptManager: + while True: + do = await self.iter(retry_state=self._retry_state) + if do is None: + raise StopAsyncIteration + elif isinstance(do, DoAttempt): + return AttemptManager(retry_state=self._retry_state) + elif isinstance(do, DoSleep): + self._retry_state.prepare_for_next_attempt() + await self.sleep(do) # type: ignore[misc] + else: + raise StopAsyncIteration + + def wraps(self, fn: WrappedFn) -> WrappedFn: + wrapped = super().wraps(fn) + # Ensure wrapper is recognized as a coroutine function. + + @functools.wraps( + fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__") + ) + async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any: + # Always create a copy to prevent overwriting the local contexts when + # calling the same wrapped functions multiple times in the same stack + copy = self.copy() + async_wrapped.statistics = copy.statistics # type: ignore[attr-defined] + return await copy(fn, *args, **kwargs) + + # Preserve attributes + async_wrapped.retry = self # type: ignore[attr-defined] + async_wrapped.retry_with = wrapped.retry_with # type: ignore[attr-defined] + async_wrapped.statistics = {} # type: ignore[attr-defined] + + return async_wrapped # type: ignore[return-value] + + +__all__ = [ + "retry_all", + "retry_any", + "retry_if_exception", + "retry_if_result", + "WrappedFn", + "AsyncRetrying", +] diff --git a/src/pip/_vendor/tenacity/asyncio/retry.py b/src/pip/_vendor/tenacity/asyncio/retry.py new file mode 100644 index 00000000000..f7da72fb8e4 --- /dev/null +++ b/src/pip/_vendor/tenacity/asyncio/retry.py @@ -0,0 +1,125 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import abc +import typing + +from pip._vendor.tenacity import _utils +from pip._vendor.tenacity import retry_base + +if typing.TYPE_CHECKING: + from pip._vendor.tenacity import RetryCallState + + +class async_retry_base(retry_base): + """Abstract base class for async retry strategies.""" + + @abc.abstractmethod + async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override] + pass + + def __and__( # type: ignore[override] + self, other: "typing.Union[retry_base, async_retry_base]" + ) -> "retry_all": + return retry_all(self, other) + + def __rand__( # type: ignore[misc,override] + self, other: "typing.Union[retry_base, async_retry_base]" + ) -> "retry_all": + return retry_all(other, self) + + def __or__( # type: ignore[override] + self, other: "typing.Union[retry_base, async_retry_base]" + ) -> "retry_any": + return retry_any(self, other) + + def __ror__( # type: ignore[misc,override] + self, other: "typing.Union[retry_base, async_retry_base]" + ) -> "retry_any": + return retry_any(other, self) + + +RetryBaseT = typing.Union[ + async_retry_base, typing.Callable[["RetryCallState"], typing.Awaitable[bool]] +] + + +class retry_if_exception(async_retry_base): + """Retry strategy that retries if an exception verifies a predicate.""" + + def __init__( + self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]] + ) -> None: + self.predicate = predicate + + async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override] + if retry_state.outcome is None: + raise RuntimeError("__call__() called before outcome was set") + + if retry_state.outcome.failed: + exception = retry_state.outcome.exception() + if exception is None: + raise RuntimeError("outcome failed but the exception is None") + return await self.predicate(exception) + else: + return False + + +class retry_if_result(async_retry_base): + """Retries if the result verifies a predicate.""" + + def __init__( + self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]] + ) -> None: + self.predicate = predicate + + async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override] + if retry_state.outcome is None: + raise RuntimeError("__call__() called before outcome was set") + + if not retry_state.outcome.failed: + return await self.predicate(retry_state.outcome.result()) + else: + return False + + +class retry_any(async_retry_base): + """Retries if any of the retries condition is valid.""" + + def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None: + self.retries = retries + + async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override] + result = False + for r in self.retries: + result = result or await _utils.wrap_to_async_func(r)(retry_state) + if result: + break + return result + + +class retry_all(async_retry_base): + """Retries if all the retries condition are valid.""" + + def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None: + self.retries = retries + + async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override] + result = True + for r in self.retries: + result = result and await _utils.wrap_to_async_func(r)(retry_state) + if not result: + break + return result diff --git a/src/pip/_vendor/tenacity/before.py b/src/pip/_vendor/tenacity/before.py index cfd7dc72ee7..ad1cdf80ec1 100644 --- a/src/pip/_vendor/tenacity/before.py +++ b/src/pip/_vendor/tenacity/before.py @@ -28,7 +28,9 @@ def before_nothing(retry_state: "RetryCallState") -> None: """Before call strategy that does nothing.""" -def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["RetryCallState"], None]: +def before_log( + logger: "logging.Logger", log_level: int +) -> typing.Callable[["RetryCallState"], None]: """Before call strategy that logs to some logger the attempt.""" def log_it(retry_state: "RetryCallState") -> None: diff --git a/src/pip/_vendor/tenacity/before_sleep.py b/src/pip/_vendor/tenacity/before_sleep.py index 8c6167fb3a6..b52a4576a73 100644 --- a/src/pip/_vendor/tenacity/before_sleep.py +++ b/src/pip/_vendor/tenacity/before_sleep.py @@ -64,7 +64,8 @@ def log_it(retry_state: "RetryCallState") -> None: logger.log( log_level, - f"Retrying {fn_name} " f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.", + f"Retrying {fn_name} " + f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.", exc_info=local_exc_info, ) diff --git a/src/pip/_vendor/tenacity/retry.py b/src/pip/_vendor/tenacity/retry.py index 38988739d64..67600932934 100644 --- a/src/pip/_vendor/tenacity/retry.py +++ b/src/pip/_vendor/tenacity/retry.py @@ -30,10 +30,16 @@ def __call__(self, retry_state: "RetryCallState") -> bool: pass def __and__(self, other: "retry_base") -> "retry_all": - return retry_all(self, other) + return other.__rand__(self) + + def __rand__(self, other: "retry_base") -> "retry_all": + return retry_all(other, self) def __or__(self, other: "retry_base") -> "retry_any": - return retry_any(self, other) + return other.__ror__(self) + + def __ror__(self, other: "retry_base") -> "retry_any": + return retry_any(other, self) RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]] @@ -204,7 +210,9 @@ def __init__( match: typing.Optional[str] = None, ) -> None: if message and match: - raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both") + raise TypeError( + f"{self.__class__.__name__}() takes either 'message' or 'match', not both" + ) # set predicate if message: @@ -221,7 +229,9 @@ def match_fnc(exception: BaseException) -> bool: predicate = match_fnc else: - raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'") + raise TypeError( + f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'" + ) super().__init__(predicate) diff --git a/src/pip/_vendor/tenacity/stop.py b/src/pip/_vendor/tenacity/stop.py index bb23effdf86..9c39a04a21d 100644 --- a/src/pip/_vendor/tenacity/stop.py +++ b/src/pip/_vendor/tenacity/stop.py @@ -92,7 +92,14 @@ def __call__(self, retry_state: "RetryCallState") -> bool: class stop_after_delay(stop_base): - """Stop when the time from the first attempt >= limit.""" + """ + Stop when the time from the first attempt >= limit. + + Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater + than `max_delay` by some of the final sleep period before `max_delay` is exceeded. + + If you need stricter timing with waits, consider `stop_before_delay` instead. + """ def __init__(self, max_delay: _utils.time_unit_type) -> None: self.max_delay = _utils.to_seconds(max_delay) @@ -101,3 +108,23 @@ def __call__(self, retry_state: "RetryCallState") -> bool: if retry_state.seconds_since_start is None: raise RuntimeError("__call__() called but seconds_since_start is not set") return retry_state.seconds_since_start >= self.max_delay + + +class stop_before_delay(stop_base): + """ + Stop right before the next attempt would take place after the time from the first attempt >= limit. + + Most useful when you are using with a `wait` function like wait_random_exponential, but need to make + sure that the max_delay is not exceeded. + """ + + def __init__(self, max_delay: _utils.time_unit_type) -> None: + self.max_delay = _utils.to_seconds(max_delay) + + def __call__(self, retry_state: "RetryCallState") -> bool: + if retry_state.seconds_since_start is None: + raise RuntimeError("__call__() called but seconds_since_start is not set") + return ( + retry_state.seconds_since_start + retry_state.upcoming_sleep + >= self.max_delay + ) diff --git a/src/pip/_vendor/tenacity/tornadoweb.py b/src/pip/_vendor/tenacity/tornadoweb.py index e19c30b1890..15255b02925 100644 --- a/src/pip/_vendor/tenacity/tornadoweb.py +++ b/src/pip/_vendor/tenacity/tornadoweb.py @@ -29,7 +29,11 @@ class TornadoRetrying(BaseRetrying): - def __init__(self, sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, **kwargs: typing.Any) -> None: + def __init__( + self, + sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, + **kwargs: typing.Any, + ) -> None: super().__init__(**kwargs) self.sleep = sleep diff --git a/src/pip/_vendor/tenacity/wait.py b/src/pip/_vendor/tenacity/wait.py index f9349c02836..a31f8e2f735 100644 --- a/src/pip/_vendor/tenacity/wait.py +++ b/src/pip/_vendor/tenacity/wait.py @@ -41,7 +41,9 @@ def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_bas return self.__add__(other) -WaitBaseT = typing.Union[wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]] +WaitBaseT = typing.Union[ + wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]] +] class wait_fixed(wait_base): @@ -64,12 +66,16 @@ def __init__(self) -> None: class wait_random(wait_base): """Wait strategy that waits a random amount of time between min/max.""" - def __init__(self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1) -> None: # noqa + def __init__( + self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1 + ) -> None: # noqa self.wait_random_min = _utils.to_seconds(min) self.wait_random_max = _utils.to_seconds(max) def __call__(self, retry_state: "RetryCallState") -> float: - return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min)) + return self.wait_random_min + ( + random.random() * (self.wait_random_max - self.wait_random_min) + ) class wait_combine(wait_base): diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index 7e5b4e5df3c..74e5fa751a2 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -3,17 +3,17 @@ distlib==0.3.8 distro==1.9.0 msgpack==1.0.8 packaging==24.1 -platformdirs==4.2.1 +platformdirs==4.2.2 pyproject-hooks==1.0.0 requests==2.32.3 - certifi==2024.2.2 + certifi==2024.7.4 idna==3.7 urllib3==1.26.18 rich==13.7.1 - pygments==2.17.2 + pygments==2.18.0 typing_extensions==4.12.2 resolvelib==1.0.1 -setuptools==69.5.1 -tenacity==8.2.3 +setuptools==70.2.0 +tenacity==8.5.0 tomli==2.0.1 truststore==0.9.1 diff --git a/tools/vendoring/patches/pkg_resources.patch b/tools/vendoring/patches/pkg_resources.patch index a99b6c63df8..946c1eb9cea 100644 --- a/tools/vendoring/patches/pkg_resources.patch +++ b/tools/vendoring/patches/pkg_resources.patch @@ -1,11 +1,11 @@ diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py -index 3f2476a0c..8d5727d35 100644 +index d47df3f3c..1ee0d2cf9 100644 --- a/src/pip/_vendor/pkg_resources/__init__.py +++ b/src/pip/_vendor/pkg_resources/__init__.py -@@ -65,7 +65,7 @@ - from os import open as os_open - from os.path import isdir, split - +@@ -87,7 +87,7 @@ except ImportError: + # no write support, probably under GAE + WRITE_SUPPORT = False + -from pkg_resources.extern.jaraco.text import ( +from pip._internal.utils._jaraco_text import ( yield_lines,