Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -1 +1,2 @@
"""Contains purely network-related utilities."""
"""Contains purely network-related utilities.
"""

View file

@ -3,9 +3,6 @@
Contains interface (MultiDomainBasicAuth) and associated glue code for
providing credentials in the context of network requests.
"""
from __future__ import annotations
import logging
import os
import shutil
@ -14,10 +11,10 @@ import sysconfig
import typing
import urllib.parse
from abc import ABC, abstractmethod
from functools import cache
from functools import lru_cache
from os.path import commonprefix
from pathlib import Path
from typing import Any, NamedTuple
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
from pip._vendor.requests.models import Request, Response
@ -50,10 +47,12 @@ class KeyRingBaseProvider(ABC):
has_keyring: bool
@abstractmethod
def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None: ...
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
...
@abstractmethod
def save_auth_info(self, url: str, username: str, password: str) -> None: ...
def save_auth_info(self, url: str, username: str, password: str) -> None:
...
class KeyRingNullProvider(KeyRingBaseProvider):
@ -61,7 +60,7 @@ class KeyRingNullProvider(KeyRingBaseProvider):
has_keyring = False
def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
return None
def save_auth_info(self, url: str, username: str, password: str) -> None:
@ -78,7 +77,7 @@ class KeyRingPythonProvider(KeyRingBaseProvider):
self.keyring = keyring
def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
# Support keyring's get_credential interface which supports getting
# credentials without a username. This is only available for
# keyring>=15.2.0.
@ -114,7 +113,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
def __init__(self, cmd: str) -> None:
self.keyring = cmd
def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
# This is the default implementation of keyring.get_credential
# https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
if username is not None:
@ -126,7 +125,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
def save_auth_info(self, url: str, username: str, password: str) -> None:
return self._set_password(url, username, password)
def _get_password(self, service_name: str, username: str) -> str | None:
def _get_password(self, service_name: str, username: str) -> Optional[str]:
"""Mirror the implementation of keyring.get_password using cli"""
if self.keyring is None:
return None
@ -152,14 +151,14 @@ class KeyRingCliProvider(KeyRingBaseProvider):
env["PYTHONIOENCODING"] = "utf-8"
subprocess.run(
[self.keyring, "set", service_name, username],
input=f"{password}{os.linesep}".encode(),
input=f"{password}{os.linesep}".encode("utf-8"),
env=env,
check=True,
)
return None
@cache
@lru_cache(maxsize=None)
def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
logger.verbose("Keyring provider requested: %s", provider)
@ -225,19 +224,19 @@ class MultiDomainBasicAuth(AuthBase):
def __init__(
self,
prompting: bool = True,
index_urls: list[str] | None = None,
index_urls: Optional[List[str]] = None,
keyring_provider: str = "auto",
) -> None:
self.prompting = prompting
self.index_urls = index_urls
self.keyring_provider = keyring_provider
self.passwords: dict[str, AuthInfo] = {}
self.keyring_provider = keyring_provider # type: ignore[assignment]
self.passwords: Dict[str, AuthInfo] = {}
# When the user is prompted to enter credentials and keyring is
# available, we will offer to save them. If the user accepts,
# this value is set to the credentials they entered. After the
# request authenticates, the caller should call
# ``save_credentials`` to save these.
self._credentials_to_save: Credentials | None = None
self._credentials_to_save: Optional[Credentials] = None
@property
def keyring_provider(self) -> KeyRingBaseProvider:
@ -260,9 +259,9 @@ class MultiDomainBasicAuth(AuthBase):
def _get_keyring_auth(
self,
url: str | None,
username: str | None,
) -> AuthInfo | None:
url: Optional[str],
username: Optional[str],
) -> Optional[AuthInfo]:
"""Return the tuple auth for a given url from keyring."""
# Do nothing if no url was provided
if not url:
@ -271,10 +270,6 @@ class MultiDomainBasicAuth(AuthBase):
try:
return self.keyring_provider.get_auth_info(url, username)
except Exception as exc:
# Log the full exception (with stacktrace) at debug, so it'll only
# show up when running in verbose mode.
logger.debug("Keyring is skipped due to an exception", exc_info=True)
# Always log a shortened version of the exception.
logger.warning(
"Keyring is skipped due to an exception: %s",
str(exc),
@ -284,7 +279,7 @@ class MultiDomainBasicAuth(AuthBase):
get_keyring_provider.cache_clear()
return None
def _get_index_url(self, url: str) -> str | None:
def _get_index_url(self, url: str) -> Optional[str]:
"""Return the original index URL matching the requested URL.
Cached or dynamically generated credentials may work against
@ -391,7 +386,7 @@ class MultiDomainBasicAuth(AuthBase):
def _get_url_and_credentials(
self, original_url: str
) -> tuple[str, str | None, str | None]:
) -> Tuple[str, Optional[str], Optional[str]]:
"""Return the credentials to use for the provided URL.
If allowed, netrc and keyring may be used to obtain the
@ -454,7 +449,9 @@ class MultiDomainBasicAuth(AuthBase):
return req
# Factored out to allow for easy patching in tests
def _prompt_for_password(self, netloc: str) -> tuple[str | None, str | None, bool]:
def _prompt_for_password(
self, netloc: str
) -> Tuple[Optional[str], Optional[str], bool]:
username = ask_input(f"User for {netloc}: ") if self.prompting else None
if not username:
return None, None, False

View file

@ -1,23 +1,16 @@
"""HTTP cache implementation."""
from __future__ import annotations
"""HTTP cache implementation.
"""
import os
import shutil
from collections.abc import Generator
from contextlib import contextmanager
from datetime import datetime
from typing import Any, BinaryIO, Callable
from typing import BinaryIO, Generator, Optional, Union
from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
from pip._vendor.requests.models import Response
from pip._internal.utils.filesystem import (
adjacent_tmp_file,
copy_directory_permissions,
replace,
)
from pip._internal.utils.filesystem import adjacent_tmp_file, replace
from pip._internal.utils.misc import ensure_dir
@ -67,7 +60,7 @@ class SafeFileCache(SeparateBodyBaseCache):
parts = list(hashed[:5]) + [hashed]
return os.path.join(self.directory, *parts)
def get(self, key: str) -> bytes | None:
def get(self, key: str) -> Optional[bytes]:
# The cache entry is only valid if both metadata and body exist.
metadata_path = self._get_cache_path(key)
body_path = metadata_path + ".body"
@ -77,27 +70,17 @@ class SafeFileCache(SeparateBodyBaseCache):
with open(metadata_path, "rb") as f:
return f.read()
def _write_to_file(self, path: str, writer_func: Callable[[BinaryIO], Any]) -> None:
"""Common file writing logic with proper permissions and atomic replacement."""
def _write(self, path: str, data: bytes) -> None:
with suppressed_cache_errors():
ensure_dir(os.path.dirname(path))
with adjacent_tmp_file(path) as f:
writer_func(f)
# Inherit the read/write permissions of the cache directory
# to enable multi-user cache use-cases.
copy_directory_permissions(self.directory, f)
f.write(data)
replace(f.name, path)
def _write(self, path: str, data: bytes) -> None:
self._write_to_file(path, lambda f: f.write(data))
def _write_from_io(self, path: str, source_file: BinaryIO) -> None:
self._write_to_file(path, lambda f: shutil.copyfileobj(source_file, f))
def set(
self, key: str, value: bytes, expires: int | datetime | None = None
self, key: str, value: bytes, expires: Union[int, datetime, None] = None
) -> None:
path = self._get_cache_path(key)
self._write(path, value)
@ -109,7 +92,7 @@ class SafeFileCache(SeparateBodyBaseCache):
with suppressed_cache_errors():
os.remove(path + ".body")
def get_body(self, key: str) -> BinaryIO | None:
def get_body(self, key: str) -> Optional[BinaryIO]:
# The cache entry is only valid if both metadata and body exist.
metadata_path = self._get_cache_path(key)
body_path = metadata_path + ".body"
@ -121,8 +104,3 @@ class SafeFileCache(SeparateBodyBaseCache):
def set_body(self, key: str, body: bytes) -> None:
path = self._get_cache_path(key) + ".body"
self._write(path, body)
def set_body_from_io(self, key: str, body_file: BinaryIO) -> None:
"""Set the body of the cache entry from a file object."""
path = self._get_cache_path(key) + ".body"
self._write_from_io(path, body_file)

View file

@ -1,56 +1,39 @@
"""Download files with progress indicators."""
from __future__ import annotations
"""Download files with progress indicators.
"""
import email.message
import logging
import mimetypes
import os
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from http import HTTPStatus
from typing import BinaryIO
from typing import Iterable, Optional, Tuple
from pip._vendor.requests import PreparedRequest
from pip._vendor.requests.models import Response
from pip._vendor.urllib3 import HTTPResponse as URLlib3Response
from pip._vendor.urllib3._collections import HTTPHeaderDict
from pip._vendor.urllib3.exceptions import ReadTimeoutError
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
from pip._internal.cli.progress_bars import BarType, get_download_progress_renderer
from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError
from pip._internal.cli.progress_bars import get_download_progress_renderer
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.models.index import PyPI
from pip._internal.models.link import Link
from pip._internal.network.cache import SafeFileCache, is_from_cache
from pip._internal.network.session import CacheControlAdapter, PipSession
from pip._internal.network.cache import is_from_cache
from pip._internal.network.session import PipSession
from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext
logger = logging.getLogger(__name__)
def _get_http_response_size(resp: Response) -> int | None:
def _get_http_response_size(resp: Response) -> Optional[int]:
try:
return int(resp.headers["content-length"])
except (ValueError, KeyError, TypeError):
return None
def _get_http_response_etag_or_last_modified(resp: Response) -> str | None:
"""
Return either the ETag or Last-Modified header (or None if neither exists).
The return value can be used in an If-Range header.
"""
return resp.headers.get("etag", resp.headers.get("last-modified"))
def _log_download(
def _prepare_download(
resp: Response,
link: Link,
progress_bar: BarType,
total_length: int | None,
range_start: int | None = 0,
progress_bar: str,
) -> Iterable[bytes]:
total_length = _get_http_response_size(resp)
if link.netloc == PyPI.file_storage_domain:
url = link.show_url
else:
@ -59,17 +42,10 @@ def _log_download(
logged_url = redact_auth_from_url(url)
if total_length:
if range_start:
logged_url = (
f"{logged_url} ({format_size(range_start)}/{format_size(total_length)})"
)
else:
logged_url = f"{logged_url} ({format_size(total_length)})"
logged_url = f"{logged_url} ({format_size(total_length)})"
if is_from_cache(resp):
logger.info("Using cached %s", logged_url)
elif range_start:
logger.info("Resuming download %s", logged_url)
else:
logger.info("Downloading %s", logged_url)
@ -79,19 +55,17 @@ def _log_download(
show_progress = False
elif not total_length:
show_progress = True
elif total_length > (512 * 1024):
elif total_length > (40 * 1000):
show_progress = True
else:
show_progress = False
chunks = response_chunks(resp)
chunks = response_chunks(resp, CONTENT_CHUNK_SIZE)
if not show_progress:
return chunks
renderer = get_download_progress_renderer(
bar_type=progress_bar, size=total_length, initial_progress=range_start
)
renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
return renderer(chunks)
@ -126,7 +100,7 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
content_disposition = resp.headers.get("content-disposition")
if content_disposition:
filename = parse_content_disposition(content_disposition, filename)
ext: str | None = splitext(filename)[1]
ext: Optional[str] = splitext(filename)[1]
if not ext:
ext = mimetypes.guess_extension(resp.headers.get("content-type", ""))
if ext:
@ -138,205 +112,75 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
return filename
@dataclass
class _FileDownload:
"""Stores the state of a single link download."""
link: Link
output_file: BinaryIO
size: int | None
bytes_received: int = 0
reattempts: int = 0
def is_incomplete(self) -> bool:
return bool(self.size is not None and self.bytes_received < self.size)
def write_chunk(self, data: bytes) -> None:
self.bytes_received += len(data)
self.output_file.write(data)
def reset_file(self) -> None:
"""Delete any saved data and reset progress to zero."""
self.output_file.seek(0)
self.output_file.truncate()
self.bytes_received = 0
def _http_get_download(session: PipSession, link: Link) -> Response:
target_url = link.url.split("#", 1)[0]
resp = session.get(target_url, headers=HEADERS, stream=True)
raise_for_status(resp)
return resp
class Downloader:
def __init__(
self,
session: PipSession,
progress_bar: BarType,
resume_retries: int,
progress_bar: str,
) -> None:
assert (
resume_retries >= 0
), "Number of max resume retries must be bigger or equal to zero"
self._session = session
self._progress_bar = progress_bar
self._resume_retries = resume_retries
def batch(
self, links: Iterable[Link], location: str
) -> Iterable[tuple[Link, tuple[str, str]]]:
"""Convenience method to download multiple links."""
for link in links:
filepath, content_type = self(link, location)
yield link, (filepath, content_type)
def __call__(self, link: Link, location: str) -> tuple[str, str]:
"""Download a link and save it under location."""
resp = self._http_get(link)
download_size = _get_http_response_size(resp)
filepath = os.path.join(location, _get_http_response_filename(resp, link))
with open(filepath, "wb") as content_file:
download = _FileDownload(link, content_file, download_size)
self._process_response(download, resp)
if download.is_incomplete():
self._attempt_resumes_or_redownloads(download, resp)
content_type = resp.headers.get("Content-Type", "")
return filepath, content_type
def _process_response(self, download: _FileDownload, resp: Response) -> None:
"""Download and save chunks from a response."""
chunks = _log_download(
resp,
download.link,
self._progress_bar,
download.size,
range_start=download.bytes_received,
)
def __call__(self, link: Link, location: str) -> Tuple[str, str]:
"""Download the file given by link into location."""
try:
for chunk in chunks:
download.write_chunk(chunk)
except ReadTimeoutError as e:
# If the download size is not known, then give up downloading the file.
if download.size is None:
raise e
logger.warning("Connection timed out while downloading.")
def _attempt_resumes_or_redownloads(
self, download: _FileDownload, first_resp: Response
) -> None:
"""Attempt to resume/restart the download if connection was dropped."""
while download.reattempts < self._resume_retries and download.is_incomplete():
assert download.size is not None
download.reattempts += 1
logger.warning(
"Attempting to resume incomplete download (%s/%s, attempt %d)",
format_size(download.bytes_received),
format_size(download.size),
download.reattempts,
)
try:
resume_resp = self._http_get_resume(download, should_match=first_resp)
# Fallback: if the server responded with 200 (i.e., the file has
# since been modified or range requests are unsupported) or any
# other unexpected status, restart the download from the beginning.
must_restart = resume_resp.status_code != HTTPStatus.PARTIAL_CONTENT
if must_restart:
download.reset_file()
download.size = _get_http_response_size(resume_resp)
first_resp = resume_resp
self._process_response(download, resume_resp)
except (ConnectionError, ReadTimeoutError, OSError):
continue
# No more resume attempts. Raise an error if the download is still incomplete.
if download.is_incomplete():
os.remove(download.output_file.name)
raise IncompleteDownloadError(download)
# If we successfully completed the download via resume, manually cache it
# as a complete response to enable future caching
if download.reattempts > 0:
self._cache_resumed_download(download, first_resp)
def _cache_resumed_download(
self, download: _FileDownload, original_response: Response
) -> None:
"""
Manually cache a file that was successfully downloaded via resume retries.
cachecontrol doesn't cache 206 (Partial Content) responses, since they
are not complete files. This method manually adds the final file to the
cache as though it was downloaded in a single request, so that future
requests can use the cache.
"""
url = download.link.url_without_fragment
adapter = self._session.get_adapter(url)
# Check if the adapter is the CacheControlAdapter (i.e. caching is enabled)
if not isinstance(adapter, CacheControlAdapter):
logger.debug(
"Skipping resume download caching: no cache controller for %s", url
)
return
# Check SafeFileCache is being used
assert isinstance(
adapter.cache, SafeFileCache
), "separate body cache not in use!"
synthetic_request = PreparedRequest()
synthetic_request.prepare(method="GET", url=url, headers={})
synthetic_response_headers = HTTPHeaderDict()
for key, value in original_response.headers.items():
if key.lower() not in ["content-range", "content-length"]:
synthetic_response_headers[key] = value
synthetic_response_headers["content-length"] = str(download.size)
synthetic_response = URLlib3Response(
body="",
headers=synthetic_response_headers,
status=200,
preload_content=False,
)
# Save metadata and then stream the file contents to cache.
cache_url = adapter.controller.cache_url(url)
metadata_blob = adapter.controller.serializer.dumps(
synthetic_request, synthetic_response, b""
)
adapter.cache.set(cache_url, metadata_blob)
download.output_file.flush()
with open(download.output_file.name, "rb") as f:
adapter.cache.set_body_from_io(cache_url, f)
logger.debug(
"Cached resumed download as complete response for future use: %s", url
)
def _http_get_resume(
self, download: _FileDownload, should_match: Response
) -> Response:
"""Issue a HTTP range request to resume the download."""
# To better understand the download resumption logic, see the mdn web docs:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests
headers = HEADERS.copy()
headers["Range"] = f"bytes={download.bytes_received}-"
# If possible, use a conditional range request to avoid corrupted
# downloads caused by the remote file changing in-between.
if identifier := _get_http_response_etag_or_last_modified(should_match):
headers["If-Range"] = identifier
return self._http_get(download.link, headers)
def _http_get(self, link: Link, headers: Mapping[str, str] = HEADERS) -> Response:
target_url = link.url_without_fragment
try:
resp = self._session.get(target_url, headers=headers, stream=True)
raise_for_status(resp)
resp = _http_get_download(self._session, link)
except NetworkConnectionError as e:
assert e.response is not None
logger.critical(
"HTTP error %s while getting %s", e.response.status_code, link
)
raise
return resp
filename = _get_http_response_filename(resp, link)
filepath = os.path.join(location, filename)
chunks = _prepare_download(resp, link, self._progress_bar)
with open(filepath, "wb") as content_file:
for chunk in chunks:
content_file.write(chunk)
content_type = resp.headers.get("Content-Type", "")
return filepath, content_type
class BatchDownloader:
def __init__(
self,
session: PipSession,
progress_bar: str,
) -> None:
self._session = session
self._progress_bar = progress_bar
def __call__(
self, links: Iterable[Link], location: str
) -> Iterable[Tuple[Link, Tuple[str, str]]]:
"""Download the files given by links into location."""
for link in links:
try:
resp = _http_get_download(self._session, link)
except NetworkConnectionError as e:
assert e.response is not None
logger.critical(
"HTTP error %s while getting %s",
e.response.status_code,
link,
)
raise
filename = _get_http_response_filename(resp, link)
filepath = os.path.join(location, filename)
chunks = _prepare_download(resp, link, self._progress_bar)
with open(filepath, "wb") as content_file:
for chunk in chunks:
content_file.write(chunk)
content_type = resp.headers.get("Content-Type", "")
yield link, (filepath, content_type)

View file

@ -1,17 +1,14 @@
"""Lazy ZIP over HTTP"""
from __future__ import annotations
__all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
from bisect import bisect_left, bisect_right
from collections.abc import Generator
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from typing import Any
from typing import Any, Dict, Generator, List, Optional, Tuple
from zipfile import BadZipFile, ZipFile
from pip._vendor.packaging.utils import NormalizedName
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
@ -23,9 +20,7 @@ class HTTPRangeRequestUnsupported(Exception):
pass
def dist_from_wheel_url(
name: NormalizedName, url: str, session: PipSession
) -> BaseDistribution:
def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
"""Return a distribution object from the given wheel URL.
This uses HTTP range requests to only fetch the portion of the wheel
@ -39,7 +34,7 @@ def dist_from_wheel_url(
wheel = MemoryWheel(zf.name, zf) # type: ignore
# After context manager exit, wheel.name
# is an invalid file by intention.
return get_wheel_distribution(wheel, name)
return get_wheel_distribution(wheel, canonicalize_name(name))
class LazyZipOverHTTP:
@ -61,8 +56,8 @@ class LazyZipOverHTTP:
self._length = int(head.headers["Content-Length"])
self._file = NamedTemporaryFile()
self.truncate(self._length)
self._left: list[int] = []
self._right: list[int] = []
self._left: List[int] = []
self._right: List[int] = []
if "bytes" not in head.headers.get("Accept-Ranges", "none"):
raise HTTPRangeRequestUnsupported("range request is not supported")
self._check_zip()
@ -122,7 +117,7 @@ class LazyZipOverHTTP:
"""Return the current position."""
return self._file.tell()
def truncate(self, size: int | None = None) -> int:
def truncate(self, size: Optional[int] = None) -> int:
"""Resize the stream to the given size in bytes.
If size is unspecified resize to the current position.
@ -136,7 +131,7 @@ class LazyZipOverHTTP:
"""Return False."""
return False
def __enter__(self) -> LazyZipOverHTTP:
def __enter__(self) -> "LazyZipOverHTTP":
self._file.__enter__()
return self
@ -164,14 +159,14 @@ class LazyZipOverHTTP:
try:
# For read-only ZIP files, ZipFile only needs
# methods read, seek, seekable and tell.
ZipFile(self)
ZipFile(self) # type: ignore
except BadZipFile:
pass
else:
break
def _stream_response(
self, start: int, end: int, base_headers: dict[str, str] = HEADERS
self, start: int, end: int, base_headers: Dict[str, str] = HEADERS
) -> Response:
"""Return HTTP response to a range request from start to end."""
headers = base_headers.copy()
@ -182,7 +177,7 @@ class LazyZipOverHTTP:
def _merge(
self, start: int, end: int, left: int, right: int
) -> Generator[tuple[int, int], None, None]:
) -> Generator[Tuple[int, int], None, None]:
"""Return a generator of intervals to be fetched.
Args:

View file

@ -2,10 +2,7 @@
network request configuration and behavior.
"""
from __future__ import annotations
import email.utils
import functools
import io
import ipaddress
import json
@ -18,11 +15,16 @@ import subprocess
import sys
import urllib.parse
import warnings
from collections.abc import Generator, Mapping, Sequence
from typing import (
TYPE_CHECKING,
Any,
Dict,
Generator,
List,
Mapping,
Optional,
Sequence,
Tuple,
Union,
)
@ -51,19 +53,18 @@ if TYPE_CHECKING:
from ssl import SSLContext
from pip._vendor.urllib3.poolmanager import PoolManager
from pip._vendor.urllib3.proxymanager import ProxyManager
logger = logging.getLogger(__name__)
SecureOrigin = tuple[str, str, Optional[Union[int, str]]]
SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
# Ignore warning raised when using --trusted-host.
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
SECURE_ORIGINS: list[SecureOrigin] = [
SECURE_ORIGINS: List[SecureOrigin] = [
# protocol, hostname, port
# Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
("https", "*", "*"),
@ -105,12 +106,11 @@ def looks_like_ci() -> bool:
return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)
@functools.lru_cache(maxsize=1)
def user_agent() -> str:
"""
Return a string representing the user agent.
"""
data: dict[str, Any] = {
data: Dict[str, Any] = {
"installer": {"name": "pip", "version": __version__},
"python": platform.python_version(),
"implementation": {
@ -138,7 +138,7 @@ def user_agent() -> str:
from pip._vendor import distro
linux_distribution = distro.name(), distro.version(), distro.codename()
distro_infos: dict[str, Any] = dict(
distro_infos: Dict[str, Any] = dict(
filter(
lambda x: x[1],
zip(["name", "version", "id"], linux_distribution),
@ -212,10 +212,10 @@ class LocalFSAdapter(BaseAdapter):
self,
request: PreparedRequest,
stream: bool = False,
timeout: float | tuple[float, float] | None = None,
verify: bool | str = True,
cert: str | tuple[str, str] | None = None,
proxies: Mapping[str, str] | None = None,
timeout: Optional[Union[float, Tuple[float, float]]] = None,
verify: Union[bool, str] = True,
cert: Optional[Union[str, Tuple[str, str]]] = None,
proxies: Optional[Mapping[str, str]] = None,
) -> Response:
pathname = url_to_path(request.url)
@ -230,7 +230,7 @@ class LocalFSAdapter(BaseAdapter):
# to return a better error message:
resp.status_code = 404
resp.reason = type(exc).__name__
resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode())
resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode("utf8"))
else:
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
@ -262,7 +262,7 @@ class _SSLContextAdapterMixin:
def __init__(
self,
*,
ssl_context: SSLContext | None = None,
ssl_context: Optional["SSLContext"] = None,
**kwargs: Any,
) -> None:
self._ssl_context = ssl_context
@ -274,7 +274,7 @@ class _SSLContextAdapterMixin:
maxsize: int,
block: bool = DEFAULT_POOLBLOCK,
**pool_kwargs: Any,
) -> PoolManager:
) -> "PoolManager":
if self._ssl_context is not None:
pool_kwargs.setdefault("ssl_context", self._ssl_context)
return super().init_poolmanager( # type: ignore[misc]
@ -284,13 +284,6 @@ class _SSLContextAdapterMixin:
**pool_kwargs,
)
def proxy_manager_for(self, proxy: str, **proxy_kwargs: Any) -> ProxyManager:
# Proxy manager replaces the pool manager, so inject our SSL
# context here too. https://github.com/pypa/pip/issues/13288
if self._ssl_context is not None:
proxy_kwargs.setdefault("ssl_context", self._ssl_context)
return super().proxy_manager_for(proxy, **proxy_kwargs) # type: ignore[misc]
class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
pass
@ -305,8 +298,8 @@ class InsecureHTTPAdapter(HTTPAdapter):
self,
conn: ConnectionPool,
url: str,
verify: bool | str,
cert: str | tuple[str, str] | None,
verify: Union[bool, str],
cert: Optional[Union[str, Tuple[str, str]]],
) -> None:
super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
@ -316,23 +309,23 @@ class InsecureCacheControlAdapter(CacheControlAdapter):
self,
conn: ConnectionPool,
url: str,
verify: bool | str,
cert: str | tuple[str, str] | None,
verify: Union[bool, str],
cert: Optional[Union[str, Tuple[str, str]]],
) -> None:
super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
class PipSession(requests.Session):
timeout: int | None = None
timeout: Optional[int] = None
def __init__(
self,
*args: Any,
retries: int = 0,
cache: str | None = None,
cache: Optional[str] = None,
trusted_hosts: Sequence[str] = (),
index_urls: list[str] | None = None,
ssl_context: SSLContext | None = None,
index_urls: Optional[List[str]] = None,
ssl_context: Optional["SSLContext"] = None,
**kwargs: Any,
) -> None:
"""
@ -343,8 +336,7 @@ class PipSession(requests.Session):
# Namespace the attribute with "pip_" just in case to prevent
# possible conflicts with the base class.
self.pip_trusted_origins: list[tuple[str, int | None]] = []
self.pip_proxy = None
self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
# Attach our User Agent to the request
self.headers["User-Agent"] = user_agent()
@ -406,7 +398,7 @@ class PipSession(requests.Session):
for host in trusted_hosts:
self.add_trusted_host(host, suppress_logging=True)
def update_index_urls(self, new_index_urls: list[str]) -> None:
def update_index_urls(self, new_index_urls: List[str]) -> None:
"""
:param new_index_urls: New index urls to update the authentication
handler with.
@ -414,7 +406,7 @@ class PipSession(requests.Session):
self.auth.index_urls = new_index_urls
def add_trusted_host(
self, host: str, source: str | None = None, suppress_logging: bool = False
self, host: str, source: Optional[str] = None, suppress_logging: bool = False
) -> None:
"""
:param host: It is okay to provide a host that has previously been

View file

@ -1,6 +1,6 @@
from collections.abc import Generator
from typing import Dict, Generator
from pip._vendor.requests.models import Response
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
from pip._internal.exceptions import NetworkConnectionError
@ -23,9 +23,7 @@ from pip._internal.exceptions import NetworkConnectionError
# you're not asking for a compressed file and will then decompress it
# before sending because if that's the case I don't think it'll ever be
# possible to make this work.
HEADERS: dict[str, str] = {"Accept-Encoding": "identity"}
DOWNLOAD_CHUNK_SIZE = 256 * 1024
HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"}
def raise_for_status(resp: Response) -> None:
@ -57,7 +55,7 @@ def raise_for_status(resp: Response) -> None:
def response_chunks(
response: Response, chunk_size: int = DOWNLOAD_CHUNK_SIZE
response: Response, chunk_size: int = CONTENT_CHUNK_SIZE
) -> Generator[bytes, None, None]:
"""Given a requests Response, provide the data chunks."""
try:

View file

@ -1,9 +1,10 @@
"""xmlrpclib.Transport implementation"""
"""xmlrpclib.Transport implementation
"""
import logging
import urllib.parse
import xmlrpc.client
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Tuple
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.network.session import PipSession
@ -36,7 +37,7 @@ class PipXmlrpcTransport(xmlrpc.client.Transport):
handler: str,
request_body: "SizedBuffer",
verbose: bool = False,
) -> tuple["_Marshallable", ...]:
) -> Tuple["_Marshallable", ...]:
assert isinstance(host, str)
parts = (self._scheme, host, handler, None, None, None)
url = urllib.parse.urlunparse(parts)