Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support both urllib3 v1 and v2 at the same time (fixes #688) #699

Merged
merged 12 commits into from
May 26, 2023

Conversation

hartwork
Copy link
Collaborator

@hartwork hartwork commented May 16, 2023

Hi! 👋

This builds on the work by @shifqu in #697 literally. Fixes #688, happy to adjust.

I would like to note two things:

  • If anyone has ideas about the urllib3 v2 SSL connection abort protocol errors that seems to happen with Python 3.7/3.8/3.9 but not with Python 3.10/3.11 — that would rock! 🙏
  • If some form of this pull request ends up being merged, please do not squash the commits together. Thanks! 🙏

Looking forward to your review! 🍻

CC @jairhenrique @kevin1024 @pquentin @shifqu @vEpiphyte

@codecov-commenter
Copy link

codecov-commenter commented May 16, 2023

Codecov Report

Merging #699 (52efffb) into master (c41bd2b) will increase coverage by 0.10%.
The diff coverage is 100.00%.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

@@            Coverage Diff             @@
##           master     #699      +/-   ##
==========================================
+ Coverage   90.15%   90.25%   +0.10%     
==========================================
  Files          27       27              
  Lines        1726     1745      +19     
  Branches      308      311       +3     
==========================================
+ Hits         1556     1575      +19     
  Misses        135      135              
  Partials       35       35              
Impacted Files Coverage Δ
vcr/patch.py 88.42% <100.00%> (+0.19%) ⬆️
vcr/stubs/__init__.py 94.68% <100.00%> (+0.46%) ⬆️
vcr/stubs/requests_stubs.py 100.00% <100.00%> (ø)
vcr/stubs/urllib3_stubs.py 100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@hartwork hartwork changed the title Support both urllib3 v1 and v2 at the same time Support both urllib3 v1 and v2 at the same time (fixes #688) May 16, 2023
@shifqu
Copy link
Contributor

shifqu commented May 17, 2023

Thanks a lot @hartwork, does this mean I should close my PR in favor of this one?

Very weird to see 3.10 and 3.11 work, while the others don't :/

@vEpiphyte
Copy link

If there is code that is using the standard library ssl module to create contexts, the defaults for those contexts have changed over time and gotten tighter. That is a good thing from a security perspective, but can be difficult for supporting the same code across multiple versions. I've seen this result in different errors (ssl.sslerror vs ConnectionResetError ) with different interpreter versions. Depending on the underlying code and test assertions, it is possible that things just need to be adjusted to account in changes

Can you link directly to the tests and their CI failures?

@hartwork
Copy link
Collaborator Author

hartwork commented May 17, 2023

@vEpiphyte for all of CPython 3.7, 3.8, 3.9, pypy 3.8 the failing environments are these two:

  • py3[..]-requests-urllib3-2
  • py3[..]-urllib3-2

They all fail at test tests/integration/test_urllib3.py::test_post[https].

I'll inline the CI traceback from py37-requests-urllib3-2 (i.e. CPython 3.7) below so that even when CI links stop working in a few days, we'll still have the output right here:

👉 Click me to unfold tests/integration/test_urllib3.py::test_post[https] error log 👈
2023-05-16T22:17:19.3527363Z =================================== FAILURES ===================================
2023-05-16T22:17:19.3527716Z _______________________________ test_post[https] _______________________________
2023-05-16T22:17:19.3527920Z 
2023-05-16T22:17:19.3528163Z self = 
2023-05-16T22:17:19.3528941Z method = 'POST', url = '/post', body = {'key1': 'value1', 'key2': 'value2'}
2023-05-16T22:17:19.3529279Z headers = HTTPHeaderDict({})
2023-05-16T22:17:19.3529622Z retries = Retry(total=3, connect=None, read=None, redirect=None, status=None)
2023-05-16T22:17:19.3530165Z redirect = False, assert_same_host = False, timeout = <_TYPE_DEFAULT.token: -1>
2023-05-16T22:17:19.3530588Z pool_timeout = None, release_conn = True, chunked = False, body_pos = None
2023-05-16T22:17:19.3530979Z preload_content = True, decode_content = True, response_kw = {}
2023-05-16T22:17:19.3531523Z parsed_url = Url(scheme=None, auth=None, host=None, port=None, path='/post', query=None, fragment=None)
2023-05-16T22:17:19.3531957Z destination_scheme = None, conn = None, release_this_conn = True
2023-05-16T22:17:19.3532334Z http_tunnel_required = False, err = None, clean_exit = False
2023-05-16T22:17:19.3532546Z 
2023-05-16T22:17:19.3532673Z     def urlopen(  # type: ignore[override]
2023-05-16T22:17:19.3532931Z         self,
2023-05-16T22:17:19.3533157Z         method: str,
2023-05-16T22:17:19.3533371Z         url: str,
2023-05-16T22:17:19.3533627Z         body: _TYPE_BODY | None = None,
2023-05-16T22:17:19.3585589Z         headers: typing.Mapping[str, str] | None = None,
2023-05-16T22:17:19.3586362Z         retries: Retry | bool | int | None = None,
2023-05-16T22:17:19.3586633Z         redirect: bool = True,
2023-05-16T22:17:19.3586908Z         assert_same_host: bool = True,
2023-05-16T22:17:19.3587173Z         timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
2023-05-16T22:17:19.3587448Z         pool_timeout: int | None = None,
2023-05-16T22:17:19.3587709Z         release_conn: bool | None = None,
2023-05-16T22:17:19.3587937Z         chunked: bool = False,
2023-05-16T22:17:19.3588204Z         body_pos: _TYPE_BODY_POSITION | None = None,
2023-05-16T22:17:19.3588707Z         preload_content: bool = True,
2023-05-16T22:17:19.3588944Z         decode_content: bool = True,
2023-05-16T22:17:19.3589194Z         **response_kw: typing.Any,
2023-05-16T22:17:19.3589658Z     ) -> BaseHTTPResponse:
2023-05-16T22:17:19.3589879Z         """
2023-05-16T22:17:19.3590151Z         Get a connection from the pool and perform an HTTP request. This is the
2023-05-16T22:17:19.3590615Z         lowest level call for making a request, so you'll need to specify all
2023-05-16T22:17:19.3590903Z         the raw details.
2023-05-16T22:17:19.3591087Z     
2023-05-16T22:17:19.3591297Z         .. note::
2023-05-16T22:17:19.3591484Z     
2023-05-16T22:17:19.3591808Z            More commonly, it's appropriate to use a convenience method
2023-05-16T22:17:19.3592116Z            such as :meth:`request`.
2023-05-16T22:17:19.3592323Z     
2023-05-16T22:17:19.3592490Z         .. note::
2023-05-16T22:17:19.3592672Z     
2023-05-16T22:17:19.3592909Z            `release_conn` will only behave as expected if
2023-05-16T22:17:19.3593208Z            `preload_content=False` because we want to make
2023-05-16T22:17:19.3597422Z            `preload_content=False` the default behaviour someday soon without
2023-05-16T22:17:19.3597760Z            breaking backwards compatibility.
2023-05-16T22:17:19.3598009Z     
2023-05-16T22:17:19.3598189Z         :param method:
2023-05-16T22:17:19.3598460Z             HTTP request method (such as GET, POST, PUT, etc.)
2023-05-16T22:17:19.3598716Z     
2023-05-16T22:17:19.3598889Z         :param url:
2023-05-16T22:17:19.3599130Z             The URL to perform the request on.
2023-05-16T22:17:19.3599348Z     
2023-05-16T22:17:19.3599518Z         :param body:
2023-05-16T22:17:19.3599802Z             Data to send in the request body, either :class:`str`, :class:`bytes`,
2023-05-16T22:17:19.3600300Z             an iterable of :class:`str`/:class:`bytes`, or a file-like object.
2023-05-16T22:17:19.3600540Z     
2023-05-16T22:17:19.3600735Z         :param headers:
2023-05-16T22:17:19.3601096Z             Dictionary of custom headers to send, such as User-Agent,
2023-05-16T22:17:19.3601538Z             If-None-Match, etc. If None, pool headers are used. If provided,
2023-05-16T22:17:19.3601964Z             these headers completely replace any pool-specific headers.
2023-05-16T22:17:19.3602222Z     
2023-05-16T22:17:19.3602412Z         :param retries:
2023-05-16T22:17:19.3602678Z             Configure the number of retries to allow before raising a
2023-05-16T22:17:19.3603023Z             :class:`~urllib3.exceptions.MaxRetryError` exception.
2023-05-16T22:17:19.3603288Z     
2023-05-16T22:17:19.3603533Z             Pass ``None`` to retry until you receive a response. Pass a
2023-05-16T22:17:19.3603975Z             :class:`~urllib3.util.retry.Retry` object for fine-grained control
2023-05-16T22:17:19.3607925Z             over different types of retries.
2023-05-16T22:17:19.3608286Z             Pass an integer number to retry connection errors that many times,
2023-05-16T22:17:19.3608617Z             but no other types of errors. Pass zero to never retry.
2023-05-16T22:17:19.3608879Z     
2023-05-16T22:17:19.3609149Z             If ``False``, then retries are disabled and any exception is raised
2023-05-16T22:17:19.3609497Z             immediately. Also, instead of raising a MaxRetryError on redirects,
2023-05-16T22:17:19.3609826Z             the redirect response will be returned.
2023-05-16T22:17:19.3610054Z     
2023-05-16T22:17:19.3610543Z         :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.
2023-05-16T22:17:19.3610801Z     
2023-05-16T22:17:19.3610998Z         :param redirect:
2023-05-16T22:17:19.3611293Z             If True, automatically handle redirects (status codes 301, 302,
2023-05-16T22:17:19.3611617Z             303, 307, 308). Each redirect counts as a retry. Disabling retries
2023-05-16T22:17:19.3611902Z             will disable redirect, too.
2023-05-16T22:17:19.3612116Z     
2023-05-16T22:17:19.3612348Z         :param assert_same_host:
2023-05-16T22:17:19.3612644Z             If ``True``, will make sure that the host of the pool requests is
2023-05-16T22:17:19.3613130Z             consistent else will raise HostChangedError. When ``False``, you can
2023-05-16T22:17:19.3613463Z             use the pool on an HTTP proxy and request foreign hosts.
2023-05-16T22:17:19.3613712Z     
2023-05-16T22:17:19.3613906Z         :param timeout:
2023-05-16T22:17:19.3617099Z             If specified, overrides the default timeout for this one
2023-05-16T22:17:19.3617476Z             request. It may be a float (in seconds) or an instance of
2023-05-16T22:17:19.3617759Z             :class:`urllib3.util.Timeout`.
2023-05-16T22:17:19.3617986Z     
2023-05-16T22:17:19.3618190Z         :param pool_timeout:
2023-05-16T22:17:19.3618470Z             If set and the pool is set to block=True, then this method will
2023-05-16T22:17:19.3618823Z             block for ``pool_timeout`` seconds and raise EmptyPoolError if no
2023-05-16T22:17:19.3619158Z             connection is available within the time period.
2023-05-16T22:17:19.3619408Z     
2023-05-16T22:17:19.3619605Z         :param bool preload_content:
2023-05-16T22:17:19.3620042Z             If True, the response's body will be preloaded into memory.
2023-05-16T22:17:19.3620298Z     
2023-05-16T22:17:19.3620490Z         :param bool decode_content:
2023-05-16T22:17:19.3620773Z             If True, will attempt to decode the body based on the
2023-05-16T22:17:19.3621117Z             'content-encoding' header.
2023-05-16T22:17:19.3621323Z     
2023-05-16T22:17:19.3621521Z         :param release_conn:
2023-05-16T22:17:19.3621810Z             If False, then the urlopen call will not release the connection
2023-05-16T22:17:19.3622144Z             back into the pool once a response is received (but will release if
2023-05-16T22:17:19.3622489Z             you read the entire contents of the response such as when
2023-05-16T22:17:19.3622901Z             `preload_content=True`). This is useful if you're not preloading
2023-05-16T22:17:19.3626101Z             the response's content immediately. You will need to call
2023-05-16T22:17:19.3626468Z             ``r.release_conn()`` on the response ``r`` to return the connection
2023-05-16T22:17:19.3626831Z             back into the pool. If None, it takes the value of ``preload_content``
2023-05-16T22:17:19.3627132Z             which defaults to ``True``.
2023-05-16T22:17:19.3627351Z     
2023-05-16T22:17:19.3627537Z         :param bool chunked:
2023-05-16T22:17:19.3627827Z             If True, urllib3 will send the body using chunked transfer
2023-05-16T22:17:19.3628169Z             encoding. Otherwise, urllib3 will send the body using the standard
2023-05-16T22:17:19.3628540Z             content-length form. Defaults to False.
2023-05-16T22:17:19.3628810Z     
2023-05-16T22:17:19.3629010Z         :param int body_pos:
2023-05-16T22:17:19.3629368Z             Position to seek to in file-like body in the event of a retry or
2023-05-16T22:17:19.3629806Z             redirect. Typically this won't need to be set because urllib3 will
2023-05-16T22:17:19.3630188Z             auto-populate the value when needed.
2023-05-16T22:17:19.3630407Z         """
2023-05-16T22:17:19.3630622Z         parsed_url = parse_url(url)
2023-05-16T22:17:19.3630894Z         destination_scheme = parsed_url.scheme
2023-05-16T22:17:19.3631120Z     
2023-05-16T22:17:19.3631302Z         if headers is None:
2023-05-16T22:17:19.3631532Z             headers = self.headers
2023-05-16T22:17:19.3631938Z     
2023-05-16T22:17:19.3632147Z         if not isinstance(retries, Retry):
2023-05-16T22:17:19.3635233Z             retries = Retry.from_int(retries, redirect=redirect, default=self.retries)
2023-05-16T22:17:19.3635523Z     
2023-05-16T22:17:19.3635736Z         if release_conn is None:
2023-05-16T22:17:19.3635990Z             release_conn = preload_content
2023-05-16T22:17:19.3636206Z     
2023-05-16T22:17:19.3636382Z         # Check host
2023-05-16T22:17:19.3636646Z         if assert_same_host and not self.is_same_host(url):
2023-05-16T22:17:19.3636950Z             raise HostChangedError(self, url, retries)
2023-05-16T22:17:19.3637369Z     
2023-05-16T22:17:19.3637744Z         # Ensure that the URL we're connecting to is properly encoded
2023-05-16T22:17:19.3638035Z         if url.startswith("/"):
2023-05-16T22:17:19.3638291Z             url = to_str(_encode_target(url))
2023-05-16T22:17:19.3638515Z         else:
2023-05-16T22:17:19.3638736Z             url = to_str(parsed_url.url)
2023-05-16T22:17:19.3638963Z     
2023-05-16T22:17:19.3639133Z         conn = None
2023-05-16T22:17:19.3639324Z     
2023-05-16T22:17:19.3639565Z         # Track whether `conn` needs to be released before
2023-05-16T22:17:19.3639891Z         # returning/raising/recursing. Update this variable if necessary, and
2023-05-16T22:17:19.3640261Z         # leave `release_conn` constant throughout the function. That way, if
2023-05-16T22:17:19.3640617Z         # the function recurses, the original value of `release_conn` will be
2023-05-16T22:17:19.3640973Z         # passed down into the recursive call, and its value will be respected.
2023-05-16T22:17:19.3641232Z         #
2023-05-16T22:17:19.3641446Z         # See issue #651 [1] for details.
2023-05-16T22:17:19.3641657Z         #
2023-05-16T22:17:19.3641909Z         # [1] 
2023-05-16T22:17:19.3642194Z         release_this_conn = release_conn
2023-05-16T22:17:19.3642415Z     
2023-05-16T22:17:19.3645662Z         http_tunnel_required = connection_requires_http_tunnel(
2023-05-16T22:17:19.3646028Z             self.proxy, self.proxy_config, destination_scheme
2023-05-16T22:17:19.3646384Z         )
2023-05-16T22:17:19.3646564Z     
2023-05-16T22:17:19.3646827Z         # Merge the proxy headers. Only done when not using HTTP CONNECT. We
2023-05-16T22:17:19.3647315Z         # have to copy the headers dict so we can safely change it without those
2023-05-16T22:17:19.3647772Z         # changes being reflected in anyone else's copy.
2023-05-16T22:17:19.3648031Z         if not http_tunnel_required:
2023-05-16T22:17:19.3648414Z             headers = headers.copy()  # type: ignore[attr-defined]
2023-05-16T22:17:19.3648855Z             headers.update(self.proxy_headers)  # type: ignore[union-attr]
2023-05-16T22:17:19.3649122Z     
2023-05-16T22:17:19.3649380Z         # Must keep the exception bound to a separate variable or else Python 3
2023-05-16T22:17:19.3649704Z         # complains about UnboundLocalError.
2023-05-16T22:17:19.3649941Z         err = None
2023-05-16T22:17:19.3650120Z     
2023-05-16T22:17:19.3650382Z         # Keep track of whether we cleanly exited the except block. This
2023-05-16T22:17:19.3650696Z         # ensures we do proper cleanup in finally.
2023-05-16T22:17:19.3650936Z         clean_exit = False
2023-05-16T22:17:19.3651132Z     
2023-05-16T22:17:19.3651386Z         # Rewind body position, if needed. Record current position
2023-05-16T22:17:19.3651693Z         # for future rewinds in the event of a redirect/retry.
2023-05-16T22:17:19.3651993Z         body_pos = set_file_position(body, body_pos)
2023-05-16T22:17:19.3652232Z     
2023-05-16T22:17:19.3655149Z         try:
2023-05-16T22:17:19.3655396Z             # Request a connection from the queue.
2023-05-16T22:17:19.3655727Z             timeout_obj = self._get_timeout(timeout)
2023-05-16T22:17:19.3656022Z             conn = self._get_conn(timeout=pool_timeout)
2023-05-16T22:17:19.3656245Z     
2023-05-16T22:17:19.3656733Z             conn.timeout = timeout_obj.connect_timeout  # type: ignore[assignment]
2023-05-16T22:17:19.3657022Z     
2023-05-16T22:17:19.3657292Z             # Is this a closed/new connection that requires CONNECT tunnelling?
2023-05-16T22:17:19.3657646Z             if self.proxy is not None and http_tunnel_required and conn.is_closed:
2023-05-16T22:17:19.3657919Z                 try:
2023-05-16T22:17:19.3658151Z                     self._prepare_proxy(conn)
2023-05-16T22:17:19.3658442Z                 except (BaseSSLError, OSError, SocketTimeout) as e:
2023-05-16T22:17:19.3658726Z                     self._raise_timeout(
2023-05-16T22:17:19.3659139Z                         err=e, url=self.proxy.url, timeout_value=conn.timeout
2023-05-16T22:17:19.3659390Z                     )
2023-05-16T22:17:19.3659586Z                     raise
2023-05-16T22:17:19.3659823Z     
2023-05-16T22:17:19.3660207Z             # If we're going to release the connection in ``finally:``, then
2023-05-16T22:17:19.3660640Z             # the response doesn't need to know about the connection. Otherwise
2023-05-16T22:17:19.3661068Z             # it will also try to release it and we'll have a double-release
2023-05-16T22:17:19.3661327Z             # mess.
2023-05-16T22:17:19.3664334Z             response_conn = conn if not release_conn else None
2023-05-16T22:17:19.3664588Z     
2023-05-16T22:17:19.3664842Z             # Make the request on the HTTPConnection object
2023-05-16T22:17:19.3665144Z             response = self._make_request(
2023-05-16T22:17:19.3665357Z                 conn,
2023-05-16T22:17:19.3665554Z                 method,
2023-05-16T22:17:19.3665762Z                 url,
2023-05-16T22:17:19.3665967Z                 timeout=timeout_obj,
2023-05-16T22:17:19.3666189Z                 body=body,
2023-05-16T22:17:19.3666406Z                 headers=headers,
2023-05-16T22:17:19.3666617Z                 chunked=chunked,
2023-05-16T22:17:19.3666839Z                 retries=retries,
2023-05-16T22:17:19.3667084Z                 response_conn=response_conn,
2023-05-16T22:17:19.3667350Z                 preload_content=preload_content,
2023-05-16T22:17:19.3667619Z                 decode_content=decode_content,
2023-05-16T22:17:19.3667858Z >               **response_kw,
2023-05-16T22:17:19.3668047Z             )
2023-05-16T22:17:19.3668166Z 
2023-05-16T22:17:19.3668533Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connectionpool.py:802: 
2023-05-16T22:17:19.3668891Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3669044Z 
2023-05-16T22:17:19.3669251Z self = 
2023-05-16T22:17:19.3669896Z conn = 
2023-05-16T22:17:19.3670440Z method = 'POST', url = '/post', body = {'key1': 'value1', 'key2': 'value2'}
2023-05-16T22:17:19.3673389Z headers = HTTPHeaderDict({})
2023-05-16T22:17:19.3673726Z retries = Retry(total=3, connect=None, read=None, redirect=None, status=None)
2023-05-16T22:17:19.3674267Z timeout = Timeout(connect=<_TYPE_DEFAULT.token: -1>, read=<_TYPE_DEFAULT.token: -1>, total=None)
2023-05-16T22:17:19.3674659Z chunked = False, response_conn = None, preload_content = True
2023-05-16T22:17:19.3674979Z decode_content = True, enforce_content_length = True
2023-05-16T22:17:19.3675154Z 
2023-05-16T22:17:19.3675246Z     def _make_request(
2023-05-16T22:17:19.3675435Z         self,
2023-05-16T22:17:19.3675660Z         conn: BaseHTTPConnection,
2023-05-16T22:17:19.3675900Z         method: str,
2023-05-16T22:17:19.3676091Z         url: str,
2023-05-16T22:17:19.3676311Z         body: _TYPE_BODY | None = None,
2023-05-16T22:17:19.3676597Z         headers: typing.Mapping[str, str] | None = None,
2023-05-16T22:17:19.3676856Z         retries: Retry | None = None,
2023-05-16T22:17:19.3677122Z         timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
2023-05-16T22:17:19.3677378Z         chunked: bool = False,
2023-05-16T22:17:19.3677833Z         response_conn: BaseHTTPConnection | None = None,
2023-05-16T22:17:19.3678126Z         preload_content: bool = True,
2023-05-16T22:17:19.3678375Z         decode_content: bool = True,
2023-05-16T22:17:19.3678639Z         enforce_content_length: bool = True,
2023-05-16T22:17:19.3678930Z     ) -> BaseHTTPResponse:
2023-05-16T22:17:19.3679146Z         """
2023-05-16T22:17:19.3679427Z         Perform a request on a given urllib connection object taken from our
2023-05-16T22:17:19.3679683Z         pool.
2023-05-16T22:17:19.3679924Z     
2023-05-16T22:17:19.3680113Z         :param conn:
2023-05-16T22:17:19.3680469Z             a connection from one of our connection pools
2023-05-16T22:17:19.3683515Z     
2023-05-16T22:17:19.3683739Z         :param method:
2023-05-16T22:17:19.3684089Z             HTTP request method (such as GET, POST, PUT, etc.)
2023-05-16T22:17:19.3684332Z     
2023-05-16T22:17:19.3684700Z         :param url:
2023-05-16T22:17:19.3684948Z             The URL to perform the request on.
2023-05-16T22:17:19.3685170Z     
2023-05-16T22:17:19.3685479Z         :param body:
2023-05-16T22:17:19.3685863Z             Data to send in the request body, either :class:`str`, :class:`bytes`,
2023-05-16T22:17:19.3686345Z             an iterable of :class:`str`/:class:`bytes`, or a file-like object.
2023-05-16T22:17:19.3686599Z     
2023-05-16T22:17:19.3686794Z         :param headers:
2023-05-16T22:17:19.3687162Z             Dictionary of custom headers to send, such as User-Agent,
2023-05-16T22:17:19.3687576Z             If-None-Match, etc. If None, pool headers are used. If provided,
2023-05-16T22:17:19.3688016Z             these headers completely replace any pool-specific headers.
2023-05-16T22:17:19.3688276Z     
2023-05-16T22:17:19.3688453Z         :param retries:
2023-05-16T22:17:19.3688726Z             Configure the number of retries to allow before raising a
2023-05-16T22:17:19.3689071Z             :class:`~urllib3.exceptions.MaxRetryError` exception.
2023-05-16T22:17:19.3689318Z     
2023-05-16T22:17:19.3689583Z             Pass ``None`` to retry until you receive a response. Pass a
2023-05-16T22:17:19.3690019Z             :class:`~urllib3.util.retry.Retry` object for fine-grained control
2023-05-16T22:17:19.3690333Z             over different types of retries.
2023-05-16T22:17:19.3693392Z             Pass an integer number to retry connection errors that many times,
2023-05-16T22:17:19.3693758Z             but no other types of errors. Pass zero to never retry.
2023-05-16T22:17:19.3694028Z     
2023-05-16T22:17:19.3694297Z             If ``False``, then retries are disabled and any exception is raised
2023-05-16T22:17:19.3694650Z             immediately. Also, instead of raising a MaxRetryError on redirects,
2023-05-16T22:17:19.3694971Z             the redirect response will be returned.
2023-05-16T22:17:19.3695202Z     
2023-05-16T22:17:19.3695461Z         :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.
2023-05-16T22:17:19.3695722Z     
2023-05-16T22:17:19.3695911Z         :param timeout:
2023-05-16T22:17:19.3696184Z             If specified, overrides the default timeout for this one
2023-05-16T22:17:19.3696516Z             request. It may be a float (in seconds) or an instance of
2023-05-16T22:17:19.3696812Z             :class:`urllib3.util.Timeout`.
2023-05-16T22:17:19.3697025Z     
2023-05-16T22:17:19.3697296Z         :param chunked:
2023-05-16T22:17:19.3697573Z             If True, urllib3 will send the body using chunked transfer
2023-05-16T22:17:19.3697916Z             encoding. Otherwise, urllib3 will send the body using the standard
2023-05-16T22:17:19.3698347Z             content-length form. Defaults to False.
2023-05-16T22:17:19.3698581Z     
2023-05-16T22:17:19.3698787Z         :param response_conn:
2023-05-16T22:17:19.3699070Z             Set this to ``None`` if you will handle releasing the connection or
2023-05-16T22:17:19.3699404Z             set the connection to have the response release it.
2023-05-16T22:17:19.3699645Z     
2023-05-16T22:17:19.3702615Z         :param preload_content:
2023-05-16T22:17:19.3703293Z           If True, the response's body will be preloaded during construction.
2023-05-16T22:17:19.3703591Z     
2023-05-16T22:17:19.3703798Z         :param decode_content:
2023-05-16T22:17:19.3704063Z             If True, will attempt to decode the body based on the
2023-05-16T22:17:19.3704404Z             'content-encoding' header.
2023-05-16T22:17:19.3704623Z     
2023-05-16T22:17:19.3704823Z         :param enforce_content_length:
2023-05-16T22:17:19.3705136Z             Enforce content length checking. Body returned by server must match
2023-05-16T22:17:19.3705709Z             value of Content-Length header, if present. Otherwise, raise error.
2023-05-16T22:17:19.3705970Z         """
2023-05-16T22:17:19.3706182Z         self.num_requests += 1
2023-05-16T22:17:19.3706395Z     
2023-05-16T22:17:19.3706609Z         timeout_obj = self._get_timeout(timeout)
2023-05-16T22:17:19.3706876Z         timeout_obj.start_connect()
2023-05-16T22:17:19.3707199Z         conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout)
2023-05-16T22:17:19.3707471Z     
2023-05-16T22:17:19.3707637Z         try:
2023-05-16T22:17:19.3708015Z             # Trigger any extra validation we need to do.
2023-05-16T22:17:19.3708261Z             try:
2023-05-16T22:17:19.3708471Z                 self._validate_conn(conn)
2023-05-16T22:17:19.3708753Z             except (SocketTimeout, BaseSSLError) as e:
2023-05-16T22:17:19.3709085Z                 self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
2023-05-16T22:17:19.3709343Z                 raise
2023-05-16T22:17:19.3709543Z     
2023-05-16T22:17:19.3709798Z         # _validate_conn() starts the connection to an HTTPS proxy
2023-05-16T22:17:19.3713348Z         # so we need to wrap errors with 'ProxyError' here too.
2023-05-16T22:17:19.3713647Z         except (
2023-05-16T22:17:19.3713849Z             OSError,
2023-05-16T22:17:19.3714075Z             NewConnectionError,
2023-05-16T22:17:19.3714290Z             TimeoutError,
2023-05-16T22:17:19.3714518Z             BaseSSLError,
2023-05-16T22:17:19.3714746Z             CertificateError,
2023-05-16T22:17:19.3714949Z             SSLError,
2023-05-16T22:17:19.3715144Z         ) as e:
2023-05-16T22:17:19.3715358Z             new_e: Exception = e
2023-05-16T22:17:19.3715630Z             if isinstance(e, (BaseSSLError, CertificateError)):
2023-05-16T22:17:19.3715908Z                 new_e = SSLError(e)
2023-05-16T22:17:19.3716283Z             # If the connection didn't successfully connect to it's proxy
2023-05-16T22:17:19.3716541Z             # then there
2023-05-16T22:17:19.3716760Z             if isinstance(
2023-05-16T22:17:19.3717057Z                 new_e, (OSError, NewConnectionError, TimeoutError, SSLError)
2023-05-16T22:17:19.3717414Z             ) and (conn and conn.proxy and not conn.has_connected_to_proxy):
2023-05-16T22:17:19.3717734Z                 new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
2023-05-16T22:17:19.3717992Z             raise new_e
2023-05-16T22:17:19.3718183Z     
2023-05-16T22:17:19.3718445Z         # conn.request() calls http.client.*.request, not the method in
2023-05-16T22:17:19.3718782Z         # urllib3.request. It also calls makefile (recv) on the socket.
2023-05-16T22:17:19.3719038Z         try:
2023-05-16T22:17:19.3719228Z             conn.request(
2023-05-16T22:17:19.3719433Z                 method,
2023-05-16T22:17:19.3719633Z                 url,
2023-05-16T22:17:19.3719822Z                 body=body,
2023-05-16T22:17:19.3722745Z                 headers=headers,
2023-05-16T22:17:19.3722989Z                 chunked=chunked,
2023-05-16T22:17:19.3723289Z                 preload_content=preload_content,
2023-05-16T22:17:19.3723567Z                 decode_content=decode_content,
2023-05-16T22:17:19.3723856Z                 enforce_content_length=enforce_content_length,
2023-05-16T22:17:19.3724099Z             )
2023-05-16T22:17:19.3724267Z     
2023-05-16T22:17:19.3724548Z         # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
2023-05-16T22:17:19.3725118Z         # legitimately able to close the connection after sending a valid response.
2023-05-16T22:17:19.3725608Z         # With this behaviour, the received response is still readable.
2023-05-16T22:17:19.3725902Z         except BrokenPipeError:
2023-05-16T22:17:19.3726172Z             pass
2023-05-16T22:17:19.3726386Z         except OSError as e:
2023-05-16T22:17:19.3726593Z             # MacOS/Linux
2023-05-16T22:17:19.3726834Z             # EPROTOTYPE is needed on macOS
2023-05-16T22:17:19.3727448Z             # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
2023-05-16T22:17:19.3727977Z             if e.errno != errno.EPROTOTYPE:
2023-05-16T22:17:19.3728209Z                 raise
2023-05-16T22:17:19.3728401Z     
2023-05-16T22:17:19.3728623Z         # Reset the timeout for the recv() on the socket
2023-05-16T22:17:19.3728942Z         read_timeout = timeout_obj.read_timeout
2023-05-16T22:17:19.3729161Z     
2023-05-16T22:17:19.3729350Z         if not conn.is_closed:
2023-05-16T22:17:19.3729648Z             # In Python 3 socket.py will catch EAGAIN and return None when you
2023-05-16T22:17:19.3732804Z             # try and read into the file pointer created by http.client, which
2023-05-16T22:17:19.3733184Z             # instead raises a BadStatusLine exception. Instead of catching
2023-05-16T22:17:19.3733554Z             # the exception and assuming all BadStatusLine exceptions are read
2023-05-16T22:17:19.3733906Z             # timeouts, check for a zero timeout before making the request.
2023-05-16T22:17:19.3734201Z             if read_timeout == 0:
2023-05-16T22:17:19.3734448Z                 raise ReadTimeoutError(
2023-05-16T22:17:19.3734732Z                     self, url, f"Read timed out. (read timeout={read_timeout})"
2023-05-16T22:17:19.3734992Z                 )
2023-05-16T22:17:19.3735208Z             conn.timeout = read_timeout
2023-05-16T22:17:19.3735410Z     
2023-05-16T22:17:19.3735638Z         # Receive the response from the server
2023-05-16T22:17:19.3735875Z         try:
2023-05-16T22:17:19.3736088Z >           response = conn.getresponse()
2023-05-16T22:17:19.3736246Z 
2023-05-16T22:17:19.3736617Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connectionpool.py:536: 
2023-05-16T22:17:19.3736973Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3737130Z 
2023-05-16T22:17:19.3737555Z self = 
2023-05-16T22:17:19.3738009Z _ = False, kwargs = {}, force_reset = 
2023-05-16T22:17:19.3738200Z 
2023-05-16T22:17:19.3740998Z     def getresponse(self, _=False, **kwargs):
2023-05-16T22:17:19.3741281Z         """Retrieve the response"""
2023-05-16T22:17:19.3741603Z         # Check to see if the cassette has a response for this request. If so,
2023-05-16T22:17:19.3741873Z         # then return it
2023-05-16T22:17:19.3742162Z         if self.cassette.can_play_response_for(self._vcr_request):
2023-05-16T22:17:19.3742524Z             log.info("Playing response for {} from cassette".format(self._vcr_request))
2023-05-16T22:17:19.3742873Z             response = self.cassette.play_response(self._vcr_request)
2023-05-16T22:17:19.3743185Z             return VCRHTTPResponse(response)
2023-05-16T22:17:19.3743417Z         else:
2023-05-16T22:17:19.3743731Z             if self.cassette.write_protected and self.cassette.filter_request(self._vcr_request):
2023-05-16T22:17:19.3744127Z                 raise CannotOverwriteExistingCassetteException(
2023-05-16T22:17:19.3744507Z                     cassette=self.cassette, failed_request=self._vcr_request
2023-05-16T22:17:19.3744764Z                 )
2023-05-16T22:17:19.3744932Z     
2023-05-16T22:17:19.3745190Z             # Otherwise, we should send the request, then get the response
2023-05-16T22:17:19.3745464Z             # and return it.
2023-05-16T22:17:19.3745649Z     
2023-05-16T22:17:19.3746130Z             log.info("{} not in cassette, sending to real server".format(self._vcr_request))
2023-05-16T22:17:19.3746485Z             # This is imported here to avoid circular import.
2023-05-16T22:17:19.3749486Z             # TODO(@IvanMalison): Refactor to allow normal import.
2023-05-16T22:17:19.3749792Z             from vcr.patch import force_reset
2023-05-16T22:17:19.3750034Z     
2023-05-16T22:17:19.3750234Z             with force_reset():
2023-05-16T22:17:19.3750479Z                 self.real_connection.request(
2023-05-16T22:17:19.3750758Z                     method=self._vcr_request.method,
2023-05-16T22:17:19.3751220Z                     url=self._url(self._vcr_request.uri),
2023-05-16T22:17:19.3751495Z                     body=self._vcr_request.body,
2023-05-16T22:17:19.3751756Z                     headers=self._vcr_request.headers,
2023-05-16T22:17:19.3751991Z                 )
2023-05-16T22:17:19.3752178Z     
2023-05-16T22:17:19.3752362Z             # get the response
2023-05-16T22:17:19.3752649Z >           response = self.real_connection.getresponse()
2023-05-16T22:17:19.3752832Z 
2023-05-16T22:17:19.3752929Z vcr/stubs/__init__.py:274: 
2023-05-16T22:17:19.3753164Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3753317Z 
2023-05-16T22:17:19.3753506Z self = 
2023-05-16T22:17:19.3753720Z 
2023-05-16T22:17:19.3753846Z     def getresponse(  # type: ignore[override]
2023-05-16T22:17:19.3754079Z         self,
2023-05-16T22:17:19.3754369Z     ) -> HTTPResponse:
2023-05-16T22:17:19.3754577Z         """
2023-05-16T22:17:19.3754802Z         Get the response from the server.
2023-05-16T22:17:19.3755009Z     
2023-05-16T22:17:19.3755388Z         If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable.
2023-05-16T22:17:19.3755746Z     
2023-05-16T22:17:19.3759130Z         If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed.
2023-05-16T22:17:19.3759794Z         """
2023-05-16T22:17:19.3760051Z         # Raise the same error as http.client.HTTPConnection
2023-05-16T22:17:19.3760351Z         if self._response_options is None:
2023-05-16T22:17:19.3760614Z             raise ResponseNotReady()
2023-05-16T22:17:19.3760827Z     
2023-05-16T22:17:19.3761061Z         # Reset this attribute for being used again.
2023-05-16T22:17:19.3761346Z         resp_options = self._response_options
2023-05-16T22:17:19.3761595Z         self._response_options = None
2023-05-16T22:17:19.3761811Z     
2023-05-16T22:17:19.3762192Z         # Since the connection's timeout value may have been updated
2023-05-16T22:17:19.3762502Z         # we need to set the timeout on the socket.
2023-05-16T22:17:19.3762775Z         self.sock.settimeout(self.timeout)
2023-05-16T22:17:19.3763007Z     
2023-05-16T22:17:19.3763254Z         # This is needed here to avoid circular import errors
2023-05-16T22:17:19.3763531Z         from .response import HTTPResponse
2023-05-16T22:17:19.3763758Z     
2023-05-16T22:17:19.3764013Z         # Get the response from http.client.HTTPConnection
2023-05-16T22:17:19.3764307Z >       httplib_response = super().getresponse()
2023-05-16T22:17:19.3764476Z 
2023-05-16T22:17:19.3764777Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connection.py:454: 
2023-05-16T22:17:19.3768118Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3768293Z 
2023-05-16T22:17:19.3768594Z self = 
2023-05-16T22:17:19.3768809Z 
2023-05-16T22:17:19.3768898Z     def getresponse(self):
2023-05-16T22:17:19.3769157Z         """Get the response from the server.
2023-05-16T22:17:19.3769586Z     
2023-05-16T22:17:19.3769917Z         If the HTTPConnection is in the correct state, returns an
2023-05-16T22:17:19.3770243Z         instance of HTTPResponse or of whatever object is returned by
2023-05-16T22:17:19.3770539Z         the response_class variable.
2023-05-16T22:17:19.3770754Z     
2023-05-16T22:17:19.3770997Z         If a request has not been sent or if a previous response has
2023-05-16T22:17:19.3771331Z         not be handled, ResponseNotReady is raised.  If the HTTP
2023-05-16T22:17:19.3771674Z         response indicates that the connection should be closed, then
2023-05-16T22:17:19.3772126Z         it will be closed before the response is returned.  When the
2023-05-16T22:17:19.3772451Z         connection is closed, the underlying socket is closed.
2023-05-16T22:17:19.3772697Z         """
2023-05-16T22:17:19.3772883Z     
2023-05-16T22:17:19.3773132Z         # if a prior response has been completed, then forget about it.
2023-05-16T22:17:19.3773472Z         if self.__response and self.__response.isclosed():
2023-05-16T22:17:19.3773747Z             self.__response = None
2023-05-16T22:17:19.3773939Z     
2023-05-16T22:17:19.3774213Z         # if a prior response exists, then it must be completed (otherwise, we
2023-05-16T22:17:19.3777419Z         # cannot read this response's header to determine the connection-close
2023-05-16T22:17:19.3777735Z         # behavior)
2023-05-16T22:17:19.3777917Z         #
2023-05-16T22:17:19.3778289Z         # note: if a prior response existed, but was connection-close, then the
2023-05-16T22:17:19.3778662Z         # socket and response were made independent of this HTTPConnection
2023-05-16T22:17:19.3779007Z         # object since a new request requires that we open a whole new
2023-05-16T22:17:19.3779273Z         # connection
2023-05-16T22:17:19.3779468Z         #
2023-05-16T22:17:19.3779704Z         # this means the prior response had one of two states:
2023-05-16T22:17:19.3780041Z         #   1) will_close: this connection was reset and the prior socket and
2023-05-16T22:17:19.3780358Z         #                  response operate independently
2023-05-16T22:17:19.3780671Z         #   2) persistent: the response was retained and we await its
2023-05-16T22:17:19.3781030Z         #                  isclosed() status to become true.
2023-05-16T22:17:19.3781260Z         #
2023-05-16T22:17:19.3781503Z         if self.__state != _CS_REQ_SENT or self.__response:
2023-05-16T22:17:19.3781784Z             raise ResponseNotReady(self.__state)
2023-05-16T22:17:19.3782014Z     
2023-05-16T22:17:19.3782219Z         if self.debuglevel > 0:
2023-05-16T22:17:19.3782508Z             response = self.response_class(self.sock, self.debuglevel,
2023-05-16T22:17:19.3782816Z                                            method=self._method)
2023-05-16T22:17:19.3783092Z         else:
2023-05-16T22:17:19.3786045Z             response = self.response_class(self.sock, method=self._method)
2023-05-16T22:17:19.3786336Z     
2023-05-16T22:17:19.3786524Z         try:
2023-05-16T22:17:19.3786743Z             try:
2023-05-16T22:17:19.3786947Z >               response.begin()
2023-05-16T22:17:19.3787092Z 
2023-05-16T22:17:19.3787273Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:1373: 
2023-05-16T22:17:19.3787596Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3787752Z 
2023-05-16T22:17:19.3787906Z self = 
2023-05-16T22:17:19.3788080Z 
2023-05-16T22:17:19.3788168Z     def begin(self):
2023-05-16T22:17:19.3788399Z         if self.headers is not None:
2023-05-16T22:17:19.3788803Z             # we've already started reading the response
2023-05-16T22:17:19.3789030Z             return
2023-05-16T22:17:19.3789217Z     
2023-05-16T22:17:19.3789501Z         # read until we get a non-100 response
2023-05-16T22:17:19.3789720Z         while True:
2023-05-16T22:17:19.3789973Z             version, status, reason = self._read_status()
2023-05-16T22:17:19.3790421Z             if status != CONTINUE:
2023-05-16T22:17:19.3790629Z                 break
2023-05-16T22:17:19.3790870Z             # skip the header from the 100 response
2023-05-16T22:17:19.3791151Z             skipped_headers = _read_headers(self.fp)
2023-05-16T22:17:19.3791404Z             if self.debuglevel > 0:
2023-05-16T22:17:19.3791669Z                 print("headers:", skipped_headers)
2023-05-16T22:17:19.3791924Z             del skipped_headers
2023-05-16T22:17:19.3792130Z     
2023-05-16T22:17:19.3792333Z         self.code = self.status = status
2023-05-16T22:17:19.3792586Z         self.reason = reason.strip()
2023-05-16T22:17:19.3792954Z         if version in ("HTTP/1.0", "HTTP/0.9"):
2023-05-16T22:17:19.3796045Z             # Some servers might still return "0.9", treat it as 1.0 anyway
2023-05-16T22:17:19.3796344Z             self.version = 10
2023-05-16T22:17:19.3796607Z         elif version.startswith("HTTP/1."):
2023-05-16T22:17:19.3796931Z             self.version = 11   # use HTTP/1.1 code for HTTP/1.x where x>=1
2023-05-16T22:17:19.3797181Z         else:
2023-05-16T22:17:19.3797416Z             raise UnknownProtocol(version)
2023-05-16T22:17:19.3797642Z     
2023-05-16T22:17:19.3797873Z >       self.headers = self.msg = parse_headers(self.fp)
2023-05-16T22:17:19.3798047Z 
2023-05-16T22:17:19.3798217Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:338: 
2023-05-16T22:17:19.3798534Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3798687Z 
2023-05-16T22:17:19.3799012Z fp = <_io.BufferedReader name=-1>, _class = 
2023-05-16T22:17:19.3799223Z 
2023-05-16T22:17:19.3799356Z     def parse_headers(fp, _class=HTTPMessage):
2023-05-16T22:17:19.3799658Z         """Parses only RFC2822 headers from a file pointer.
2023-05-16T22:17:19.3799899Z     
2023-05-16T22:17:19.3800130Z         email Parser wants to see strings rather than bytes.
2023-05-16T22:17:19.3800580Z         But a TextIOWrapper around self.rfile would buffer too many bytes
2023-05-16T22:17:19.3801001Z         from the stream, bytes which we later need to read as bytes.
2023-05-16T22:17:19.3801527Z         So we read the correct bytes here, as bytes, for email Parser
2023-05-16T22:17:19.3801813Z         to parse.
2023-05-16T22:17:19.3802090Z     
2023-05-16T22:17:19.3802338Z         """
2023-05-16T22:17:19.3802579Z >       headers = _read_headers(fp)
2023-05-16T22:17:19.3802761Z 
2023-05-16T22:17:19.3805993Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:235: 
2023-05-16T22:17:19.3806433Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3806641Z 
2023-05-16T22:17:19.3806901Z fp = <_io.BufferedReader name=-1>
2023-05-16T22:17:19.3807094Z 
2023-05-16T22:17:19.3807180Z     def _read_headers(fp):
2023-05-16T22:17:19.3807600Z         """Reads potential header lines into a list from a file pointer.
2023-05-16T22:17:19.3807935Z     
2023-05-16T22:17:19.3808217Z         Length of line is limited by _MAXLINE, and number of
2023-05-16T22:17:19.3808579Z         headers is limited by _MAXHEADERS.
2023-05-16T22:17:19.3808892Z         """
2023-05-16T22:17:19.3809189Z         headers = []
2023-05-16T22:17:19.3809414Z         while True:
2023-05-16T22:17:19.3809726Z >           line = fp.readline(_MAXLINE + 1)
2023-05-16T22:17:19.3809921Z 
2023-05-16T22:17:19.3810128Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:215: 
2023-05-16T22:17:19.3810480Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3810699Z 
2023-05-16T22:17:19.3810979Z self = 
2023-05-16T22:17:19.3811315Z b = 
2023-05-16T22:17:19.3811494Z 
2023-05-16T22:17:19.3811627Z     def readinto(self, b):
2023-05-16T22:17:19.3811950Z         """Read up to len(b) bytes into the writable buffer *b* and return
2023-05-16T22:17:19.3812495Z         the number of bytes read.  If the socket is non-blocking and no bytes
2023-05-16T22:17:19.3813071Z         are available, None is returned.
2023-05-16T22:17:19.3813322Z     
2023-05-16T22:17:19.3816919Z         If *b* is non-empty, a 0 return value indicates that the connection
2023-05-16T22:17:19.3817332Z         was shutdown at the other end.
2023-05-16T22:17:19.3817728Z         """
2023-05-16T22:17:19.3817964Z         self._checkClosed()
2023-05-16T22:17:19.3818291Z         self._checkReadable()
2023-05-16T22:17:19.3818692Z         if self._timeout_occurred:
2023-05-16T22:17:19.3818997Z             raise OSError("cannot read from timed out object")
2023-05-16T22:17:19.3819573Z         while True:
2023-05-16T22:17:19.3819855Z             try:
2023-05-16T22:17:19.3820108Z >               return self._sock.recv_into(b)
2023-05-16T22:17:19.3820298Z 
2023-05-16T22:17:19.3820505Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/socket.py:589: 
2023-05-16T22:17:19.3820940Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3821134Z 
2023-05-16T22:17:19.3821536Z self = 
2023-05-16T22:17:19.3821944Z buffer = , nbytes = 8192, flags = 0
2023-05-16T22:17:19.3822150Z 
2023-05-16T22:17:19.3822326Z     def recv_into(self, buffer, nbytes=None, flags=0):
2023-05-16T22:17:19.3822654Z         self._checkClosed()
2023-05-16T22:17:19.3822948Z         if buffer and (nbytes is None):
2023-05-16T22:17:19.3823277Z             nbytes = len(buffer)
2023-05-16T22:17:19.3823574Z         elif nbytes is None:
2023-05-16T22:17:19.3823825Z             nbytes = 1024
2023-05-16T22:17:19.3824127Z         if self._sslobj is not None:
2023-05-16T22:17:19.3824473Z             if flags != 0:
2023-05-16T22:17:19.3824721Z                 raise ValueError(
2023-05-16T22:17:19.3825168Z                   "non-zero flags not allowed in calls to recv_into() on %s" %
2023-05-16T22:17:19.3828545Z                   self.__class__)
2023-05-16T22:17:19.3829028Z >           return self.read(nbytes, buffer)
2023-05-16T22:17:19.3829268Z 
2023-05-16T22:17:19.3829424Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/ssl.py:1071: 
2023-05-16T22:17:19.3829810Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3830017Z 
2023-05-16T22:17:19.3830438Z self = 
2023-05-16T22:17:19.3830913Z len = 8192, buffer = 
2023-05-16T22:17:19.3831111Z 
2023-05-16T22:17:19.3831224Z     def read(self, len=1024, buffer=None):
2023-05-16T22:17:19.3831571Z         """Read up to LEN bytes and return them.
2023-05-16T22:17:19.3831993Z         Return zero-length string on EOF."""
2023-05-16T22:17:19.3832241Z     
2023-05-16T22:17:19.3832541Z         self._checkClosed()
2023-05-16T22:17:19.3832852Z         if self._sslobj is None:
2023-05-16T22:17:19.3833236Z             raise ValueError("Read on closed or unwrapped SSL socket.")
2023-05-16T22:17:19.3833524Z         try:
2023-05-16T22:17:19.3833781Z             if buffer is not None:
2023-05-16T22:17:19.3861940Z >               return self._sslobj.read(len, buffer)
2023-05-16T22:17:19.3862291Z E               ConnectionResetError: [Errno 104] Connection reset by peer
2023-05-16T22:17:19.3862493Z 
2023-05-16T22:17:19.3862689Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/ssl.py:929: ConnectionResetError
2023-05-16T22:17:19.3862917Z 
2023-05-16T22:17:19.3863071Z During handling of the above exception, another exception occurred:
2023-05-16T22:17:19.3863284Z 
2023-05-16T22:17:19.3866744Z tmpdir = local('/tmp/pytest-of-runner/pytest-1/test_post_https_1')
2023-05-16T22:17:19.3867155Z httpbin_both = 
2023-05-16T22:17:19.3867559Z verify_pool_mgr = 
2023-05-16T22:17:19.3867779Z 
2023-05-16T22:17:19.3867912Z     def test_post(tmpdir, httpbin_both, verify_pool_mgr):
2023-05-16T22:17:19.3868442Z         """Ensure that we can post and cache the results"""
2023-05-16T22:17:19.3868743Z         data = {"key1": "value1", "key2": "value2"}
2023-05-16T22:17:19.3869004Z         url = httpbin_both.url + "/post"
2023-05-16T22:17:19.3869296Z         with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
2023-05-16T22:17:19.3869630Z >           req1 = verify_pool_mgr.request("POST", url, data).data
2023-05-16T22:17:19.3869811Z 
2023-05-16T22:17:19.3869930Z tests/integration/test_urllib3.py:92: 
2023-05-16T22:17:19.3870186Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3870788Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/_request_methods.py:119: in request
2023-05-16T22:17:19.3871179Z     method, url, fields=fields, headers=headers, **urlopen_kw
2023-05-16T22:17:19.3871695Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/_request_methods.py:217: in request_encode_body
2023-05-16T22:17:19.3872070Z     return self.urlopen(method, url, **extra_kw)
2023-05-16T22:17:19.3875880Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/poolmanager.py:433: in urlopen
2023-05-16T22:17:19.3876292Z     response = conn.urlopen(method, u.request_uri, **kw)
2023-05-16T22:17:19.3876774Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connectionpool.py:845: in urlopen
2023-05-16T22:17:19.3877171Z     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
2023-05-16T22:17:19.3877666Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/util/retry.py:470: in increment
2023-05-16T22:17:19.3878040Z     raise reraise(type(error), error, _stacktrace)
2023-05-16T22:17:19.3878483Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/util/util.py:38: in reraise
2023-05-16T22:17:19.3878816Z     raise value.with_traceback(tb)
2023-05-16T22:17:19.3879265Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connectionpool.py:802: in urlopen
2023-05-16T22:17:19.3879576Z     **response_kw,
2023-05-16T22:17:19.3880022Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connectionpool.py:536: in _make_request
2023-05-16T22:17:19.3880376Z     response = conn.getresponse()
2023-05-16T22:17:19.3880637Z vcr/stubs/__init__.py:274: in getresponse
2023-05-16T22:17:19.3880910Z     response = self.real_connection.getresponse()
2023-05-16T22:17:19.3881391Z .tox/py37-requests-urllib3-2/lib/python3.7/site-packages/urllib3/connection.py:454: in getresponse
2023-05-16T22:17:19.3887184Z     httplib_response = super().getresponse()
2023-05-16T22:17:19.3887559Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:1373: in getresponse
2023-05-16T22:17:19.3887888Z     response.begin()
2023-05-16T22:17:19.3888194Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:338: in begin
2023-05-16T22:17:19.3888531Z     self.headers = self.msg = parse_headers(self.fp)
2023-05-16T22:17:19.3888880Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:235: in parse_headers
2023-05-16T22:17:19.3889191Z     headers = _read_headers(fp)
2023-05-16T22:17:19.3889511Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/http/client.py:215: in _read_headers
2023-05-16T22:17:19.3889827Z     line = fp.readline(_MAXLINE + 1)
2023-05-16T22:17:19.3890131Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/socket.py:589: in readinto
2023-05-16T22:17:19.3890428Z     return self._sock.recv_into(b)
2023-05-16T22:17:19.3890734Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/ssl.py:1071: in recv_into
2023-05-16T22:17:19.3891031Z     return self.read(nbytes, buffer)
2023-05-16T22:17:19.3891290Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-05-16T22:17:19.3891443Z 
2023-05-16T22:17:19.3891830Z self = 
2023-05-16T22:17:19.3892191Z len = 8192, buffer = 
2023-05-16T22:17:19.3892336Z 
2023-05-16T22:17:19.3892672Z     def read(self, len=1024, buffer=None):
2023-05-16T22:17:19.3895810Z         """Read up to LEN bytes and return them.
2023-05-16T22:17:19.3896225Z         Return zero-length string on EOF."""
2023-05-16T22:17:19.3896443Z     
2023-05-16T22:17:19.3896645Z         self._checkClosed()
2023-05-16T22:17:19.3896878Z         if self._sslobj is None:
2023-05-16T22:17:19.3897154Z             raise ValueError("Read on closed or unwrapped SSL socket.")
2023-05-16T22:17:19.3897424Z         try:
2023-05-16T22:17:19.3897636Z             if buffer is not None:
2023-05-16T22:17:19.3898088Z >               return self._sslobj.read(len, buffer)
2023-05-16T22:17:19.3898628Z E               urllib3.exceptions.ProtocolError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))
2023-05-16T22:17:19.3898908Z 
2023-05-16T22:17:19.3899085Z /opt/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/ssl.py:929: ProtocolError
2023-05-16T22:17:19.3899568Z ----------------------------- Captured stderr call -----------------------------
2023-05-16T22:17:19.3899987Z 127.0.0.1 - - [16/May/2023 22:17:16] "POST /post HTTP/1.1" 501 184
2023-05-16T22:17:19.3900155Z 
2023-05-16T22:17:19.3900397Z ---------- coverage: platform linux, python 3.7.16-final-0 -----------
2023-05-16T22:17:19.3900715Z Coverage XML written to file coverage.xml
2023-05-16T22:17:19.3900877Z 
2023-05-16T22:17:19.3901014Z =========================== short test summary info ============================
2023-05-16T22:17:19.3901450Z FAILED tests/integration/test_urllib3.py::test_post[https] - urllib3.exceptio...
2023-05-16T22:17:19.3904550Z ================== 1 failed, 296 passed, 7 skipped in 12.44s ===================

