This commit is contained in:
Ambulance Clerc
2023-06-01 08:59:37 +02:00
parent 1fe8228d1b
commit 796746d175
346 changed files with 18799 additions and 44645 deletions

View File

@@ -3,9 +3,18 @@
Contains interface (MultiDomainBasicAuth) and associated glue code for
providing credentials in the context of network requests.
"""
import logging
import os
import shutil
import subprocess
import sysconfig
import typing
import urllib.parse
from typing import Any, Dict, List, Optional, Tuple
from abc import ABC, abstractmethod
from functools import lru_cache
from os.path import commonprefix
from pathlib import Path
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
@@ -23,59 +32,204 @@ from pip._internal.vcs.versioncontrol import AuthInfo
logger = getLogger(__name__)
Credentials = Tuple[str, str, str]
try:
import keyring
except ImportError:
keyring = None
except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s",
str(exc),
)
keyring = None
KEYRING_DISABLED = False
def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
"""Return the tuple auth for a given url from keyring."""
global keyring
if not url or not keyring:
class Credentials(NamedTuple):
url: str
username: str
password: str
class KeyRingBaseProvider(ABC):
"""Keyring base provider interface"""
has_keyring: bool
@abstractmethod
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:
...
class KeyRingNullProvider(KeyRingBaseProvider):
"""Keyring null provider"""
has_keyring = False
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
return None
try:
try:
get_credential = keyring.get_credential
except AttributeError:
pass
else:
def save_auth_info(self, url: str, username: str, password: str) -> None:
return None
class KeyRingPythonProvider(KeyRingBaseProvider):
"""Keyring interface which uses locally imported `keyring`"""
has_keyring = True
def __init__(self) -> None:
import keyring
self.keyring = keyring
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.
if hasattr(self.keyring, "get_credential"):
logger.debug("Getting credentials from keyring for %s", url)
cred = get_credential(url, username)
cred = self.keyring.get_credential(url, username)
if cred is not None:
return cred.username, cred.password
return None
if username:
if username is not None:
logger.debug("Getting password from keyring for %s", url)
password = keyring.get_password(url, username)
password = self.keyring.get_password(url, username)
if password:
return username, password
return None
except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s",
str(exc),
def save_auth_info(self, url: str, username: str, password: str) -> None:
self.keyring.set_password(url, username, password)
class KeyRingCliProvider(KeyRingBaseProvider):
"""Provider which uses `keyring` cli
Instead of calling the keyring package installed alongside pip
we call keyring on the command line which will enable pip to
use which ever installation of keyring is available first in
PATH.
"""
has_keyring = True
def __init__(self, cmd: str) -> None:
self.keyring = cmd
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:
password = self._get_password(url, username)
if password is not None:
return username, password
return None
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) -> Optional[str]:
"""Mirror the implementation of keyring.get_password using cli"""
if self.keyring is None:
return None
cmd = [self.keyring, "get", service_name, username]
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
res = subprocess.run(
cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
env=env,
)
keyring = None
return None
if res.returncode:
return None
return res.stdout.decode("utf-8").strip(os.linesep)
def _set_password(self, service_name: str, username: str, password: str) -> None:
"""Mirror the implementation of keyring.set_password using cli"""
if self.keyring is None:
return None
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
subprocess.run(
[self.keyring, "set", service_name, username],
input=f"{password}{os.linesep}".encode("utf-8"),
env=env,
check=True,
)
return None
@lru_cache(maxsize=None)
def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
logger.verbose("Keyring provider requested: %s", provider)
# keyring has previously failed and been disabled
if KEYRING_DISABLED:
provider = "disabled"
if provider in ["import", "auto"]:
try:
impl = KeyRingPythonProvider()
logger.verbose("Keyring provider set: import")
return impl
except ImportError:
pass
except Exception as exc:
# In the event of an unexpected exception
# we should warn the user
msg = "Installed copy of keyring fails with exception %s"
if provider == "auto":
msg = msg + ", trying to find a keyring executable as a fallback"
logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
if provider in ["subprocess", "auto"]:
cli = shutil.which("keyring")
if cli and cli.startswith(sysconfig.get_path("scripts")):
# all code within this function is stolen from shutil.which implementation
@typing.no_type_check
def PATH_as_shutil_which_determines_it() -> str:
path = os.environ.get("PATH", None)
if path is None:
try:
path = os.confstr("CS_PATH")
except (AttributeError, ValueError):
# os.confstr() or CS_PATH is not available
path = os.defpath
# bpo-35755: Don't use os.defpath if the PATH environment variable is
# set to an empty string
return path
scripts = Path(sysconfig.get_path("scripts"))
paths = []
for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
p = Path(path)
try:
if not p.samefile(scripts):
paths.append(path)
except FileNotFoundError:
pass
path = os.pathsep.join(paths)
cli = shutil.which("keyring", path=path)
if cli:
logger.verbose("Keyring provider set: subprocess with executable %s", cli)
return KeyRingCliProvider(cli)
logger.verbose("Keyring provider set: disabled")
return KeyRingNullProvider()
class MultiDomainBasicAuth(AuthBase):
def __init__(
self, prompting: bool = True, index_urls: Optional[List[str]] = None
self,
prompting: bool = True,
index_urls: Optional[List[str]] = None,
keyring_provider: str = "auto",
) -> None:
self.prompting = prompting
self.index_urls = index_urls
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,
@@ -84,6 +238,47 @@ class MultiDomainBasicAuth(AuthBase):
# ``save_credentials`` to save these.
self._credentials_to_save: Optional[Credentials] = None
@property
def keyring_provider(self) -> KeyRingBaseProvider:
return get_keyring_provider(self._keyring_provider)
@keyring_provider.setter
def keyring_provider(self, provider: str) -> None:
# The free function get_keyring_provider has been decorated with
# functools.cache. If an exception occurs in get_keyring_auth that
# cache will be cleared and keyring disabled, take that into account
# if you want to remove this indirection.
self._keyring_provider = provider
@property
def use_keyring(self) -> bool:
# We won't use keyring when --no-input is passed unless
# a specific provider is requested because it might require
# user interaction
return self.prompting or self._keyring_provider not in ["auto", "disabled"]
def _get_keyring_auth(
self,
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:
return None
try:
return self.keyring_provider.get_auth_info(url, username)
except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s",
str(exc),
)
global KEYRING_DISABLED
KEYRING_DISABLED = True
get_keyring_provider.cache_clear()
return None
def _get_index_url(self, url: str) -> Optional[str]:
"""Return the original index URL matching the requested URL.
@@ -100,15 +295,42 @@ class MultiDomainBasicAuth(AuthBase):
if not url or not self.index_urls:
return None
for u in self.index_urls:
prefix = remove_auth_from_url(u).rstrip("/") + "/"
if url.startswith(prefix):
return u
return None
url = remove_auth_from_url(url).rstrip("/") + "/"
parsed_url = urllib.parse.urlsplit(url)
candidates = []
for index in self.index_urls:
index = index.rstrip("/") + "/"
parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
if parsed_url == parsed_index:
return index
if parsed_url.netloc != parsed_index.netloc:
continue
candidate = urllib.parse.urlsplit(index)
candidates.append(candidate)
if not candidates:
return None
candidates.sort(
reverse=True,
key=lambda candidate: commonprefix(
[
parsed_url.path,
candidate.path,
]
).rfind("/"),
)
return urllib.parse.urlunsplit(candidates[0])
def _get_new_credentials(
self,
original_url: str,
*,
allow_netrc: bool = True,
allow_keyring: bool = False,
) -> AuthInfo:
@@ -152,8 +374,8 @@ class MultiDomainBasicAuth(AuthBase):
# The index url is more specific than the netloc, so try it first
# fmt: off
kr_auth = (
get_keyring_auth(index_url, username) or
get_keyring_auth(netloc, username)
self._get_keyring_auth(index_url, username) or
self._get_keyring_auth(netloc, username)
)
# fmt: on
if kr_auth:
@@ -179,9 +401,16 @@ class MultiDomainBasicAuth(AuthBase):
# Try to get credentials from original url
username, password = self._get_new_credentials(original_url)
# If credentials not found, use any stored credentials for this netloc
if username is None and password is None:
username, password = self.passwords.get(netloc, (None, None))
# If credentials not found, use any stored credentials for this netloc.
# Do this if either the username or the password is missing.
# This accounts for the situation in which the user has specified
# the username in the index url, but the password comes from keyring.
if (username is None or password is None) and netloc in self.passwords:
un, pw = self.passwords[netloc]
# It is possible that the cached credentials are for a different username,
# in which case the cache should be ignored.
if username is None or username == un:
username, password = un, pw
if username is not None or password is not None:
# Convert the username and password if they're None, so that
@@ -223,18 +452,23 @@ class MultiDomainBasicAuth(AuthBase):
def _prompt_for_password(
self, netloc: str
) -> Tuple[Optional[str], Optional[str], bool]:
username = ask_input(f"User for {netloc}: ")
username = ask_input(f"User for {netloc}: ") if self.prompting else None
if not username:
return None, None, False
auth = get_keyring_auth(netloc, username)
if auth and auth[0] is not None and auth[1] is not None:
return auth[0], auth[1], False
if self.use_keyring:
auth = self._get_keyring_auth(netloc, username)
if auth and auth[0] is not None and auth[1] is not None:
return auth[0], auth[1], False
password = ask_password("Password: ")
return username, password, True
# Factored out to allow for easy patching in tests
def _should_save_password_to_keyring(self) -> bool:
if not keyring:
if (
not self.prompting
or not self.use_keyring
or not self.keyring_provider.has_keyring
):
return False
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
@@ -244,19 +478,22 @@ class MultiDomainBasicAuth(AuthBase):
if resp.status_code != 401:
return resp
username, password = None, None
# Query the keyring for credentials:
if self.use_keyring:
username, password = self._get_new_credentials(
resp.url,
allow_netrc=False,
allow_keyring=True,
)
# We are not able to prompt the user so simply return the response
if not self.prompting:
if not self.prompting and not username and not password:
return resp
parsed = urllib.parse.urlparse(resp.url)
# Query the keyring for credentials:
username, password = self._get_new_credentials(
resp.url,
allow_netrc=False,
allow_keyring=True,
)
# Prompt the user for a new username and password
save = False
if not username and not password:
@@ -269,7 +506,11 @@ class MultiDomainBasicAuth(AuthBase):
# Prompt to save the password to keyring
if save and self._should_save_password_to_keyring():
self._credentials_to_save = (parsed.netloc, username, password)
self._credentials_to_save = Credentials(
url=parsed.netloc,
username=username,
password=password,
)
# Consume content and release the original connection to allow our new
# request to reuse the same one.
@@ -302,15 +543,17 @@ class MultiDomainBasicAuth(AuthBase):
def save_credentials(self, resp: Response, **kwargs: Any) -> None:
"""Response callback to save credentials on success."""
assert keyring is not None, "should never reach here without keyring"
if not keyring:
return
assert (
self.keyring_provider.has_keyring
), "should never reach here without keyring"
creds = self._credentials_to_save
self._credentials_to_save = None
if creds and resp.status_code < 400:
try:
logger.info("Saving credentials to keyring")
keyring.set_password(*creds)
self.keyring_provider.save_auth_info(
creds.url, creds.username, creds.password
)
except Exception:
logger.exception("Failed to save credentials")

