Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/requests.vendor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Upgrade requests to 2.26.0.
34 changes: 23 additions & 11 deletions src/pip/_vendor/requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@
"""

from pip._vendor import urllib3
from pip._vendor import chardet
import warnings
from .exceptions import RequestsDependencyWarning

charset_normalizer_version = None

def check_compatibility(urllib3_version, chardet_version):
try:
from pip._vendor.chardet import __version__ as chardet_version
except ImportError:
chardet_version = None

def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
urllib3_version = urllib3_version.split('.')
assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.

Expand All @@ -62,12 +67,19 @@ def check_compatibility(urllib3_version, chardet_version):
assert minor >= 21
assert minor <= 26

# Check chardet for compatibility.
major, minor, patch = chardet_version.split('.')[:3]
major, minor, patch = int(major), int(minor), int(patch)
# chardet >= 3.0.2, < 5.0.0
assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0)

# Check charset_normalizer for compatibility.
if chardet_version:
major, minor, patch = chardet_version.split('.')[:3]
major, minor, patch = int(major), int(minor), int(patch)
# chardet_version >= 3.0.2, < 5.0.0
assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0)
elif charset_normalizer_version:
major, minor, patch = charset_normalizer_version.split('.')[:3]
major, minor, patch = int(major), int(minor), int(patch)
# charset_normalizer >= 2.0.0 < 3.0.0
assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0)
else:
raise Exception("You need either charset_normalizer or chardet installed")

def _check_cryptography(cryptography_version):
# cryptography < 1.3.4
Expand All @@ -82,10 +94,10 @@ def _check_cryptography(cryptography_version):

# Check imported dependencies for compatibility.
try:
check_compatibility(urllib3.__version__, chardet.__version__)
check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version)
except (AssertionError, ValueError):
warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "
"version!".format(urllib3.__version__, chardet.__version__),
warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
"version!".format(urllib3.__version__, chardet_version, charset_normalizer_version),
RequestsDependencyWarning)

# Attempt to enable urllib3's fallback for SNI support
Expand Down
4 changes: 2 additions & 2 deletions src/pip/_vendor/requests/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'https://requests.readthedocs.io'
__version__ = '2.25.1'
__build__ = 0x022501
__version__ = '2.26.0'
__build__ = 0x022600
__author__ = 'Kenneth Reitz'
__author_email__ = '[email protected]'
__license__ = 'Apache 2.0'
Expand Down
2 changes: 0 additions & 2 deletions src/pip/_vendor/requests/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def get(url, params=None, **kwargs):
:rtype: requests.Response
"""

kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)


Expand All @@ -85,7 +84,6 @@ def options(url, **kwargs):
:rtype: requests.Response
"""

kwargs.setdefault('allow_redirects', True)
return request('options', url, **kwargs)


Expand Down
5 changes: 4 additions & 1 deletion src/pip/_vendor/requests/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
Python 3.
"""

from pip._vendor import chardet
try:
from pip._vendor import chardet
except ImportError:
import charset_normalizer as chardet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we patch this to remove the try-except block? I feel it's more predictable if we always import the vendored chardet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just patched it.

I thought about an alternative: substituting all charset_normalizer imports with pip._vendor.charset_normalizer using vendoring capabilities. This way we won't have to maintain the additional patches, and switching to charset_normalizer will be smoother.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a good idea. Would it be easier if I merge this first or can you do that in this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, let’s have this in main so we can more easily evaluate this alternative solution.


import sys

Expand Down
4 changes: 4 additions & 0 deletions src/pip/_vendor/requests/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def __init__(self, *args, **kwargs):
super(RequestException, self).__init__(*args, **kwargs)


class InvalidJSONError(RequestException):
"""A JSON error occurred."""


class HTTPError(RequestException):
"""An HTTP error occurred."""

Expand Down
17 changes: 15 additions & 2 deletions src/pip/_vendor/requests/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@

from pip._vendor import idna
from pip._vendor import urllib3
from pip._vendor import chardet

from . import __version__ as requests_version

charset_normalizer = None

try:
from pip._vendor import chardet
except ImportError:
chardet = None

try:
from pip._vendor.urllib3.contrib import pyopenssl
except ImportError:
Expand Down Expand Up @@ -71,7 +77,12 @@ def info():