I have a secret hope that @pquentin has seen something like that elswhere before.

@vEpiphyte
Copy link

Normally I would say the ConnectionResetError is probably coming from a SSL/TLS handshake issue; but the traceback shows it happens after the status line has been received? That seems very odd.

@hartwork
Copy link
Collaborator Author

Thanks a lot @hartwork, does this mean I should close my PR in favor of this one?

@shifqu would that work for you? If so, it's one of the options. Alternatively, we could drop the tox changes in yours for a green CI, treat it as one then-complete building block, get it reviewed, merge it, rebase #699 onto latest master after its merge. We can also decide later. What do you think?

@jairhenrique
Copy link
Collaborator

@hartwork sorry, I put this commit in your pr. 😞

@hartwork
Copy link
Collaborator Author

@jairhenrique a was a bit surprised also 😄 Let me drop the commit and rebase onto latest master. PS: I believe httpbin_both is a local copy of httpbin.org through pytest-httpbin rather than the flaky live site httpbin.org. So that part does probably not need to be replaced.

@hartwork hartwork force-pushed the urllib3-hartwork branch 2 times, most recently from 5ea87dc to ab3d8bf Compare May 20, 2023 14:21
@jairhenrique
Copy link
Collaborator

@hartwork I move many tests to use mockbin when httpbin goes down and tests broken. 🤔 I don't know if this lib run an local service for tests.

