Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-httpcore for openSUSE:Factory checked in at 2022-03-02 18:20:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-httpcore (Old) and /work/SRC/openSUSE:Factory/.python-httpcore.new.1958 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-httpcore" Wed Mar 2 18:20:19 2022 rev:5 rq:958238 version:0.14.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python-httpcore/python-httpcore.changes 2022-02-14 22:35:57.141379478 +0100 +++ /work/SRC/openSUSE:Factory/.python-httpcore.new.1958/python-httpcore.changes 2022-03-02 18:20:29.484654648 +0100 @@ -1,0 +2,9 @@ +Mon Feb 21 10:54:40 UTC 2022 - Michael Str??der <mich...@stroeder.com> + +- update to 0.14.7: + * Requests which raise a PoolTimeout need to be removed from the pool queue. + * Fix AttributeError that happened when Socks5Connection were terminated. + * Fix SOCKS support for `http://` URLs. + * Resolve race condition around exceptions during streaming a response. + +------------------------------------------------------------------- Old: ---- httpcore-0.14.5.tar.gz New: ---- httpcore-0.14.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-httpcore.spec ++++++ --- /var/tmp/diff_new_pack.M5LSLd/_old 2022-03-02 18:20:31.184654708 +0100 +++ /var/tmp/diff_new_pack.M5LSLd/_new 2022-03-02 18:20:31.188654708 +0100 @@ -27,7 +27,7 @@ %endif %define skip_python2 1 Name: python-httpcore%{psuffix} -Version: 0.14.5 +Version: 0.14.7 Release: 0 Summary: Minimal low-level Python HTTP client License: BSD-3-Clause ++++++ httpcore-0.14.5.tar.gz -> httpcore-0.14.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/.github/stale.yml new/httpcore-0.14.7/.github/stale.yml --- old/httpcore-0.14.5/.github/stale.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/httpcore-0.14.7/.github/stale.yml 2022-02-04 13:16:06.000000000 +0100 @@ -0,0 +1,11 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 30 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/.github/workflows/publish.yml new/httpcore-0.14.7/.github/workflows/publish.yml --- old/httpcore-0.14.5/.github/workflows/publish.yml 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/.github/workflows/publish.yml 2022-02-04 13:16:06.000000000 +0100 @@ -1,4 +1,3 @@ ---- name: Publish on: @@ -11,6 +10,9 @@ name: "Publish release" runs-on: "ubuntu-latest" + environment: + name: deploy + steps: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/CHANGELOG.md new/httpcore-0.14.7/CHANGELOG.md --- old/httpcore-0.14.5/CHANGELOG.md 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/CHANGELOG.md 2022-02-04 13:16:06.000000000 +0100 @@ -4,6 +4,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 0.14.7 (February 4th, 2022) + +- Requests which raise a PoolTimeout need to be removed from the pool queue. (#502) +- Fix AttributeError that happened when Socks5Connection were terminated. (#501) + +## 0.14.6 (February 1st, 2022) + +- Fix SOCKS support for `http://` URLs. (#492) +- Resolve race condition around exceptions during streaming a response. (#491) + ## 0.14.5 (January 18th, 2022) - SOCKS proxy support. (#478) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/README.md new/httpcore-0.14.7/README.md --- old/httpcore-0.14.5/README.md 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/README.md 2022-02-04 13:16:06.000000000 +0100 @@ -18,25 +18,31 @@ * Sending HTTP requests. * Thread-safe / task-safe connection pooling. -* HTTP(S) proxy support. +* HTTP(S) proxy & SOCKS proxy support. * Supports HTTP/1.1 and HTTP/2. * Provides both sync and async interfaces. * Async backend support for `asyncio` and `trio`. ## Installation -For HTTP/1.1 only support, install with... +For HTTP/1.1 only support, install with: ```shell $ pip install httpcore ``` -For HTTP/1.1 and HTTP/2 support, install with... +For HTTP/1.1 and HTTP/2 support, install with: ```shell $ pip install httpcore[http2] ``` +For SOCKS proxy support, install with: + +```shell +$ pip install httpcore[socks] +``` + # Sending requests Send an HTTP request: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/docs/proxies.md new/httpcore-0.14.7/docs/proxies.md --- old/httpcore-0.14.5/docs/proxies.md 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/docs/proxies.md 2022-02-04 13:16:06.000000000 +0100 @@ -33,7 +33,7 @@ # A `Proxy-Authorization` header will be included on the initial proxy connection. proxy = httpcore.HTTPProxy( proxy_url="http://127.0.0.1:8080/", - proxy_auth=("<username", "password") + proxy_auth=("<username>", "<password>") ) ``` @@ -58,7 +58,7 @@ ## SOCKS proxy support -The `httpcore` package also supports proxies using the SOCKS protocol. +The `httpcore` package also supports proxies using the SOCKS5 protocol. Make sure to install the optional dependancy using `pip install httpcore[socks]`. @@ -68,7 +68,7 @@ import httpcore # Note that the SOCKS port is 1080. -proxy = httpcore.SOCKSProxy(proxy_url="socks://127.0.0.1:1080/") +proxy = httpcore.SOCKSProxy(proxy_url="socks5://127.0.0.1:1080/") r = proxy.request("GET", "https://www.example.com/") ``` @@ -78,8 +78,8 @@ import httpcore proxy = httpcore.SOCKSProxy( - proxy_url="socks://127.0.0.1:8080/", - proxy_auth=("<username", "password") + proxy_url="socks5://127.0.0.1:8080/", + proxy_auth=("<username>", "<password>") ) r = proxy.request("GET", "https://www.example.com/") ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/__init__.py new/httpcore-0.14.7/httpcore/__init__.py --- old/httpcore-0.14.5/httpcore/__init__.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/__init__.py 2022-02-04 13:16:06.000000000 +0100 @@ -82,7 +82,7 @@ "WriteError", ] -__version__ = "0.14.5" +__version__ = "0.14.7" __locals = locals() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_async/connection_pool.py new/httpcore-0.14.7/httpcore/_async/connection_pool.py --- old/httpcore-0.14.5/httpcore/_async/connection_pool.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_async/connection_pool.py 2022-02-04 13:16:06.000000000 +0100 @@ -223,7 +223,16 @@ while True: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("pool", None) - connection = await status.wait_for_connection(timeout=timeout) + try: + connection = await status.wait_for_connection(timeout=timeout) + except BaseException as exc: + # If we timeout here, or if the task is cancelled, then make + # sure to remove the request from the queue before bubbling + # up the exception. + async with self._pool_lock: + self._requests.remove(status) + raise exc + try: response = await connection.handle_async_request(request) except ConnectionNotAvailable: @@ -239,7 +248,7 @@ # status so that the request becomes queued again. status.unset_connection() await self._attempt_to_acquire_connection(status) - except Exception as exc: + except BaseException as exc: await self.response_closed(status) raise exc else: @@ -267,7 +276,8 @@ async with self._pool_lock: # Update the state of the connection pool. - self._requests.remove(status) + if status in self._requests: + self._requests.remove(status) if connection.is_closed() and connection in self._pool: self._pool.remove(connection) @@ -291,11 +301,19 @@ Close any connections in the pool. """ async with self._pool_lock: + requests_still_in_flight = len(self._requests) + for connection in self._pool: await connection.aclose() self._pool = [] self._requests = [] + if requests_still_in_flight: + raise RuntimeError( + f"The connection pool was closed while {requests_still_in_flight} " + f"HTTP requests/responses were still in-flight." + ) + async def __aenter__(self) -> "AsyncConnectionPool": return self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_async/http11.py new/httpcore-0.14.7/httpcore/_async/http11.py --- old/httpcore-0.14.5/httpcore/_async/http11.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_async/http11.py 2022-02-04 13:16:06.000000000 +0100 @@ -279,13 +279,23 @@ def __init__(self, connection: AsyncHTTP11Connection, request: Request) -> None: self._connection = connection self._request = request + self._closed = False async def __aiter__(self) -> AsyncIterator[bytes]: kwargs = {"request": self._request} - async with Trace("http11.receive_response_body", self._request, kwargs): - async for chunk in self._connection._receive_response_body(**kwargs): - yield chunk + try: + async with Trace("http11.receive_response_body", self._request, kwargs): + async for chunk in self._connection._receive_response_body(**kwargs): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + await self.aclose() + raise exc async def aclose(self) -> None: - async with Trace("http11.response_closed", self._request): - await self._connection._response_closed() + if not self._closed: + self._closed = True + async with Trace("http11.response_closed", self._request): + await self._connection._response_closed() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_async/http2.py new/httpcore-0.14.7/httpcore/_async/http2.py --- old/httpcore-0.14.5/httpcore/_async/http2.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_async/http2.py 2022-02-04 13:16:06.000000000 +0100 @@ -170,7 +170,7 @@ ] self._h2_state.initiate_connection() - self._h2_state.increment_flow_control_window(2 ** 24) + self._h2_state.increment_flow_control_window(2**24) await self._write_outgoing_data(request) # Sending the request... @@ -200,7 +200,7 @@ ] self._h2_state.send_headers(stream_id, headers, end_stream=end_stream) - self._h2_state.increment_flow_control_window(2 ** 24, stream_id=stream_id) + self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id) await self._write_outgoing_data(request) async def _send_request_body(self, request: Request, stream_id: int) -> None: @@ -305,7 +305,6 @@ async def aclose(self) -> None: # Note that this method unilaterally closes the connection, and does # not have any kind of locking in place around it. - # For task-safe/thread-safe operations call into 'attempt_close' instead. self._h2_state.close_connection() self._state = HTTPConnectionState.CLOSED await self._network_stream.aclose() @@ -446,16 +445,26 @@ self._connection = connection self._request = request self._stream_id = stream_id + self._closed = False async def __aiter__(self) -> typing.AsyncIterator[bytes]: kwargs = {"request": self._request, "stream_id": self._stream_id} - async with Trace("http2.receive_response_body", self._request, kwargs): - async for chunk in self._connection._receive_response_body( - request=self._request, stream_id=self._stream_id - ): - yield chunk + try: + async with Trace("http2.receive_response_body", self._request, kwargs): + async for chunk in self._connection._receive_response_body( + request=self._request, stream_id=self._stream_id + ): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + await self.aclose() + raise exc async def aclose(self) -> None: - kwargs = {"stream_id": self._stream_id} - async with Trace("http2.response_closed", self._request, kwargs): - await self._connection._response_closed(stream_id=self._stream_id) + if not self._closed: + self._closed = True + kwargs = {"stream_id": self._stream_id} + async with Trace("http2.response_closed", self._request, kwargs): + await self._connection._response_closed(stream_id=self._stream_id) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_async/socks_proxy.py new/httpcore-0.14.7/httpcore/_async/socks_proxy.py --- old/httpcore-0.14.5/httpcore/_async/socks_proxy.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_async/socks_proxy.py 2022-02-04 13:16:06.000000000 +0100 @@ -206,7 +206,7 @@ ) self._connect_lock = AsyncLock() self._connection: typing.Optional[AsyncConnectionInterface] = None - self._connection_failed = False + self._connect_failed = False async def handle_async_request(self, request: Request) -> Response: timeouts = request.extensions.get("timeout", {}) @@ -239,22 +239,27 @@ trace.return_value = stream # Upgrade the stream to SSL - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) - alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] - ssl_context.set_alpn_protocols(alpn_protocols) - - kwargs = { - "ssl_context": ssl_context, - "server_hostname": self._remote_origin.host.decode("ascii"), - "timeout": timeout, - } - async with Trace("connection.start_tls", request, kwargs) as trace: - stream = await stream.start_tls(**kwargs) - trace.return_value = stream + if self._remote_origin.scheme == b"https": + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ( + ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ) + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace( + "connection.start_tls", request, kwargs + ) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream # Determine if we should be using HTTP/1.1 or HTTP/2 ssl_object = stream.get_extra_info("ssl_object") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_sync/connection_pool.py new/httpcore-0.14.7/httpcore/_sync/connection_pool.py --- old/httpcore-0.14.5/httpcore/_sync/connection_pool.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_sync/connection_pool.py 2022-02-04 13:16:06.000000000 +0100 @@ -223,7 +223,16 @@ while True: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("pool", None) - connection = status.wait_for_connection(timeout=timeout) + try: + connection = status.wait_for_connection(timeout=timeout) + except BaseException as exc: + # If we timeout here, or if the task is cancelled, then make + # sure to remove the request from the queue before bubbling + # up the exception. + with self._pool_lock: + self._requests.remove(status) + raise exc + try: response = connection.handle_request(request) except ConnectionNotAvailable: @@ -239,7 +248,7 @@ # status so that the request becomes queued again. status.unset_connection() self._attempt_to_acquire_connection(status) - except Exception as exc: + except BaseException as exc: self.response_closed(status) raise exc else: @@ -267,7 +276,8 @@ with self._pool_lock: # Update the state of the connection pool. - self._requests.remove(status) + if status in self._requests: + self._requests.remove(status) if connection.is_closed() and connection in self._pool: self._pool.remove(connection) @@ -291,11 +301,19 @@ Close any connections in the pool. """ with self._pool_lock: + requests_still_in_flight = len(self._requests) + for connection in self._pool: connection.close() self._pool = [] self._requests = [] + if requests_still_in_flight: + raise RuntimeError( + f"The connection pool was closed while {requests_still_in_flight} " + f"HTTP requests/responses were still in-flight." + ) + def __enter__(self) -> "ConnectionPool": return self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_sync/http11.py new/httpcore-0.14.7/httpcore/_sync/http11.py --- old/httpcore-0.14.5/httpcore/_sync/http11.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_sync/http11.py 2022-02-04 13:16:06.000000000 +0100 @@ -279,13 +279,23 @@ def __init__(self, connection: HTTP11Connection, request: Request) -> None: self._connection = connection self._request = request + self._closed = False def __iter__(self) -> Iterator[bytes]: kwargs = {"request": self._request} - with Trace("http11.receive_response_body", self._request, kwargs): - for chunk in self._connection._receive_response_body(**kwargs): - yield chunk + try: + with Trace("http11.receive_response_body", self._request, kwargs): + for chunk in self._connection._receive_response_body(**kwargs): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + self.close() + raise exc def close(self) -> None: - with Trace("http11.response_closed", self._request): - self._connection._response_closed() + if not self._closed: + self._closed = True + with Trace("http11.response_closed", self._request): + self._connection._response_closed() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_sync/http2.py new/httpcore-0.14.7/httpcore/_sync/http2.py --- old/httpcore-0.14.5/httpcore/_sync/http2.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_sync/http2.py 2022-02-04 13:16:06.000000000 +0100 @@ -170,7 +170,7 @@ ] self._h2_state.initiate_connection() - self._h2_state.increment_flow_control_window(2 ** 24) + self._h2_state.increment_flow_control_window(2**24) self._write_outgoing_data(request) # Sending the request... @@ -200,7 +200,7 @@ ] self._h2_state.send_headers(stream_id, headers, end_stream=end_stream) - self._h2_state.increment_flow_control_window(2 ** 24, stream_id=stream_id) + self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id) self._write_outgoing_data(request) def _send_request_body(self, request: Request, stream_id: int) -> None: @@ -305,7 +305,6 @@ def close(self) -> None: # Note that this method unilaterally closes the connection, and does # not have any kind of locking in place around it. - # For task-safe/thread-safe operations call into 'attempt_close' instead. self._h2_state.close_connection() self._state = HTTPConnectionState.CLOSED self._network_stream.close() @@ -446,16 +445,26 @@ self._connection = connection self._request = request self._stream_id = stream_id + self._closed = False def __iter__(self) -> typing.Iterator[bytes]: kwargs = {"request": self._request, "stream_id": self._stream_id} - with Trace("http2.receive_response_body", self._request, kwargs): - for chunk in self._connection._receive_response_body( - request=self._request, stream_id=self._stream_id - ): - yield chunk + try: + with Trace("http2.receive_response_body", self._request, kwargs): + for chunk in self._connection._receive_response_body( + request=self._request, stream_id=self._stream_id + ): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + self.close() + raise exc def close(self) -> None: - kwargs = {"stream_id": self._stream_id} - with Trace("http2.response_closed", self._request, kwargs): - self._connection._response_closed(stream_id=self._stream_id) + if not self._closed: + self._closed = True + kwargs = {"stream_id": self._stream_id} + with Trace("http2.response_closed", self._request, kwargs): + self._connection._response_closed(stream_id=self._stream_id) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/httpcore/_sync/socks_proxy.py new/httpcore-0.14.7/httpcore/_sync/socks_proxy.py --- old/httpcore-0.14.5/httpcore/_sync/socks_proxy.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/httpcore/_sync/socks_proxy.py 2022-02-04 13:16:06.000000000 +0100 @@ -206,7 +206,7 @@ ) self._connect_lock = Lock() self._connection: typing.Optional[ConnectionInterface] = None - self._connection_failed = False + self._connect_failed = False def handle_request(self, request: Request) -> Response: timeouts = request.extensions.get("timeout", {}) @@ -239,22 +239,27 @@ trace.return_value = stream # Upgrade the stream to SSL - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) - alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] - ssl_context.set_alpn_protocols(alpn_protocols) - - kwargs = { - "ssl_context": ssl_context, - "server_hostname": self._remote_origin.host.decode("ascii"), - "timeout": timeout, - } - with Trace("connection.start_tls", request, kwargs) as trace: - stream = stream.start_tls(**kwargs) - trace.return_value = stream + if self._remote_origin.scheme == b"https": + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ( + ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ) + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace( + "connection.start_tls", request, kwargs + ) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream # Determine if we should be using HTTP/1.1 or HTTP/2 ssl_object = stream.get_extra_info("ssl_object") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/requirements.txt new/httpcore-0.14.7/requirements.txt --- old/httpcore-0.14.5/requirements.txt 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/requirements.txt 2022-02-04 13:16:06.000000000 +0100 @@ -6,22 +6,22 @@ # Docs mkdocs==1.2.3 mkdocs-autorefs==0.3.1 -mkdocs-material==8.1.4 +mkdocs-material==8.1.9 mkdocs-material-extensions==1.0.3 mkdocstrings==0.17.0 # Packaging twine==3.7.1 -wheel==0.37.0 +wheel==0.37.1 # Tests & Linting -anyio==3.4.0 +anyio==3.5.0 autoflake==1.4 -black==21.12b0 +black==22.1.0 coverage==6.2 flake8==4.0.1 isort==5.10.1 -mypy==0.930 +mypy==0.931 pytest==6.2.5 pytest-httpbin==1.0.1 pytest-trio==0.7.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/tests/_async/test_connection_pool.py new/httpcore-0.14.7/tests/_async/test_connection_pool.py --- old/httpcore-0.14.5/tests/_async/test_connection_pool.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/tests/_async/test_connection_pool.py 2022-02-04 13:16:06.000000000 +0100 @@ -3,7 +3,7 @@ import pytest import trio as concurrency -from httpcore import AsyncConnectionPool, ConnectError, UnsupportedProtocol +from httpcore import AsyncConnectionPool, ConnectError, PoolTimeout, UnsupportedProtocol from httpcore.backends.mock import AsyncMockBackend @@ -435,3 +435,56 @@ with pytest.raises(UnsupportedProtocol): await pool.request("GET", "://www.example.com/") + + +@pytest.mark.anyio +async def test_connection_pool_closed_while_request_in_flight(): + """ + Closing a connection pool while a request/response is still in-flight + should raise an error. + """ + network_backend = AsyncMockBackend( + [ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, world!", + ] + ) + + async with AsyncConnectionPool( + network_backend=network_backend, + ) as pool: + # Send a request, and then close the connection pool while the + # response has not yet been streamed. + async with pool.stream("GET", "https://example.com/"): + with pytest.raises(RuntimeError): + await pool.aclose() + + +@pytest.mark.anyio +async def test_connection_pool_timeout(): + """ + Ensure that exceeding max_connections can cause a request to timeout. + """ + network_backend = AsyncMockBackend( + [ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, world!", + ] + ) + + async with AsyncConnectionPool( + network_backend=network_backend, max_connections=1 + ) as pool: + # Send a request to a pool that is configured to only support a single + # connection, and then ensure that a second concurrent request + # fails with a timeout. + async with pool.stream("GET", "https://example.com/"): + with pytest.raises(PoolTimeout): + extensions = {"timeout": {"pool": 0.0001}} + await pool.request("GET", "https://example.com/", extensions=extensions) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/tests/_async/test_http11.py new/httpcore-0.14.7/tests/_async/test_http11.py --- old/httpcore-0.14.5/tests/_async/test_http11.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/tests/_async/test_http11.py 2022-02-04 13:16:06.000000000 +0100 @@ -92,6 +92,35 @@ @pytest.mark.anyio +async def test_http11_connection_with_incomplete_response(): + """ + We should be gracefully handling the case where the connection ends prematurely. + """ + origin = Origin(b"https", b"example.com", 443) + stream = AsyncMockStream( + [ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, wor", + ] + ) + async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn: + with pytest.raises(RemoteProtocolError): + await conn.request("GET", "https://example.com/") + + assert not conn.is_idle() + assert conn.is_closed() + assert not conn.is_available() + assert not conn.has_expired() + assert ( + repr(conn) + == "<AsyncHTTP11Connection ['https://example.com:443', CLOSED, Request Count: 1]>" + ) + + +@pytest.mark.anyio async def test_http11_connection_with_local_protocol_error(): """ If a local protocol error occurs, then no response will be returned, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/tests/_sync/test_connection_pool.py new/httpcore-0.14.7/tests/_sync/test_connection_pool.py --- old/httpcore-0.14.5/tests/_sync/test_connection_pool.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/tests/_sync/test_connection_pool.py 2022-02-04 13:16:06.000000000 +0100 @@ -3,7 +3,7 @@ import pytest from tests import concurrency -from httpcore import ConnectionPool, ConnectError, UnsupportedProtocol +from httpcore import ConnectionPool, ConnectError, PoolTimeout, UnsupportedProtocol from httpcore.backends.mock import MockBackend @@ -435,3 +435,56 @@ with pytest.raises(UnsupportedProtocol): pool.request("GET", "://www.example.com/") + + + +def test_connection_pool_closed_while_request_in_flight(): + """ + Closing a connection pool while a request/response is still in-flight + should raise an error. + """ + network_backend = MockBackend( + [ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, world!", + ] + ) + + with ConnectionPool( + network_backend=network_backend, + ) as pool: + # Send a request, and then close the connection pool while the + # response has not yet been streamed. + with pool.stream("GET", "https://example.com/"): + with pytest.raises(RuntimeError): + pool.close() + + + +def test_connection_pool_timeout(): + """ + Ensure that exceeding max_connections can cause a request to timeout. + """ + network_backend = MockBackend( + [ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, world!", + ] + ) + + with ConnectionPool( + network_backend=network_backend, max_connections=1 + ) as pool: + # Send a request to a pool that is configured to only support a single + # connection, and then ensure that a second concurrent request + # fails with a timeout. + with pool.stream("GET", "https://example.com/"): + with pytest.raises(PoolTimeout): + extensions = {"timeout": {"pool": 0.0001}} + pool.request("GET", "https://example.com/", extensions=extensions) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/httpcore-0.14.5/tests/_sync/test_http11.py new/httpcore-0.14.7/tests/_sync/test_http11.py --- old/httpcore-0.14.5/tests/_sync/test_http11.py 2022-01-18 12:44:32.000000000 +0100 +++ new/httpcore-0.14.7/tests/_sync/test_http11.py 2022-02-04 13:16:06.000000000 +0100 @@ -92,6 +92,35 @@ +def test_http11_connection_with_incomplete_response(): + """ + We should be gracefully handling the case where the connection ends prematurely. + """ + origin = Origin(b"https", b"example.com", 443) + stream = MockStream( + [ + b"HTTP/1.1 200 OK\r\n", + b"Content-Type: plain/text\r\n", + b"Content-Length: 13\r\n", + b"\r\n", + b"Hello, wor", + ] + ) + with HTTP11Connection(origin=origin, stream=stream) as conn: + with pytest.raises(RemoteProtocolError): + conn.request("GET", "https://example.com/") + + assert not conn.is_idle() + assert conn.is_closed() + assert not conn.is_available() + assert not conn.has_expired() + assert ( + repr(conn) + == "<HTTP11Connection ['https://example.com:443', CLOSED, Request Count: 1]>" + ) + + + def test_http11_connection_with_local_protocol_error(): """ If a local protocol error occurs, then no response will be returned,