99import os .path
1010import socket # noqa: F401
1111import typing
12+ import warnings
1213
1314from pip ._vendor .urllib3 .exceptions import ClosedPoolError , ConnectTimeoutError
1415from pip ._vendor .urllib3 .exceptions import HTTPError as _HTTPError
@@ -72,26 +73,44 @@ def SOCKSProxyManager(*args, **kwargs):
7273DEFAULT_RETRIES = 0
7374DEFAULT_POOL_TIMEOUT = None
7475
75- _preloaded_ssl_context = create_urllib3_context ()
76- _preloaded_ssl_context .load_verify_locations (
77- extract_zipped_paths (DEFAULT_CA_BUNDLE_PATH )
78- )
76+
77+ try :
78+ import ssl # noqa: F401
79+
80+ _preloaded_ssl_context = create_urllib3_context ()
81+ _preloaded_ssl_context .load_verify_locations (
82+ extract_zipped_paths (DEFAULT_CA_BUNDLE_PATH )
83+ )
84+ except ImportError :
85+ # Bypass default SSLContext creation when Python
86+ # interpreter isn't built with the ssl module.
87+ _preloaded_ssl_context = None
7988
8089
8190def _urllib3_request_context (
8291 request : "PreparedRequest" ,
8392 verify : "bool | str | None" ,
8493 client_cert : "typing.Tuple[str, str] | str | None" ,
94+ poolmanager : "PoolManager" ,
8595) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])" :
8696 host_params = {}
8797 pool_kwargs = {}
8898 parsed_request_url = urlparse (request .url )
8999 scheme = parsed_request_url .scheme .lower ()
90100 port = parsed_request_url .port
101+
102+ # Determine if we have and should use our default SSLContext
103+ # to optimize performance on standard requests.
104+ poolmanager_kwargs = getattr (poolmanager , "connection_pool_kw" , {})
105+ has_poolmanager_ssl_context = poolmanager_kwargs .get ("ssl_context" )
106+ should_use_default_ssl_context = (
107+ _preloaded_ssl_context is not None and not has_poolmanager_ssl_context
108+ )
109+
91110 cert_reqs = "CERT_REQUIRED"
92111 if verify is False :
93112 cert_reqs = "CERT_NONE"
94- elif verify is True :
113+ elif verify is True and should_use_default_ssl_context :
95114 pool_kwargs ["ssl_context" ] = _preloaded_ssl_context
96115 elif isinstance (verify , str ):
97116 if not os .path .isdir (verify ):
@@ -374,13 +393,83 @@ def build_response(self, req, resp):
374393
375394 return response
376395
377- def _get_connection (self , request , verify , proxies = None , cert = None ):
378- # Replace the existing get_connection without breaking things and
379- # ensure that TLS settings are considered when we interact with
380- # urllib3 HTTP Pools
396+ def build_connection_pool_key_attributes (self , request , verify , cert = None ):
397+ """Build the PoolKey attributes used by urllib3 to return a connection.
398+
399+ This looks at the PreparedRequest, the user-specified verify value,
400+ and the value of the cert parameter to determine what PoolKey values
401+ to use to select a connection from a given urllib3 Connection Pool.
402+
403+ The SSL related pool key arguments are not consistently set. As of
404+ this writing, use the following to determine what keys may be in that
405+ dictionary:
406+
407+ * If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the
408+ default Requests SSL Context
409+ * If ``verify`` is ``False``, ``"ssl_context"`` will not be set but
410+ ``"cert_reqs"`` will be set
411+ * If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
412+ ``"ca_certs"`` will be set if the string is not a directory recognized
413+ by :py:func:`os.path.isdir`, otherwise ``"ca_certs_dir"`` will be
414+ set.
415+ * If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
416+ ``"cert"`` is a tuple with a second item, ``"key_file"`` will also
417+ be present
418+
419+ To override these settings, one may subclass this class, call this
420+ method and use the above logic to change parameters as desired. For
421+ example, if one wishes to use a custom :py:class:`ssl.SSLContext` one
422+ must both set ``"ssl_context"`` and based on what else they require,
423+ alter the other keys to ensure the desired behaviour.
424+
425+ :param request:
426+ The PreparedReqest being sent over the connection.
427+ :type request:
428+ :class:`~requests.models.PreparedRequest`
429+ :param verify:
430+ Either a boolean, in which case it controls whether
431+ we verify the server's TLS certificate, or a string, in which case it
432+ must be a path to a CA bundle to use.
433+ :param cert:
434+ (optional) Any user-provided SSL certificate for client
435+ authentication (a.k.a., mTLS). This may be a string (i.e., just
436+ the path to a file which holds both certificate and key) or a
437+ tuple of length 2 with the certificate file path and key file
438+ path.
439+ :returns:
440+ A tuple of two dictionaries. The first is the "host parameters"
441+ portion of the Pool Key including scheme, hostname, and port. The
442+ second is a dictionary of SSLContext related parameters.
443+ """
444+ return _urllib3_request_context (request , verify , cert , self .poolmanager )
445+
446+ def get_connection_with_tls_context (self , request , verify , proxies = None , cert = None ):
447+ """Returns a urllib3 connection for the given request and TLS settings.
448+ This should not be called from user code, and is only exposed for use
449+ when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
450+
451+ :param request:
452+ The :class:`PreparedRequest <PreparedRequest>` object to be sent
453+ over the connection.
454+ :param verify:
455+ Either a boolean, in which case it controls whether we verify the
456+ server's TLS certificate, or a string, in which case it must be a
457+ path to a CA bundle to use.
458+ :param proxies:
459+ (optional) The proxies dictionary to apply to the request.
460+ :param cert:
461+ (optional) Any user-provided SSL certificate to be used for client
462+ authentication (a.k.a., mTLS).
463+ :rtype:
464+ urllib3.ConnectionPool
465+ """
381466 proxy = select_proxy (request .url , proxies )
382467 try :
383- host_params , pool_kwargs = _urllib3_request_context (request , verify , cert )
468+ host_params , pool_kwargs = self .build_connection_pool_key_attributes (
469+ request ,
470+ verify ,
471+ cert ,
472+ )
384473 except ValueError as e :
385474 raise InvalidURL (e , request = request )
386475 if proxy :
@@ -404,14 +493,26 @@ def _get_connection(self, request, verify, proxies=None, cert=None):
404493 return conn
405494
406495 def get_connection (self , url , proxies = None ):
407- """Returns a urllib3 connection for the given URL. This should not be
496+ """DEPRECATED: Users should move to `get_connection_with_tls_context`
497+ for all subclasses of HTTPAdapter using Requests>=2.32.2.
498+
499+ Returns a urllib3 connection for the given URL. This should not be
408500 called from user code, and is only exposed for use when subclassing the
409501 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
410502
411503 :param url: The URL to connect to.
412504 :param proxies: (optional) A Requests-style dictionary of proxies used on this request.
413505 :rtype: urllib3.ConnectionPool
414506 """
507+ warnings .warn (
508+ (
509+ "`get_connection` has been deprecated in favor of "
510+ "`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "
511+ "will need to migrate for Requests>=2.32.2. Please see "
512+ "https://github.com/psf/requests/pull/6710 for more details."
513+ ),
514+ DeprecationWarning ,
515+ )
415516 proxy = select_proxy (url , proxies )
416517
417518 if proxy :
@@ -529,7 +630,9 @@ def send(
529630 """
530631
531632 try :
532- conn = self ._get_connection (request , verify , proxies = proxies , cert = cert )
633+ conn = self .get_connection_with_tls_context (
634+ request , verify , proxies = proxies , cert = cert
635+ )
533636 except LocationValueError as e :
534637 raise InvalidURL (e , request = request )
535638
0 commit comments