@hartwork
Copy link
Collaborator Author

@hartwork I move many tests to use mockbin when httpbin goes down and tests broken.

@jairhenrique if mockbin is more reliable, then that was and is an improvement.
However ideally we do not want to have the test suite talk to the internet outside of localhost at all, e.g. to be even more robust.

thinking I don't know if this lib run an local service for tests.

https://github.com/kevin1024/pytest-httpbin has the answer to that. I believe if it was used everywhere in vcrpy testing, our CI should not behave flaky any more, hopefully including aiohttp.

@hartwork
Copy link
Collaborator Author

@pquentin any chance you could have a look at the trace in #699 (comment) ? If this works with Python >=3.10 but not with earlier versions, could it be this is a bug in urllib3 2.x.x?

Here's an easy to-the-point way of getting to that very traceback locally:

cd "$(mktemp -d)"
git clone --depth 1 --branch urllib3-hartwork https://github.com/kevin1024/vcrpy
cd vcrpy/
python3.8 -m venv venv
source venv/bin/activate
pip3 install tox
tox -e py38-urllib3-2 -- 'tests/integration/test_urllib3.py::test_post[https]'

Similar for Python 3.7 or 3.9.

@pquentin
Copy link

I'll try to look at this in the next 24 hours, sorry for the silence - I was on vacation.

