Skip to content

Commit ce4790b

Browse files
SNOW-2176524 bump up vendored urllib3 to 2.5.0 and requests to v2.32.5 (#2504)
Co-authored-by: Patryk Czajka <[email protected]>
1 parent 6d66f96 commit ce4790b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+6979
-6566
lines changed

DESCRIPTION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1212
- Fixed `get_results_from_sfqid` when using `DictCursor` and executing multiple statements at once
1313
- Added the `oauth_credentials_in_body` parameter supporting an option to send the oauth client credentials in the request body
1414
- Added support for intermediate certificates as roots when they are stored in the trust store
15+
- Bumped up vendored `urllib3` to `2.5.0` and `requests` to `v2.32.5`
1516

1617
- v3.17.3(September 02,2025)
1718
- Enhanced configuration file permission warning messages.

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ include LICENSE.txt
44
include NOTICE
55
include pyproject.toml
66
include src/snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt
7-
recursive-include src/snowflake/connector py.typed *.py *.pyx
7+
recursive-include src/snowflake/connector py.typed *.py *.pyx *.js
88
recursive-include src/snowflake/connector/vendored LICENSE*
99

1010
recursive-include src/snowflake/connector/nanoarrow_cpp *.cpp *.hpp

src/snowflake/connector/session_manager.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .vendored import requests
1515
from .vendored.requests import Response, Session
1616
from .vendored.requests.adapters import BaseAdapter, HTTPAdapter
17-
from .vendored.requests.exceptions import InvalidProxyURL
17+
from .vendored.requests.exceptions import InvalidProxyURL, InvalidURL
1818
from .vendored.requests.utils import prepend_scheme_if_needed, select_proxy
1919
from .vendored.urllib3 import PoolManager, Retry
2020
from .vendored.urllib3.poolmanager import ProxyManager
@@ -23,7 +23,6 @@
2323
if TYPE_CHECKING:
2424
from .vendored.urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
2525

26-
2726
logger = logging.getLogger(__name__)
2827
REQUESTS_RETRY = 1 # requests library builtin retry
2928

@@ -60,19 +59,25 @@ def wrapper(self, *args, **kwargs):
6059
class ProxySupportAdapter(HTTPAdapter):
6160
"""This Adapter creates proper headers for Proxy CONNECT messages."""
6261

63-
def get_connection(
64-
self, url: str, proxies: dict | None = None
62+
def get_connection_with_tls_context(
63+
self, request, verify, proxies=None, cert=None
6564
) -> HTTPConnectionPool | HTTPSConnectionPool:
66-
proxy = select_proxy(url, proxies)
67-
parsed_url = urlparse(url)
68-
65+
proxy = select_proxy(request.url, proxies)
66+
try:
67+
host_params, pool_kwargs = self.build_connection_pool_key_attributes(
68+
request,
69+
verify,
70+
cert,
71+
)
72+
except ValueError as e:
73+
raise InvalidURL(e, request=request)
6974
if proxy:
7075
proxy = prepend_scheme_if_needed(proxy, "http")
7176
proxy_url = parse_url(proxy)
7277
if not proxy_url.host:
7378
raise InvalidProxyURL(
74-
"Please check proxy URL. It is malformed"
75-
" and could be missing the host."
79+
"Please check proxy URL. It is malformed "
80+
"and could be missing the host."
7681
)
7782
proxy_manager = self.proxy_manager_for(proxy)
7883

@@ -85,17 +90,22 @@ def get_connection(
8590
# Note: netloc also keeps user-info (user:pass@host) if present in URL. The driver never sends
8691
# URLs with embedded credentials, so we leave them unhandled — for full support
8792
# we’d need to manually concatenate hostname with optional port and IPv6 brackets.
93+
parsed_url = urlparse(request.url)
8894
proxy_manager.proxy_headers["Host"] = parsed_url.netloc
8995
else:
9096
logger.debug(
9197
f"Unable to set 'Host' to proxy manager of type {type(proxy_manager)} as"
9298
f" it does not have attribute 'proxy_headers'."
9399
)
94-
conn = proxy_manager.connection_from_url(url)
100+
101+
conn = proxy_manager.connection_from_host(
102+
**host_params, pool_kwargs=pool_kwargs
103+
)
95104
else:
96105
# Only scheme should be lower case
97-
url = parsed_url.geturl()
98-
conn = self.poolmanager.connection_from_url(url)
106+
conn = self.poolmanager.connection_from_host(
107+
**host_params, pool_kwargs=pool_kwargs
108+
)
99109

100110
return conn
101111

src/snowflake/connector/vendored/requests/__init__.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import warnings
4242

4343
from .. import urllib3
44+
4445
from .exceptions import RequestsDependencyWarning
4546

4647
try:
@@ -82,7 +83,11 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
8283
# charset_normalizer >= 2.0.0 < 4.0.0
8384
assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
8485
else:
85-
raise Exception("You need either charset_normalizer or chardet installed")
86+
warnings.warn(
87+
"Unable to find acceptable character detection dependency "
88+
"(chardet or charset_normalizer).",
89+
RequestsDependencyWarning,
90+
)
8691

8792

8893
def _check_cryptography(cryptography_version):
@@ -113,14 +118,24 @@ def _check_cryptography(cryptography_version):
113118
RequestsDependencyWarning,
114119
)
115120

116-
# Attempt to enable urllib3's SNI support, if possible
121+
# Attempt to enable urllib3's fallback for SNI support
122+
# if the standard library doesn't support SNI or the
123+
# 'ssl' library isn't available.
117124
try:
118-
from ..urllib3.contrib import pyopenssl
119-
pyopenssl.inject_into_urllib3()
125+
try:
126+
import ssl
127+
except ImportError:
128+
ssl = None
129+
130+
if not getattr(ssl, "HAS_SNI", False):
131+
from ..urllib3.contrib import pyopenssl
132+
133+
pyopenssl.inject_into_urllib3()
134+
135+
# Check cryptography version
136+
from cryptography import __version__ as cryptography_version
120137

121-
# Check cryptography version
122-
from cryptography import __version__ as cryptography_version
123-
_check_cryptography(cryptography_version)
138+
_check_cryptography(cryptography_version)
124139
except ImportError:
125140
pass
126141

@@ -133,7 +148,7 @@ def _check_cryptography(cryptography_version):
133148
import logging
134149
from logging import NullHandler
135150

136-
from . import utils
151+
from . import packages, utils
137152
from .__version__ import (
138153
__author__,
139154
__author_email__,

src/snowflake/connector/vendored/requests/__version__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
__title__ = "requests"
66
__description__ = "Python HTTP for Humans."
77
__url__ = "https://requests.readthedocs.io"
8-
__version__ = "2.31.0"
9-
__build__ = 0x023100
8+
__version__ = "2.32.5"
9+
__build__ = 0x023205
1010
__author__ = "Kenneth Reitz"
1111
__author_email__ = "[email protected]"
12-
__license__ = "Apache 2.0"
12+
__license__ = "Apache-2.0"
1313
__copyright__ = "Copyright Kenneth Reitz"
1414
__cake__ = "\u2728 \U0001f370 \u2728"

src/snowflake/connector/vendored/requests/adapters.py

Lines changed: 162 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import os.path
1010
import socket # noqa: F401
11+
import typing
12+
import warnings
1113

1214
from ..urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
1315
from ..urllib3.exceptions import HTTPError as _HTTPError
@@ -25,6 +27,7 @@
2527
from ..urllib3.util import Timeout as TimeoutSauce
2628
from ..urllib3.util import parse_url
2729
from ..urllib3.util.retry import Retry
30+
2831
from .auth import _basic_auth_str
2932
from .compat import basestring, urlparse
3033
from .cookies import extract_cookies_to_jar
@@ -60,12 +63,53 @@ def SOCKSProxyManager(*args, **kwargs):
6063
raise InvalidSchema("Missing dependencies for SOCKS support.")
6164

6265

66+
if typing.TYPE_CHECKING:
67+
from .models import PreparedRequest
68+
69+
6370
DEFAULT_POOLBLOCK = False
6471
DEFAULT_POOLSIZE = 10
6572
DEFAULT_RETRIES = 0
6673
DEFAULT_POOL_TIMEOUT = None
6774

6875

76+
def _urllib3_request_context(
77+
request: "PreparedRequest",
78+
verify: "bool | str | None",
79+
client_cert: "typing.Tuple[str, str] | str | None",
80+
poolmanager: "PoolManager",
81+
) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])":
82+
host_params = {}
83+
pool_kwargs = {}
84+
parsed_request_url = urlparse(request.url)
85+
scheme = parsed_request_url.scheme.lower()
86+
port = parsed_request_url.port
87+
88+
cert_reqs = "CERT_REQUIRED"
89+
if verify is False:
90+
cert_reqs = "CERT_NONE"
91+
elif isinstance(verify, str):
92+
if not os.path.isdir(verify):
93+
pool_kwargs["ca_certs"] = verify
94+
else:
95+
pool_kwargs["ca_cert_dir"] = verify
96+
pool_kwargs["cert_reqs"] = cert_reqs
97+
if client_cert is not None:
98+
if isinstance(client_cert, tuple) and len(client_cert) == 2:
99+
pool_kwargs["cert_file"] = client_cert[0]
100+
pool_kwargs["key_file"] = client_cert[1]
101+
else:
102+
# According to our docs, we allow users to specify just the client
103+
# cert path
104+
pool_kwargs["cert_file"] = client_cert
105+
host_params = {
106+
"scheme": scheme,
107+
"host": parsed_request_url.hostname,
108+
"port": port,
109+
}
110+
return host_params, pool_kwargs
111+
112+
69113
class BaseAdapter:
70114
"""The Base Transport Adapter"""
71115

@@ -246,7 +290,6 @@ def cert_verify(self, conn, url, verify, cert):
246290
:param cert: The SSL certificate to verify.
247291
"""
248292
if url.lower().startswith("https") and verify:
249-
250293
cert_loc = None
251294

252295
# Allow self-specified cert location.
@@ -327,15 +370,126 @@ def build_response(self, req, resp):
327370

328371
return response
329372

373+
def build_connection_pool_key_attributes(self, request, verify, cert=None):
374+
"""Build the PoolKey attributes used by urllib3 to return a connection.
375+
376+
This looks at the PreparedRequest, the user-specified verify value,
377+
and the value of the cert parameter to determine what PoolKey values
378+
to use to select a connection from a given urllib3 Connection Pool.
379+
380+
The SSL related pool key arguments are not consistently set. As of
381+
this writing, use the following to determine what keys may be in that
382+
dictionary:
383+
384+
* If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the
385+
default Requests SSL Context
386+
* If ``verify`` is ``False``, ``"ssl_context"`` will not be set but
387+
``"cert_reqs"`` will be set
388+
* If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
389+
``"ca_certs"`` will be set if the string is not a directory recognized
390+
by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be
391+
set.
392+
* If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
393+
``"cert"`` is a tuple with a second item, ``"key_file"`` will also
394+
be present
395+
396+
To override these settings, one may subclass this class, call this
397+
method and use the above logic to change parameters as desired. For
398+
example, if one wishes to use a custom :py:class:`ssl.SSLContext` one
399+
must both set ``"ssl_context"`` and based on what else they require,
400+
alter the other keys to ensure the desired behaviour.
401+
402+
:param request:
403+
The PreparedReqest being sent over the connection.
404+
:type request:
405+
:class:`~requests.models.PreparedRequest`
406+
:param verify:
407+
Either a boolean, in which case it controls whether
408+
we verify the server's TLS certificate, or a string, in which case it
409+
must be a path to a CA bundle to use.
410+
:param cert:
411+
(optional) Any user-provided SSL certificate for client
412+
authentication (a.k.a., mTLS). This may be a string (i.e., just
413+
the path to a file which holds both certificate and key) or a
414+
tuple of length 2 with the certificate file path and key file
415+
path.
416+
:returns:
417+
A tuple of two dictionaries. The first is the "host parameters"
418+
portion of the Pool Key including scheme, hostname, and port. The
419+
second is a dictionary of SSLContext related parameters.
420+
"""
421+
return _urllib3_request_context(request, verify, cert, self.poolmanager)
422+
423+
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
424+
"""Returns a urllib3 connection for the given request and TLS settings.
425+
This should not be called from user code, and is only exposed for use
426+
when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
427+
428+
:param request:
429+
The :class:`PreparedRequest <PreparedRequest>` object to be sent
430+
over the connection.
431+
:param verify:
432+
Either a boolean, in which case it controls whether we verify the
433+
server's TLS certificate, or a string, in which case it must be a
434+
path to a CA bundle to use.
435+
:param proxies:
436+
(optional) The proxies dictionary to apply to the request.
437+
:param cert:
438+
(optional) Any user-provided SSL certificate to be used for client
439+
authentication (a.k.a., mTLS).
440+
:rtype:
441+
urllib3.ConnectionPool
442+
"""
443+
proxy = select_proxy(request.url, proxies)
444+
try:
445+
host_params, pool_kwargs = self.build_connection_pool_key_attributes(
446+
request,
447+
verify,
448+
cert,
449+
)
450+
except ValueError as e:
451+
raise InvalidURL(e, request=request)
452+
if proxy:
453+
proxy = prepend_scheme_if_needed(proxy, "http")
454+
proxy_url = parse_url(proxy)
455+
if not proxy_url.host:
456+
raise InvalidProxyURL(
457+
"Please check proxy URL. It is malformed "
458+
"and could be missing the host."
459+
)
460+
proxy_manager = self.proxy_manager_for(proxy)
461+
conn = proxy_manager.connection_from_host(
462+
**host_params, pool_kwargs=pool_kwargs
463+
)
464+
else:
465+
# Only scheme should be lower case
466+
conn = self.poolmanager.connection_from_host(
467+
**host_params, pool_kwargs=pool_kwargs
468+
)
469+
470+
return conn
471+
330472
def get_connection(self, url, proxies=None):
331-
"""Returns a urllib3 connection for the given URL. This should not be
473+
"""DEPRECATED: Users should move to `get_connection_with_tls_context`
474+
for all subclasses of HTTPAdapter using Requests>=2.32.2.
475+
476+
Returns a urllib3 connection for the given URL. This should not be
332477
called from user code, and is only exposed for use when subclassing the
333478
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
334479
335480
:param url: The URL to connect to.
336481
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
337482
:rtype: urllib3.ConnectionPool
338483
"""
484+
warnings.warn(
485+
(
486+
"`get_connection` has been deprecated in favor of "
487+
"`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "
488+
"will need to migrate for Requests>=2.32.2. Please see "
489+
"https://github.com/psf/requests/pull/6710 for more details."
490+
),
491+
DeprecationWarning,
492+
)
339493
proxy = select_proxy(url, proxies)
340494

341495
if proxy:
@@ -390,6 +544,9 @@ def request_url(self, request, proxies):
390544
using_socks_proxy = proxy_scheme.startswith("socks")
391545

392546
url = request.path_url
547+
if url.startswith("//"): # Don't confuse urllib3
548+
url = f"/{url.lstrip('/')}"
549+
393550
if is_proxied_http_request and not using_socks_proxy:
394551
url = urldefragauth(request.url)
395552

@@ -450,7 +607,9 @@ def send(
450607
"""
451608

452609
try:
453-
conn = self.get_connection(request.url, proxies)
610+
conn = self.get_connection_with_tls_context(
611+
request, verify, proxies=proxies, cert=cert
612+
)
454613
except LocationValueError as e:
455614
raise InvalidURL(e, request=request)
456615

0 commit comments

Comments
 (0)