From 3c608498c6c43fb7ee82763e1feffb74bb23fc6b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 May 2026 10:12:53 -0700 Subject: [PATCH 1/5] ci: fall back to plain build frontend for odd-arch wheels (#12647) --- .github/workflows/ci-cd.yml | 10 ++++++++-- CHANGES/12647.contrib.rst | 4 ++++ docs/spelling_wordlist.txt | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 CHANGES/12647.contrib.rst diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c7664dad86f..68a7bbd377c 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -577,6 +577,9 @@ jobs: # Build emulated architectures only if QEMU is set, # use default "auto" otherwise echo "CIBW_ARCHS_LINUX=${{ matrix.qemu }}" >> $GITHUB_ENV + # Override pyproject.toml's `build[uv]`: the pypa odd-arch + # manylinux/musllinux containers do not ship `uv` preinstalled. + echo "CIBW_BUILD_FRONTEND=build" >> $GITHUB_ENV fi shell: bash - name: Setup Python @@ -600,8 +603,11 @@ jobs: with: # `build-frontend = "build[uv]"` (pyproject.toml) requires uv to be # available on the runner for Windows and macOS. Installing - # cibuildwheel with the `uv` extra bundles uv with it; Linux - # already has uv inside the manylinux/musllinux container. + # cibuildwheel with the `uv` extra bundles uv with it; the + # tested-arch manylinux/musllinux containers also ship uv + # preinstalled. The odd-arch containers do not, so the + # `Prepare emulation` step above sets `CIBW_BUILD_FRONTEND=build` + # for those QEMU matrix cells. extras: uv env: CIBW_SKIP: pp* ${{ matrix.musl == 'musllinux' && '*manylinux*' || '*musllinux*' }} diff --git a/CHANGES/12647.contrib.rst b/CHANGES/12647.contrib.rst new file mode 100644 index 00000000000..5203ba0a395 --- /dev/null +++ b/CHANGES/12647.contrib.rst @@ -0,0 +1,4 @@ +Override ``CIBW_BUILD_FRONTEND=build`` on the QEMU-emulated odd-arch wheel +jobs so cibuildwheel falls back to plain pip, because the pypa +``manylinux``/``musllinux`` containers for those arches do not ship ``uv`` +preinstalled -- by :user:`bdraco`. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 036e33aa7b4..874efe3f93e 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -68,6 +68,7 @@ charset charsetdetect chunked chunking +cibuildwheel CIMultiDict ClientSession cls @@ -264,6 +265,7 @@ py pydantic pyenv pyflakes +pypa pyright pytest Pytest From 37cd4a8edcbfe543067a96e36f4da3e9395328a3 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 19 May 2026 18:35:58 +0100 Subject: [PATCH 2/5] Add output_size property (#12452) --- CHANGES/12452.feature.rst | 2 + aiohttp/client_reqrep.py | 54 ++++++++- docs/client_reference.rst | 24 ++++ tests/test_client_functional.py | 170 +++++++++++++++++++++++++++++ tests/test_client_proto.py | 10 ++ tests/test_client_request.py | 9 ++ tests/test_client_response.py | 187 ++++++++++++++++++++++++++++++++ tests/test_proxy.py | 31 ++++++ 8 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 CHANGES/12452.feature.rst diff --git a/CHANGES/12452.feature.rst b/CHANGES/12452.feature.rst new file mode 100644 index 00000000000..ad4936323da --- /dev/null +++ b/CHANGES/12452.feature.rst @@ -0,0 +1,2 @@ +Added :attr:`~aiohttp.ClientResponse.output_size` and +:attr:`~aiohttp.ClientResponse.upload_complete` -- by :user:`Dreamsorcerer`. diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 1b3a1197c07..310f744390d 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -212,6 +212,9 @@ class ClientResponse(HeadersMixin): _resolve_charset: Callable[["ClientResponse", bytes], str] = lambda *_: "utf-8" __writer: asyncio.Task[None] | None = None + _stream_writer: AbstractStreamWriter | None = None + _output_size: int = 0 + _upload_complete: asyncio.Future[None] | None = None def __init__( self, @@ -226,6 +229,7 @@ def __init__( session: "ClientSession | None", request_headers: CIMultiDict[str], original_url: URL, + stream_writer: AbstractStreamWriter, **kwargs: object, ) -> None: # kwargs exists so authors of subclasses should expect to pass through unknown @@ -240,7 +244,10 @@ def __init__( self._real_url = url self._url = url.with_fragment(None) if url.raw_fragment else url - if writer is not None: + if writer is None: # Request already sent + self._output_size = stream_writer.output_size + else: + self._stream_writer = stream_writer self._writer = writer if continue100 is not None: self._continue = continue100 @@ -261,6 +268,11 @@ def __init__( def __reset_writer(self, _: object = None) -> None: self.__writer = None + if self._stream_writer is not None: + self._output_size = self._stream_writer.output_size + self._stream_writer = None + if self._upload_complete is not None and not self._upload_complete.done(): + self._upload_complete.set_result(None) @property def _writer(self) -> asyncio.Task[None] | None: @@ -281,10 +293,29 @@ def _writer(self, writer: asyncio.Task[None] | None) -> None: return if writer.done(): # The writer is already done, so we can clear it immediately. - self.__writer = None + self.__reset_writer() else: writer.add_done_callback(self.__reset_writer) + @property + def output_size(self) -> int: + """Number of bytes sent for this request.""" + if self._stream_writer is not None: + return self._stream_writer.output_size + return self._output_size + + @property + def upload_complete(self) -> "asyncio.Future[None]": + """Future set when the request body has been fully sent. + + Already done when the request had no body or was written eagerly. + """ + if self._upload_complete is None: + self._upload_complete = self._loop.create_future() + if self._stream_writer is None: # upload already finished + self._upload_complete.set_result(None) + return self._upload_complete + @property def cookies(self) -> SimpleCookie: if self._cookies is None: @@ -558,6 +589,9 @@ async def _wait_released(self) -> None: def _cleanup_writer(self) -> None: if self.__writer is not None: self.__writer.cancel() + if self._stream_writer is not None: + self._output_size = self._stream_writer.output_size + self._stream_writer = None self._session = None def _notify_content(self) -> None: @@ -800,7 +834,11 @@ def _update_headers(self, headers: CIMultiDict[str]) -> None: self.headers[hdrs.HOST] = headers.pop(hdrs.HOST, host) self.headers.extend(headers) - def _create_response(self, task: asyncio.Task[None] | None) -> ClientResponse: + def _create_response( + self, + task: asyncio.Task[None] | None, + stream_writer: AbstractStreamWriter, + ) -> ClientResponse: return self.response_class( self.method, self.original_url, @@ -812,6 +850,7 @@ def _create_response(self, task: asyncio.Task[None] | None) -> ClientResponse: session=None, request_headers=self.headers, original_url=self.original_url, + stream_writer=stream_writer, ) def _create_writer(self, protocol: BaseProtocol) -> StreamWriter: @@ -885,7 +924,7 @@ async def _send(self, conn: "Connection") -> ClientResponse: protocol.start_timeout() writer.set_eof() task = None - self._response = self._create_response(task) + self._response = self._create_response(task, stream_writer=writer) return self._response async def _write_bytes( @@ -1261,7 +1300,11 @@ def _update_proxy( self.proxy = proxy self.proxy_headers = proxy_headers - def _create_response(self, task: asyncio.Task[None] | None) -> ClientResponse: + def _create_response( + self, + task: asyncio.Task[None] | None, + stream_writer: AbstractStreamWriter, + ) -> ClientResponse: return self.response_class( self.method, self.original_url, @@ -1273,6 +1316,7 @@ def _create_response(self, task: asyncio.Task[None] | None) -> ClientResponse: session=self._session, request_headers=self.headers, original_url=self.original_url, + stream_writer=stream_writer, ) def _create_writer(self, protocol: BaseProtocol) -> StreamWriter: diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 47d50df3dae..164671d4e7a 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -1538,6 +1538,30 @@ Response object .. versionadded:: 3.2 + .. attribute:: output_size + + Number of bytes sent for this request. + + Pair with :attr:`upload_complete` to display upload progress:: + + async with session.post(url, data=mpwriter) as resp: + while not resp.upload_complete.done(): + print(f"uploaded {resp.output_size} bytes") + await asyncio.sleep(0.5) + print(f"upload complete: {resp.output_size} bytes") + + .. versionadded:: 3.14 + + .. attribute:: upload_complete + + An :class:`asyncio.Future` set when the request body has been fully sent. + + Use ``await resp.upload_complete`` to block until the upload finishes, or + ``resp.upload_complete.done()`` to poll from a progress-sampling loop + (see :attr:`output_size`). + + .. versionadded:: 3.14 + .. attribute:: content_type Read-only property with *content* part of *Content-Type* header. diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index 4be1fb29308..54a1245972b 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -5746,3 +5746,173 @@ async def handler(request: web.Request) -> web.Response: data = await resp.content.read() assert resp.content.total_raw_bytes == len(data) assert resp.content.total_raw_bytes == int(resp.headers["Content-Length"]) + + +async def test_output_size_bytes(aiohttp_client: AiohttpClient) -> None: + async def handler(request: web.Request) -> web.Response: + await request.read() + return web.Response() + + app = web.Application() + app.router.add_post("/", handler) + client = await aiohttp_client(app) + + body = b"x" * 1024 + async with client.post("/", data=body) as resp: + assert resp.output_size >= len(body) + + +async def test_output_size_multipart(aiohttp_client: AiohttpClient) -> None: + async def handler(request: web.Request) -> web.Response: + await request.read() + return web.Response() + + app = web.Application() + app.router.add_post("/", handler) + client = await aiohttp_client(app) + + mpwriter = aiohttp.MultipartWriter("form-data") + mpwriter.append(b"x" * 4096) + mpwriter.append(b"y" * 2048) + expected_body_size = mpwriter.size + assert expected_body_size is not None + + async with client.post("/", data=mpwriter) as resp: + assert resp.output_size >= expected_body_size + + +async def test_output_size_keepalive_isolated( + aiohttp_client: AiohttpClient, +) -> None: + """Each request on a keep-alive connection has its own counter.""" + transports: set[object] = set() + + async def handler(request: web.Request) -> web.Response: + transports.add(request.transport) + await request.read() + return web.Response() + + app = web.Application() + app.router.add_post("/", handler) + connector = aiohttp.TCPConnector(limit=1, force_close=False) + client = await aiohttp_client(app, connector=connector) + body = b"x" * 65536 + + async with client.post("/", data=body) as resp1: + size1 = resp1.output_size + + async with client.post("/", data=body) as resp2: + size2 = resp2.output_size + + assert len(transports) == 1 # Check keep-alive worked. + assert size1 >= len(body) + assert size1 == size2 + + +async def test_output_size_progress(aiohttp_client: AiohttpClient) -> None: + """output_size advances by exactly one chunk per yield.""" + + async def handler(request: web.Request) -> web.StreamResponse: + response = web.StreamResponse() + await response.prepare(request) + # Flush headers + a chunk so resp.start() returns on the client + # side before we read the body. + await response.write(b"x") + await request.read() + return response + + app = web.Application() + app.router.add_post("/", handler) + client = await aiohttp_client(app) + + chunk_size = 4096 + chunk = b"z" * chunk_size + num_chunks = 8 + sample_taken = asyncio.Event() + next_chunk = asyncio.Event() + + async def gated_body() -> AsyncIterator[bytes]: + for _ in range(num_chunks): + yield chunk + sample_taken.clear() + next_chunk.set() + await sample_taken.wait() + + async with client.post("/", data=gated_body()) as resp: + samples: list[int] = [] + for _ in range(num_chunks): + await next_chunk.wait() + next_chunk.clear() + samples.append(resp.output_size) + assert not resp.upload_complete.done() + sample_taken.set() + await resp.upload_complete + assert resp.upload_complete.done() + await resp.read() + + # Each sample after the first reflects exactly one more chunk on the wire. + chunked_framing = len(f"{chunk_size:x}".encode()) + 4 + deltas = [samples[i] - samples[i - 1] for i in range(1, len(samples))] + assert deltas == [chunk_size + chunked_framing] * (num_chunks - 1) + + +async def test_output_size_get_request(aiohttp_client: AiohttpClient) -> None: + """GET request with no body still reports the request header byte count.""" + + async def handler(request: web.Request) -> web.Response: + return web.Response() + + app = web.Application() + app.router.add_get("/", handler) + client = await aiohttp_client(app) + + async with client.get("/") as resp: + assert resp.output_size >= 0 + + +async def test_output_size_writer_released(aiohttp_client: AiohttpClient) -> None: + """Writer is dropped once body upload completes; output_size survives.""" + + async def handler(request: web.Request) -> web.Response: + await request.read() + return web.Response() + + app = web.Application() + app.router.add_post("/", handler) + client = await aiohttp_client(app) + + body = b"x" * 1024 + async with client.post("/", data=body) as resp: + await resp.read() + assert resp._stream_writer is None + assert resp.output_size >= len(body) + + +async def test_upload_complete_no_body(aiohttp_client: AiohttpClient) -> None: + async def handler(request: web.Request) -> web.Response: + return web.Response() + + app = web.Application() + app.router.add_get("/", handler) + client = await aiohttp_client(app) + + async with client.get("/") as resp: + assert resp.upload_complete.done() + + +async def test_upload_complete_late_access(aiohttp_client: AiohttpClient) -> None: + """Accessing upload_complete after the upload finished returns a done future.""" + + async def handler(request: web.Request) -> web.Response: + await request.read() + return web.Response() + + app = web.Application() + app.router.add_post("/", handler) + client = await aiohttp_client(app) + + async with client.post("/", data=b"x" * 1024) as resp: + await resp.read() + # Writer task is done; future is created lazily on this first access. + assert resp._upload_complete is None + assert resp.upload_complete.done() diff --git a/tests/test_client_proto.py b/tests/test_client_proto.py index afabd9f86ed..3b6a7b6e8af 100644 --- a/tests/test_client_proto.py +++ b/tests/test_client_proto.py @@ -6,6 +6,7 @@ from yarl import URL from aiohttp import http +from aiohttp.abc import AbstractStreamWriter from aiohttp.client_exceptions import ClientOSError, ServerDisconnectedError from aiohttp.client_proto import ResponseHandler from aiohttp.client_reqrep import ClientResponse @@ -122,6 +123,9 @@ async def test_multiple_responses_one_byte_at_a_time() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) await response.start(conn) await response.read() == payload @@ -153,6 +157,9 @@ class PatchableHttpResponseParser(http.HttpResponseParser): session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) await response.start(conn) await response.read() == b"ab" @@ -184,6 +191,9 @@ async def test_client_protocol_readuntil_eof() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) proto.set_response_params(read_until_eof=True) await response.start(conn) diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 05a0f33d7ba..29488d85493 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -137,6 +137,9 @@ async def test_request_info(make_client_request: _RequestMaker) -> None: session=None, request_headers=req.headers, original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert resp.request_info == aiohttp.RequestInfo(url, "GET", h, url) @@ -156,6 +159,9 @@ async def test_request_info_with_fragment(make_client_request: _RequestMaker) -> session=None, request_headers=req.headers, original_url=URL("http://python.org/#urlfragment"), + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert resp.request_info == aiohttp.RequestInfo( URL("http://python.org/"), @@ -1626,6 +1632,9 @@ async def _send(self, conn: Connection) -> ClientResponse: session=self._session, request_headers=self.headers, original_url=self.original_url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) self.response = resp nonlocal called diff --git a/tests/test_client_response.py b/tests/test_client_response.py index dfc1a1f3fdb..6288cc23a37 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -14,6 +14,7 @@ import aiohttp from aiohttp import ClientSession, http +from aiohttp.abc import AbstractStreamWriter from aiohttp.client_reqrep import ClientResponse from aiohttp.connector import Connection from aiohttp.helpers import HeadersDictProxy, TimerNoop @@ -45,6 +46,9 @@ async def test_http_processing_error(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) loop.get_debug = mock.Mock() loop.get_debug.return_value = True @@ -74,6 +78,9 @@ def test_del(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) loop.get_debug = mock.Mock() loop.get_debug.return_value = True @@ -103,6 +110,9 @@ def test_close(event_loop: asyncio.AbstractEventLoop, session: ClientSession) -> session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._closed = False response._connection = mock.Mock() @@ -127,6 +137,9 @@ def test_wait_for_100_1( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert response._continue is not None response.close() @@ -147,6 +160,9 @@ def test_wait_for_100_2( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert response._continue is None response.close() @@ -165,6 +181,9 @@ def test_repr(event_loop: asyncio.AbstractEventLoop, session: ClientSession) -> session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 200 response.reason = "Ok" @@ -184,6 +203,9 @@ def test_repr_non_ascii_url() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert "" in repr(response) @@ -201,6 +223,9 @@ def test_repr_non_ascii_reason() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.reason = "\u03bb" assert "" in repr( @@ -222,6 +247,9 @@ async def test_read_and_release_connection(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -251,6 +279,9 @@ async def test_read_and_release_connection_with_error(session: ClientSession) -> session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) content = response.content = mock.Mock() content.read.return_value = loop.create_future() @@ -275,6 +306,9 @@ async def test_release(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) fut = loop.create_future() fut.set_result(b"") @@ -306,6 +340,9 @@ def run(conn: Connection) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._closed = False response._connection = conn @@ -328,6 +365,9 @@ async def test_response_eof(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._closed = False conn = response._connection = mock.Mock() @@ -351,6 +391,9 @@ async def test_response_eof_upgraded(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) conn = response._connection = mock.Mock() @@ -374,6 +417,9 @@ async def test_response_eof_after_connection_detach(session: ClientSession) -> N session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._closed = False conn = response._connection = mock.Mock() @@ -398,6 +444,9 @@ async def test_text(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -429,6 +478,9 @@ async def test_text_bad_encoding(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -464,6 +516,9 @@ async def test_text_badly_encoded_encoding_header(session: ClientSession) -> Non session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -496,6 +551,9 @@ async def test_text_custom_encoding(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -530,6 +588,9 @@ async def test_text_charset_resolver(content_type: str, session: ClientSession) session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -562,6 +623,9 @@ async def test_get_encoding_body_none(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "text/html"} @@ -591,6 +655,9 @@ async def test_text_after_read(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -622,6 +689,9 @@ async def test_json(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -653,6 +723,9 @@ async def test_json_extended_content_type(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -684,6 +757,9 @@ async def test_json_custom_content_type(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -714,6 +790,9 @@ async def test_json_custom_loader(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "application/json;charset=cp1251"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -739,6 +818,9 @@ async def test_json_invalid_content_type(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "data/octet-stream"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -765,6 +847,9 @@ async def test_json_no_content(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "application/json"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -788,6 +873,9 @@ async def test_json_override_encoding(session: ClientSession) -> None: session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -821,6 +909,9 @@ def test_get_encoding_unknown( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "application/json"} @@ -841,6 +932,9 @@ def test_raise_for_status_2xx() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 200 response.reason = "OK" @@ -860,6 +954,9 @@ def test_raise_for_status_4xx() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 409 response.reason = "CONFLICT" @@ -883,6 +980,9 @@ def test_raise_for_status_4xx_without_reason() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 404 response.reason = "" @@ -906,6 +1006,9 @@ def test_resp_host() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert "del-cl-resp.org" == response.host @@ -923,6 +1026,9 @@ def test_content_type() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "application/json;charset=cp1251"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -943,6 +1049,9 @@ def test_content_type_no_header() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._headers = HeadersDictProxy(CIMultiDict()) @@ -962,6 +1071,9 @@ def test_charset() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "application/json;charset=cp1251"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -982,6 +1094,9 @@ def test_charset_no_header() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._headers = HeadersDictProxy(CIMultiDict()) @@ -1001,6 +1116,9 @@ def test_charset_no_charset() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Type": "application/json"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1021,6 +1139,9 @@ def test_content_disposition_full() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Disposition": 'attachment; filename="archive.tar.gz"; foo=bar'} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1046,6 +1167,9 @@ def test_content_disposition_no_parameters() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Disposition": "attachment"} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1076,6 +1200,9 @@ def test_content_disposition_empty_parts(content_disposition: str) -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = {"Content-Disposition": content_disposition} response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1099,6 +1226,9 @@ def test_content_disposition_no_header() -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._headers = HeadersDictProxy(CIMultiDict()) @@ -1118,6 +1248,9 @@ def test_default_encoding_is_utf8() -> None: session=None, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._headers = HeadersDictProxy(CIMultiDict()) response._body = b"" @@ -1140,6 +1273,9 @@ def test_response_request_info() -> None: session=mock.Mock(), request_headers=headers, original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert url == response.request_info.url assert "get" == response.request_info.method @@ -1161,6 +1297,9 @@ def test_request_info_in_exception() -> None: session=mock.Mock(), request_headers=headers, original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 409 response.reason = "CONFLICT" @@ -1184,6 +1323,9 @@ def test_no_redirect_history_in_exception() -> None: session=mock.Mock(), request_headers=headers, original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 409 response.reason = "CONFLICT" @@ -1210,6 +1352,9 @@ def test_redirect_history_in_exception() -> None: session=mock.Mock(), request_headers=headers, original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 409 response.reason = "CONFLICT" @@ -1225,6 +1370,9 @@ def test_redirect_history_in_exception() -> None: session=mock.Mock(), request_headers=headers, original_url=hist_url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) hist_response._headers = HeadersDictProxy(CIMultiDict(hist_headers)) @@ -1255,6 +1403,9 @@ async def test_response_read_triggers_callback(session: ClientSession) -> None: traces=[trace], request_headers=CIMultiDict[str](), original_url=response_url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) def side_effect(*args: object, **kwargs: object) -> "asyncio.Future[bytes]": @@ -1292,6 +1443,9 @@ def test_response_cookies( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) cookies = response.cookies # Ensure the same cookies object is returned each time @@ -1313,6 +1467,9 @@ def test_response_real_url( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) assert response.url == url.with_fragment(None) assert response.real_url == url @@ -1333,6 +1490,9 @@ def test_response_links_comma_separated( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = ( ( @@ -1365,6 +1525,9 @@ def test_response_links_multiple_headers( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = ( ("Link", "; rel=next"), @@ -1392,6 +1555,9 @@ def test_response_links_no_rel( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = (("Link", ""),) response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1415,6 +1581,9 @@ def test_response_links_quoted( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = (("Link", '; rel="home-page"'),) response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1438,6 +1607,9 @@ def test_response_links_relative( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) h = (("Link", "; rel=rel"),) response._headers = HeadersDictProxy(CIMultiDict(h)) @@ -1461,6 +1633,9 @@ def test_response_links_empty( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response._headers = HeadersDictProxy(CIMultiDict()) assert response.links == {} @@ -1479,6 +1654,9 @@ def test_response_not_closed_after_get_ok(mocker: MockerFixture) -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) response.status = 400 response.reason = "Bad Request" @@ -1515,6 +1693,9 @@ def test_response_duplicate_cookie_names( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) # Set headers with duplicate cookie names but different domains @@ -1554,6 +1735,9 @@ async def test_response_raw_cookie_headers_preserved(session: ClientSession) -> session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) # Set headers with multiple cookies @@ -1611,6 +1795,9 @@ def test_response_cookies_setter_updates_raw_headers( session=session, request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) # Create a SimpleCookie with some cookies diff --git a/tests/test_proxy.py b/tests/test_proxy.py index b4edcc2790a..9cd8b3f1d6a 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -11,6 +11,7 @@ import aiohttp from aiohttp import hdrs +from aiohttp.abc import AbstractStreamWriter from aiohttp.client_reqrep import ( ClientRequest, ClientRequestArgs, @@ -251,6 +252,9 @@ async def test_proxy_server_hostname_default( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -334,6 +338,9 @@ async def test_proxy_server_hostname_override( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -424,6 +431,9 @@ def close(self) -> None: session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) fingerprint_mock = mock.Mock(spec=Fingerprint, auto_spec=True) fingerprint_mock.check.side_effect = aiohttp.ServerFingerprintMismatch( @@ -529,6 +539,9 @@ async def test_https_connect( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -611,6 +624,9 @@ async def test_https_connect_certificate_error( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -689,6 +705,9 @@ async def test_https_connect_ssl_error( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -767,6 +786,9 @@ async def test_https_connect_http_proxy_error( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -845,6 +867,9 @@ async def test_https_connect_resp_start_error( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object( @@ -961,6 +986,9 @@ async def test_https_connect_pass_ssl_context( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: @@ -1053,6 +1081,9 @@ async def test_https_auth( # type: ignore[misc] session=mock.Mock(), request_headers=CIMultiDict[str](), original_url=url, + stream_writer=mock.create_autospec( + AbstractStreamWriter, spec_set=True, instance=True + ), ) with mock.patch.object(proxy_req, "_send", autospec=True, return_value=proxy_resp): with mock.patch.object(proxy_resp, "start", autospec=True) as m: From f691258306c72cb2d3b29b9b24a9602f0d64b52b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 May 2026 13:11:15 -0700 Subject: [PATCH 3/5] ci: make the deploy job re-runnable after a partial failure (#12651) --- .github/workflows/ci-cd.yml | 26 ++++++++++++++++++++++++++ CHANGES/12651.contrib.rst | 5 +++++ docs/spelling_wordlist.txt | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 CHANGES/12651.contrib.rst diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 68a7bbd377c..5744381a85f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -658,7 +658,29 @@ jobs: - name: Collected dists run: | tree dist + - name: Check whether the GitHub Release already exists + # Allows re-running the deploy job after a partial failure (e.g. PyPI + # upload error) without the Make Release step failing with HTTP 422 + # because the tag/release was created on a prior attempt. Treat + # only the literal `release not found` reply as "does not exist"; + # other failures (auth, rate-limit, network) re-raise so the job + # fails loudly instead of falling through to Make Release. + id: gh-release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" \ + >/dev/null 2>err; then + echo 'exists=true' >> "${GITHUB_OUTPUT}" + elif grep -qx 'release not found' err; then + echo 'exists=false' >> "${GITHUB_OUTPUT}" + else + cat err >&2 + exit 1 + fi - name: Make Release + if: steps.gh-release.outputs.exists != 'true' uses: aio-libs/create-release@v1.6.6 with: changes_file: CHANGES.rst @@ -674,6 +696,10 @@ jobs: - name: >- Publish 🐍📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + with: + # Allow re-running the deploy job after a partial PyPI upload + # without failing on dists that were already published. + skip-existing: true - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.3.0 diff --git a/CHANGES/12651.contrib.rst b/CHANGES/12651.contrib.rst new file mode 100644 index 00000000000..0318ed0d084 --- /dev/null +++ b/CHANGES/12651.contrib.rst @@ -0,0 +1,5 @@ +Allowed re-running the ``deploy`` job in ``.github/workflows/ci-cd.yml`` +after a partial release failure: the ``Make Release`` step now skips +when the GitHub Release already exists, and the PyPI publish step uses +``skip-existing`` so dists that were already uploaded on a prior +attempt do not break the retry -- by :user:`bdraco`. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 874efe3f93e..f7c6ed7e395 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -109,6 +109,7 @@ Dev dict Dict Discord +dists django Django dns @@ -266,6 +267,7 @@ pydantic pyenv pyflakes pypa +PyPI pyright pytest Pytest From 35a310d58db1dd1c5a889867e69548362314620f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 May 2026 14:35:20 -0700 Subject: [PATCH 4/5] ci: build armv7l wheels on native ARM runners (#12655) --- .github/workflows/ci-cd.yml | 9 +++++++-- CHANGES/12655.contrib.rst | 4 ++++ docs/spelling_wordlist.txt | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 CHANGES/12655.contrib.rst diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 5744381a85f..9924239f018 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -546,10 +546,15 @@ jobs: - os: ubuntu-latest qemu: s390x musl: musllinux - - os: ubuntu-latest + # armv7l builds on aarch64 hosts. We still register QEMU so + # binfmt picks up the 32-bit ARM userspace handler regardless of + # whether the host kernel has CONFIG_COMPAT enabled. Even with + # emulation, aarch64-on-aarch64 hosting beats x86_64 by a wide + # margin. + - os: ubuntu-24.04-arm qemu: armv7l musl: "" - - os: ubuntu-latest + - os: ubuntu-24.04-arm qemu: armv7l musl: musllinux - os: ubuntu-latest diff --git a/CHANGES/12655.contrib.rst b/CHANGES/12655.contrib.rst new file mode 100644 index 00000000000..f5c76cd4105 --- /dev/null +++ b/CHANGES/12655.contrib.rst @@ -0,0 +1,4 @@ +Switched the armv7l wheel builds onto GitHub's hosted ARM runners. The +32-bit ARM build still runs under QEMU, but the host is now aarch64 +rather than x86_64, so the emulation overhead drops sharply +-- by :user:`bdraco`. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index f7c6ed7e395..185d9ebfdf5 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,3 +1,4 @@ +aarch abc ABI addons From 0a1efefffbaaf5a548d724274cdff97fffc61e79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 22:15:41 +0000 Subject: [PATCH 5/5] Bump yarl from 1.22.0 to 1.24.2 (#12658) [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=yarl&package-manager=pip&previous-version=1.22.0&new-version=1.24.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/base-ft.txt | 2 +- requirements/base.txt | 2 +- requirements/constraints.txt | 6 ++---- requirements/dev.txt | 6 ++---- requirements/lint.txt | 2 +- requirements/runtime-deps.txt | 2 +- requirements/test-common.txt | 2 +- requirements/test-ft.txt | 2 +- requirements/test.txt | 2 +- 9 files changed, 11 insertions(+), 15 deletions(-) diff --git a/requirements/base-ft.txt b/requirements/base-ft.txt index 51321ff12a3..9862d4ee561 100644 --- a/requirements/base-ft.txt +++ b/requirements/base-ft.txt @@ -45,5 +45,5 @@ typing-extensions==4.15.0 ; python_version < "3.13" # -r requirements/runtime-deps.in # aiosignal # multidict -yarl==1.22.0 +yarl==1.24.2 # via -r requirements/runtime-deps.in diff --git a/requirements/base.txt b/requirements/base.txt index b9ed37d6176..95bc10c2a80 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -47,5 +47,5 @@ typing-extensions==4.15.0 ; python_version < "3.13" # multidict uvloop==0.22.1 ; platform_system != "Windows" and implementation_name == "cpython" # via -r requirements/base.in -yarl==1.22.0 +yarl==1.24.2 # via -r requirements/runtime-deps.in diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 1f0f55aeff8..30a6920a758 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -32,9 +32,7 @@ async-timeout==5.0.1 ; python_version < "3.11" # aiohttp # valkey attrs==26.1.0 - # via - # aiohttp - # myst-parser + # via aiohttp babel==2.18.0 # via sphinx backports-asyncio-runner==1.2.0 @@ -334,7 +332,7 @@ wait-for-it==2.3.0 # via -r requirements/test-common.in wheel==0.47.0 # via pip-tools -yarl==1.22.0 +yarl==1.24.2 # via # -r requirements/runtime-deps.in # aiohttp diff --git a/requirements/dev.txt b/requirements/dev.txt index 2dc3b32244e..8e16376cb9c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -32,9 +32,7 @@ async-timeout==5.0.1 ; python_version < "3.11" # aiohttp # valkey attrs==26.1.0 - # via - # aiohttp - # myst-parser + # via aiohttp babel==2.18.0 # via sphinx backports-asyncio-runner==1.2.0 @@ -324,7 +322,7 @@ wait-for-it==2.3.0 # via -r requirements/test-common.in wheel==0.47.0 # via pip-tools -yarl==1.22.0 +yarl==1.24.2 # via # -r requirements/runtime-deps.in # aiohttp diff --git a/requirements/lint.txt b/requirements/lint.txt index ca0b84160f7..a92a939c26d 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -167,7 +167,7 @@ valkey==6.1.1 # via -r requirements/lint.in virtualenv==21.3.3 # via pre-commit -yarl==1.23.0 +yarl==1.24.2 # via aiohttp zlib-ng==1.0.0 # via -r requirements/lint.in diff --git a/requirements/runtime-deps.txt b/requirements/runtime-deps.txt index 4ab91518028..0358ece8113 100644 --- a/requirements/runtime-deps.txt +++ b/requirements/runtime-deps.txt @@ -41,5 +41,5 @@ typing-extensions==4.15.0 ; python_version < "3.13" # -r requirements/runtime-deps.in # aiosignal # multidict -yarl==1.22.0 +yarl==1.24.2 # via -r requirements/runtime-deps.in diff --git a/requirements/test-common.txt b/requirements/test-common.txt index f21137a4ff5..155056a7b1d 100644 --- a/requirements/test-common.txt +++ b/requirements/test-common.txt @@ -149,7 +149,7 @@ typing-inspection==0.4.2 # via pydantic wait-for-it==2.3.0 # via -r requirements/test-common.in -yarl==1.23.0 +yarl==1.24.2 # via aiohttp zlib-ng==1.0.0 # via -r requirements/test-common.in diff --git a/requirements/test-ft.txt b/requirements/test-ft.txt index bd5a648e30c..163347a13eb 100644 --- a/requirements/test-ft.txt +++ b/requirements/test-ft.txt @@ -173,7 +173,7 @@ typing-inspection==0.4.2 # via pydantic wait-for-it==2.3.0 # via -r requirements/test-common.in -yarl==1.22.0 +yarl==1.24.2 # via # -r requirements/runtime-deps.in # aiohttp diff --git a/requirements/test.txt b/requirements/test.txt index ea891dcfb47..9b53d06e504 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -175,7 +175,7 @@ uvloop==0.22.1 ; platform_system != "Windows" and implementation_name == "cpytho # via -r requirements/base.in wait-for-it==2.3.0 # via -r requirements/test-common.in -yarl==1.22.0 +yarl==1.24.2 # via # -r requirements/runtime-deps.in # aiohttp