@hartwork
Copy link
Collaborator Author

@pquentin thank you! 👍 👍

@pquentin
Copy link

This test works fine on older versions of Python with OpenSSL 1.1.1. However, it fails with OpenSSL 3.0 and older versions of Python, because CPython does not have great support for OpenSSL 3.0. See this mail thread or this Discuss thread for more details. That said, according to python/cpython#83001 (comment), OpenSSL 3 is supported with Python 3.9+.

I'm suggesting the following:

  • Like urllib3, let's stick to Ubuntu 20.04 (and OpenSSL 1.1) for Python 3.7 and 3.8.
  • Let's switch to PyPy v7.3.12 to test with Python 3.10 (not yet available on GitHub Actions, but should be soon)
  • I'll try to understand the issue with Python 3.9 and urllib3 2.0

@hartwork
Copy link
Collaborator Author

Hi @pquentin thanks for looking into this!

One thing I have trouble understanding is this: if Python 3.7/3.8(/3.9?) does not fully support OpenSSL 3, why is our CI seemingly fine with OpenSSL 3 when using urllib3 1.x.x but not 2.x.x? Any ideas?

@hartwork
Copy link
Collaborator Author

Btw here is what I found regarding the version of OpenSSL for ubuntu-latest (22.04) with actions/setup-python:

Python ssl.OPENSSL_VERSION_INFO
CPython 3.7 (3, 0, 0, 2, 0)
CPython 3.8 (3, 0, 0, 2, 0)
CPython 3.9 (3, 0, 0, 2, 0)
CPython 3.10 (3, 0, 0, 2, 0)
CPython 3.11 (3, 0, 0, 2, 0)
pypy 3.7 (1, 1, 1, 14, 15)
pypy 3.8 (1, 1, 1, 19, 15)
pypy 3.9 (1, 1, 1, 19, 15)

Does not explain anything of what we have seen but could explain things we see later, potentially.

@hartwork
Copy link
Collaborator Author

@pquentin I downgraded CI to Ubuntu 20.04 with OpenSSL 1.1.1 now for Python 3.7/3.8/3.9 on another branch to find that:

  • for CPython environment py3{7,8,9}-requests-urllib3-2 becomes green, environment py3{7,8,9}-urllib3-2 is red with the same error.
  • no noticeable change for PyPy, both py3{7,8,9}-requests-urllib3-2 and py3{7,8,9}-urllib3-2 remain red.

@kevin1024
Copy link
Owner

Looks like I may have jumped the gun on the release of v4.3.0 - had some folks pinging me saying master was good so I released today but I think the bug may still exist on master?