implementation_info = _implementation()
urllib3_info = {'version': urllib3.__version__}
chardet_info = {'version': chardet.__version__}
charset_normalizer_info = {'version': None}
chardet_info = {'version': None}
if charset_normalizer:
charset_normalizer_info = {'version': charset_normalizer.__version__}
if chardet:
chardet_info = {'version': chardet.__version__}

pyopenssl_info = {
'version': None,
Expand Down Expand Up @@ -99,9 +110,11 @@ def info():
'implementation': implementation_info,
'system_ssl': system_ssl_info,
'using_pyopenssl': pyopenssl is not None,
'using_charset_normalizer': chardet is None,
'pyOpenSSL': pyopenssl_info,
'urllib3': urllib3_info,
'chardet': chardet_info,
'charset_normalizer': charset_normalizer_info,
'cryptography': cryptography_info,
'idna': idna_info,
'requests': {
Expand Down
22 changes: 16 additions & 6 deletions src/pip/_vendor/requests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
from .exceptions import (
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
ContentDecodingError, ConnectionError, StreamConsumedError)
ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError)
from ._internal_utils import to_native_string, unicode_is_ascii
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
Expand Down Expand Up @@ -466,7 +466,12 @@ def prepare_body(self, data, files, json=None):
# urllib3 requires a bytes-like body. Python 2's json.dumps
# provides this natively, but Python 3 gives a Unicode string.
content_type = 'application/json'
body = complexjson.dumps(json)

try:
body = complexjson.dumps(json, allow_nan=False)
except ValueError as ve:
raise InvalidJSONError(ve, request=self)

if not isinstance(body, bytes):
body = body.encode('utf-8')

Expand Down Expand Up @@ -726,7 +731,7 @@ def next(self):

@property
def apparent_encoding(self):
"""The apparent encoding, provided by the chardet library."""
"""The apparent encoding, provided by the charset_normalizer or chardet libraries."""
return chardet.detect(self.content)['encoding']

def iter_content(self, chunk_size=1, decode_unicode=False):
Expand Down Expand Up @@ -840,7 +845,7 @@ def text(self):
"""Content of the response, in unicode.

If Response.encoding is None, encoding will be guessed using
``chardet``.
``charset_normalizer`` or ``chardet``.

The encoding of the response content is determined based solely on HTTP
headers, following RFC 2616 to the letter. If you can take advantage of
Expand Down Expand Up @@ -877,13 +882,18 @@ def json(self, **kwargs):
r"""Returns the json-encoded content of a response, if any.

:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises ValueError: If the response body does not contain valid json.
:raises simplejson.JSONDecodeError: If the response body does not
contain valid json and simplejson is installed.
:raises json.JSONDecodeError: If the response body does not contain
valid json and simplejson is not installed on Python 3.
:raises ValueError: If the response body does not contain valid
json and simplejson is not installed on Python 2.
"""

if not self.encoding and self.content and len(self.content) > 3:
# No encoding set. JSON RFC 4627 section 3 states we should expect
# UTF-8, -16 or -32. Detect which one to use; If the detection or
# decoding fails, fall back to `self.text` (using chardet to make
# decoding fails, fall back to `self.text` (using charset_normalizer to make
# a best guess).
encoding = guess_json_utf(self.content)
if encoding is not None:
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/requests/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ def send(self, request, **kwargs):
kwargs.setdefault('stream', self.stream)
kwargs.setdefault('verify', self.verify)
kwargs.setdefault('cert', self.cert)
kwargs.setdefault('proxies', self.proxies)
kwargs.setdefault('proxies', self.rebuild_proxies(request, self.proxies))

# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
Expand Down
29 changes: 25 additions & 4 deletions src/pip/_vendor/requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import warnings
import zipfile
from collections import OrderedDict
from pip._vendor.urllib3.util import make_headers

from .__version__ import __version__
from . import certs
Expand All @@ -41,6 +42,11 @@

DEFAULT_PORTS = {'http': 80, 'https': 443}

# Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING = ", ".join(
re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"])
)


if sys.platform == 'win32':
# provide a proxy_bypass version on Windows without DNS lookups
Expand Down Expand Up @@ -256,13 +262,28 @@ def extract_zipped_paths(path):

# we have a valid zip archive and a valid member of that archive
tmp = tempfile.gettempdir()
extracted_path = os.path.join(tmp, *member.split('/'))
extracted_path = os.path.join(tmp, member.split('/')[-1])
if not os.path.exists(extracted_path):
extracted_path = zip_file.extract(member, path=tmp)

# use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition
with atomic_open(extracted_path) as file_handler:
file_handler.write(zip_file.read(member))
return extracted_path


@contextlib.contextmanager
def atomic_open(filename):
"""Write a file to the disk in an atomic fashion"""
replacer = os.rename if sys.version_info[0] == 2 else os.replace
tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
try:
with os.fdopen(tmp_descriptor, 'wb') as tmp_handler:
yield tmp_handler
replacer(tmp_name, filename)
except BaseException:
os.remove(tmp_name)
raise


def from_key_val_list(value):
"""Take an object and test to see if it can be represented as a
dictionary. Unless it can not be represented as such, return an
Expand Down Expand Up @@ -820,7 +841,7 @@ def default_headers():
"""
return CaseInsensitiveDict({
'User-Agent': default_user_agent(),
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
'Accept-Encoding': DEFAULT_ACCEPT_ENCODING,
'Accept': '*/*',
'Connection': 'keep-alive',
})
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/vendor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ packaging==21.0
pep517==0.10.0
progress==1.5
pyparsing==2.4.7
requests==2.25.1
requests==2.26.0
certifi==2020.12.05
chardet==4.0.0
idna==3.1
Expand Down
59 changes: 52 additions & 7 deletions tools/vendoring/patches/requests.patch
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
diff --git a/src/pip/_vendor/requests/packages.py b/src/pip/_vendor/requests/packages.py
index 6336a07d..9582fa73 100644
index 0f8ae0d38..9582fa730 100644
--- a/src/pip/_vendor/requests/packages.py
+++ b/src/pip/_vendor/requests/packages.py
@@ -4,11 +4,13 @@ import sys
@@ -1,26 +1,16 @@
import sys

-try:
- from pip._vendor import chardet
-except ImportError:
- import charset_normalizer as chardet
- import warnings
-
- warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer')
-
# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)

for package in ('urllib3', 'idna', 'chardet'):
-for package in ('urllib3', 'idna'):
- locals()[package] = __import__(package)
+for package in ('urllib3', 'idna', 'chardet'):
+ vendored_package = "pip._vendor." + package
+ locals()[package] = __import__(vendored_package)
# This traversal is apparently necessary such that the identities are
Expand All @@ -18,13 +30,29 @@ index 6336a07d..9582fa73 100644
+ unprefixed_mod = mod[len("pip._vendor."):]
+ sys.modules['pip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod]

-target = chardet.__name__
-for mod in list(sys.modules):
- if mod == target or mod.startswith(target + '.'):
- sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod]
# Kinda cool, though, right?

diff --git a/src/pip/_vendor/requests/__init__.py b/src/pip/_vendor/requests/__init__.py
index dc83261a8..517458b5a 100644
index 973497f5e..4f80e28fc 100644
--- a/src/pip/_vendor/requests/__init__.py
+++ b/src/pip/_vendor/requests/__init__.py
@@ -94,6 +94,11 @@ except (AssertionError, ValueError):
@@ -44,10 +44,7 @@ from pip._vendor import urllib3
import warnings
from .exceptions import RequestsDependencyWarning

-try:
- from charset_normalizer import __version__ as charset_normalizer_version
-except ImportError:
- charset_normalizer_version = None
+charset_normalizer_version = None

try:
from pip._vendor.chardet import __version__ as chardet_version
@@ -107,6 +104,11 @@ except (AssertionError, ValueError):
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
Expand All @@ -38,10 +66,10 @@ index dc83261a8..517458b5a 100644
except ImportError:

diff --git a/src/pip/_vendor/requests/compat.py b/src/pip/_vendor/requests/compat.py
index eb6530d..353ec29 100644
index 0b14f5015..2804417c3 100644
--- a/src/pip/_vendor/requests/compat.py
+++ b/src/pip/_vendor/requests/compat.py
@@ -25,10 +25,14 @@
@@ -28,10 +28,14 @@ is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)

Expand All @@ -60,3 +88,20 @@ index eb6530d..353ec29 100644

# ---------
# Specifics

diff --git a/src/pip/_vendor/requests/help.py b/src/pip/_vendor/requests/help.py
index 3a843404c..745f0d7b3 100644
--- a/src/pip/_vendor/requests/help.py
+++ b/src/pip/_vendor/requests/help.py
@@ -11,10 +11,7 @@ from pip._vendor import urllib3

from . import __version__ as requests_version

-try:
- import charset_normalizer
-except ImportError:
- charset_normalizer = None
+charset_normalizer = None

try:
from pip._vendor import chardet