View File

@@ -3,7 +3,7 @@
import os
from contextlib import contextmanager
from typing import Iterator, Optional
from typing import Generator, Optional
from pip._vendor.cachecontrol.cache import BaseCache
from pip._vendor.cachecontrol.caches import FileCache
@@ -18,7 +18,7 @@ def is_from_cache(response: Response) -> bool:
@contextmanager
def suppressed_cache_errors() -> Iterator[None]:
def suppressed_cache_errors() -> Generator[None, None, None]:
"""If we can't access the cache then we can just skip caching and process
requests as if caching wasn't enabled.
"""
@@ -53,7 +53,7 @@ class SafeFileCache(BaseCache):
with open(path, "rb") as f:
return f.read()
def set(self, key: str, value: bytes) -> None:
def set(self, key: str, value: bytes, expires: Optional[int] = None) -> None:
path = self._get_cache_path(key)
with suppressed_cache_errors():
ensure_dir(os.path.dirname(path))

View File

@@ -1,6 +1,6 @@
"""Download files with progress indicators.
"""
import cgi
import email.message
import logging
import mimetypes
import os
@@ -8,7 +8,7 @@ from typing import Iterable, Optional, Tuple
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
from pip._internal.cli.progress_bars import DownloadProgressProvider
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
@@ -65,7 +65,8 @@ def _prepare_download(
if not show_progress:
return chunks
return DownloadProgressProvider(progress_bar, max=total_length)(chunks)
renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
return renderer(chunks)
def sanitize_content_filename(filename: str) -> str:
@@ -80,12 +81,13 @@ def parse_content_disposition(content_disposition: str, default_filename: str) -
Parse the "filename" value from a Content-Disposition header, and
return the default filename if the result is empty.
"""
_type, params = cgi.parse_header(content_disposition)
filename = params.get("filename")
m = email.message.Message()
m["content-type"] = content_disposition
filename = m.get_param("filename")
if filename:
# We need to sanitize the filename to prevent directory traversal
# in case the filename contains ".." path parts.
filename = sanitize_content_filename(filename)
filename = sanitize_content_filename(str(filename))
return filename or default_filename

View File

@@ -5,36 +5,36 @@ __all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
from bisect import bisect_left, bisect_right
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from typing import Any, Dict, Iterator, List, Optional, Tuple
from zipfile import BadZipfile, ZipFile
from typing import Any, Dict, Generator, List, Optional, Tuple
from zipfile import BadZipFile, ZipFile
from pip._vendor.pkg_resources import Distribution
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
from pip._internal.network.session import PipSession
from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
class HTTPRangeRequestUnsupported(Exception):
pass
def dist_from_wheel_url(name: str, url: str, session: PipSession) -> Distribution:
"""Return a pkg_resources.Distribution from the given wheel URL.
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 potion of the wheel
This uses HTTP range requests to only fetch the portion of the wheel
containing metadata, just enough for the object to be constructed.
If such requests are not supported, HTTPRangeRequestUnsupported
is raised.
"""
with LazyZipOverHTTP(url, session) as wheel:
with LazyZipOverHTTP(url, session) as zf:
# For read-only ZIP files, ZipFile only needs methods read,
# seek, seekable and tell, not the whole IO protocol.
zip_file = ZipFile(wheel) # type: ignore
wheel = MemoryWheel(zf.name, zf) # type: ignore
# After context manager exit, wheel.name
# is an invalid file by intention.
return pkg_resources_distribution_for_wheel(zip_file, name, wheel.name)
return get_wheel_distribution(wheel, canonicalize_name(name))
class LazyZipOverHTTP:
@@ -135,11 +135,11 @@ class LazyZipOverHTTP:
self._file.__enter__()
return self
def __exit__(self, *exc: Any) -> Optional[bool]:
return self._file.__exit__(*exc)
def __exit__(self, *exc: Any) -> None:
self._file.__exit__(*exc)
@contextmanager
def _stay(self) -> Iterator[None]:
def _stay(self) -> Generator[None, None, None]:
"""Return a context manager keeping the position.
At the end of the block, seek back to original position.
@@ -160,7 +160,7 @@ class LazyZipOverHTTP:
# For read-only ZIP files, ZipFile only needs
# methods read, seek, seekable and tell.
ZipFile(self) # type: ignore
except BadZipfile:
except BadZipFile:
pass
else:
break
@@ -177,8 +177,8 @@ class LazyZipOverHTTP:
def _merge(
self, start: int, end: int, left: int, right: int
) -> Iterator[Tuple[int, int]]:
"""Return an iterator of intervals to be fetched.
) -> Generator[Tuple[int, int], None, None]:
"""Return a generator of intervals to be fetched.
Args:
start (int): Start of needed interval

View File

@@ -2,17 +2,8 @@
network request configuration and behavior.
"""
# When mypy runs on Windows the call to distro.linux_distribution() is skipped
# resulting in the failure:
#
# error: unused 'type: ignore' comment
#
# If the upstream module adds typing, this comment should be removed. See
# https://github.com/nir0s/distro/pull/269
#
# mypy: warn-unused-ignores=False
import email.utils
import io
import ipaddress
import json
import logging
@@ -24,11 +15,23 @@ import subprocess
import sys
import urllib.parse
import warnings
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union
from typing import (
TYPE_CHECKING,
Any,
Dict,
Generator,
List,
Mapping,
Optional,
Sequence,
Tuple,
Union,
)
from pip._vendor import requests, urllib3
from pip._vendor.cachecontrol import CacheControlAdapter
from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
from pip._vendor.cachecontrol import CacheControlAdapter as _BaseCacheControlAdapter
from pip._vendor.requests.adapters import DEFAULT_POOLBLOCK, BaseAdapter
from pip._vendor.requests.adapters import HTTPAdapter as _BaseHTTPAdapter
from pip._vendor.requests.models import PreparedRequest, Response
from pip._vendor.requests.structures import CaseInsensitiveDict
from pip._vendor.urllib3.connectionpool import ConnectionPool
@@ -46,6 +49,12 @@ from pip._internal.utils.glibc import libc_ver
from pip._internal.utils.misc import build_url_from_netloc, parse_netloc
from pip._internal.utils.urls import url_to_path
if TYPE_CHECKING:
from ssl import SSLContext
from pip._vendor.urllib3.poolmanager import PoolManager
logger = logging.getLogger(__name__)
SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
@@ -128,9 +137,8 @@ def user_agent() -> str:
if sys.platform.startswith("linux"):
from pip._vendor import distro
# https://github.com/nir0s/distro/pull/269
linux_distribution = distro.linux_distribution() # type: ignore
distro_infos = dict(
linux_distribution = distro.name(), distro.version(), distro.codename()
distro_infos: Dict[str, Any] = dict(
filter(
lambda x: x[1],
zip(["name", "version", "id"], linux_distribution),
@@ -218,8 +226,11 @@ class LocalFSAdapter(BaseAdapter):
try:
stats = os.stat(pathname)
except OSError as exc:
# format the exception raised as a io.BytesIO object,
# to return a better error message:
resp.status_code = 404
resp.raw = exc
resp.reason = type(exc).__name__
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"
@@ -240,6 +251,48 @@ class LocalFSAdapter(BaseAdapter):
pass
class _SSLContextAdapterMixin:
"""Mixin to add the ``ssl_context`` constructor argument to HTTP adapters.
The additional argument is forwarded directly to the pool manager. This allows us
to dynamically decide what SSL store to use at runtime, which is used to implement
the optional ``truststore`` backend.
"""
def __init__(
self,
*,
ssl_context: Optional["SSLContext"] = None,
**kwargs: Any,
) -> None:
self._ssl_context = ssl_context
super().__init__(**kwargs)
def init_poolmanager(
self,
connections: int,
maxsize: int,
block: bool = DEFAULT_POOLBLOCK,
**pool_kwargs: Any,
) -> "PoolManager":
if self._ssl_context is not None:
pool_kwargs.setdefault("ssl_context", self._ssl_context)
return super().init_poolmanager( # type: ignore[misc]
connections=connections,
maxsize=maxsize,
block=block,
**pool_kwargs,
)
class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
pass
class CacheControlAdapter(_SSLContextAdapterMixin, _BaseCacheControlAdapter):
pass
class InsecureHTTPAdapter(HTTPAdapter):
def cert_verify(
self,
@@ -263,7 +316,6 @@ class InsecureCacheControlAdapter(CacheControlAdapter):
class PipSession(requests.Session):
timeout: Optional[int] = None
def __init__(
@@ -273,6 +325,7 @@ class PipSession(requests.Session):
cache: Optional[str] = None,
trusted_hosts: Sequence[str] = (),
index_urls: Optional[List[str]] = None,
ssl_context: Optional["SSLContext"] = None,
**kwargs: Any,
) -> None:
"""
@@ -325,13 +378,14 @@ class PipSession(requests.Session):
secure_adapter = CacheControlAdapter(
cache=SafeFileCache(cache),
max_retries=retries,
ssl_context=ssl_context,
)
self._trusted_host_adapter = InsecureCacheControlAdapter(
cache=SafeFileCache(cache),
max_retries=retries,
)
else:
secure_adapter = HTTPAdapter(max_retries=retries)
secure_adapter = HTTPAdapter(max_retries=retries, ssl_context=ssl_context)
self._trusted_host_adapter = insecure_adapter
self.mount("https://", secure_adapter)
@@ -369,12 +423,19 @@ class PipSession(requests.Session):
if host_port not in self.pip_trusted_origins:
self.pip_trusted_origins.append(host_port)
self.mount(
build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter
)
self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter)
if not host_port[1]:
self.mount(
build_url_from_netloc(host, scheme="http") + ":",
self._trusted_host_adapter,
)
# Mount wildcard ports for the same host.
self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
def iter_secure_origins(self) -> Iterator[SecureOrigin]:
def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]:
yield from SECURE_ORIGINS
for host, port in self.pip_trusted_origins:
yield ("*", host, "*" if port is None else port)
@@ -403,7 +464,7 @@ class PipSession(requests.Session):
continue
try:
addr = ipaddress.ip_address(origin_host)
addr = ipaddress.ip_address(origin_host or "")
network = ipaddress.ip_network(secure_host)
except ValueError:
# We don't have both a valid address or a valid network, so
@@ -449,6 +510,8 @@ class PipSession(requests.Session):
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
# Allow setting a default timeout on a session
kwargs.setdefault("timeout", self.timeout)
# Allow setting a default proxies on a session
kwargs.setdefault("proxies", self.proxies)
# Dispatch the actual request
return super().request(method, url, *args, **kwargs)

View File

@@ -1,4 +1,4 @@
from typing import Dict, Iterator
from typing import Dict, Generator
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
@@ -56,7 +56,7 @@ def raise_for_status(resp: Response) -> None:
def response_chunks(
response: Response, chunk_size: int = CONTENT_CHUNK_SIZE
) -> Iterator[bytes]:
) -> Generator[bytes, None, None]:
"""Given a requests Response, provide the data chunks."""
try:
# Special case for urllib3.