@hartwork
Copy link
Collaborator Author

hartwork commented May 24, 2023

Hi @kevin1024 I would expect no damage from doing that release but the urllib3 situation remains mostly unchanged (compared to 4.2.1), yes.

shifqu and others added 6 commits May 25, 2023 15:27
Since urllib3 v2 the re-export of connection.HTTPConnection in
urllib3.connectionpool was removed.

In this commit we use urllib3.connection where needed. Some references
to connectionpool.HTTPConnection are still there for backward
compatibility.

Closes #688
In #690 a quick fix was introduced to get a green ci, this change should no longer be required.
@hartwork hartwork force-pushed the urllib3-hartwork branch from ab3d8bf to 52efffb Compare May 25, 2023 13:29
@hartwork
Copy link
Collaborator Author

I have added runtime dependency "urllib3 <2; python_version <'3.10'" now which means that everyone with moderately recent Python (>=3.10) can use either urllib3 v1 or v2 as they like while for older Python (<3.10) urllib3 v1 continues to work fine and v2 is prevented from breaking vcrpy. I think this will allow us to make an important improving step forward without making the situation worse for anyone than it is today. That change is how CI is green now across all versions of Python. What do you think?

@kevin1024
Copy link
Owner

Seems better than the current broken state. +1 from me

Copy link
Collaborator

@jairhenrique jairhenrique left a comment

Choose a reason for hiding this comment

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

LGTM.

@hartwork
Copy link
Collaborator Author

@shifqu @vEpiphyte @pquentin are there any concerns about merging this pull request as is?

@vEpiphyte
Copy link

For me it would be fine; since this is really a "stop the bleeding" problem. For people who are an older python install and want to use a newer urllib3 it is hard. I would wager much of that problem would be due to transitive requests -> urllib3 dependencies and older versions of pip which cannot resolve the dependencies appropriately; but I don't think we can account for every project which has a poor version constraint or running older and older code. This is what tagged release are for though; users with older/constrained systems can install specific versions of things. At some point there is going to be a breaking change due to this; and attempting to appease all possible configurations simply isn't something for unpaid FOSS volunteers to support.

Hopefully in the not-too-distant-future when the openssl stuff is sorted we can replace this:

"urllib3 <2; python_version <'3.10'"

with something like this:

"urllib3 >= 1.25.9<3.0.0"

That let's us shed the baggage of the object named VerifiedHTTPSConnection and move to HTTPSConnection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

vcrpy throws an error if latest urllib3 is installed
7 participants