From a47aae22025cdd88799a3688b469ac98296af160 Mon Sep 17 00:00:00 2001 From: vyrnsynx <153433026+vyrnsynx@users.noreply.github.com> Date: Thu, 3 Sep 2026 06:57:51 +0000 Subject: [PATCH 1/3] Use anyio.from_thread.BlockingPortal in TestClient anyio 4.15 deprecated anyio.abc.BlockingPortal. The runtime type alias in testclient.py evaluated that name on import. Fixes #3497 --- starlette/testclient.py | 6 +++--- tests/test_testclient.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/starlette/testclient.py b/starlette/testclient.py index 449fc5fcb..ffc613a28 100644 --- a/starlette/testclient.py +++ b/starlette/testclient.py @@ -50,7 +50,7 @@ stacklevel=2, ) -_PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]] +_PortalFactoryType = Callable[[], AbstractContextManager[anyio.from_thread.BlockingPortal]] ASGIInstance = Callable[[Receive, Send], Awaitable[None]] ASGI2App = Callable[[Scope], ASGIInstance] @@ -371,7 +371,7 @@ async def send(message: Message) -> None: class TestClient(httpx.Client): __test__ = False task: Future[None] - portal: anyio.abc.BlockingPortal | None = None + portal: anyio.from_thread.BlockingPortal | None = None def __init__( self, @@ -414,7 +414,7 @@ def __init__( ) @contextlib.contextmanager - def _portal_factory(self) -> Generator[anyio.abc.BlockingPortal, None, None]: + def _portal_factory(self) -> Generator[anyio.from_thread.BlockingPortal, None, None]: if self.portal is not None: yield self.portal else: diff --git a/tests/test_testclient.py b/tests/test_testclient.py index ff67b56eb..417849b66 100644 --- a/tests/test_testclient.py +++ b/tests/test_testclient.py @@ -1,7 +1,9 @@ from __future__ import annotations +import importlib import itertools import sys +import warnings from asyncio import Task, current_task as asyncio_current_task from collections.abc import AsyncGenerator from contextlib import asynccontextmanager @@ -490,3 +492,14 @@ def test_timeout_deprecation() -> None: ): client = TestClient(mock_service) client.get("/", timeout=1) + + +def test_importing_testclient_does_not_use_deprecated_blockingportal_alias() -> None: + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter("always", DeprecationWarning) + importlib.reload(sys.modules["starlette.testclient"]) + + assert not any( + issubclass(item.category, DeprecationWarning) and "BlockingPortal" in str(item.message) + for item in recorded + ) From 394d27cdb7026d97d2637c2657d8f06da4db579d Mon Sep 17 00:00:00 2001 From: vyrnsynx <153433026+vyrnsynx@users.noreply.github.com> Date: Thu, 3 Sep 2026 07:01:42 +0000 Subject: [PATCH 2/3] Format test assertion to satisfy ruff --- tests/test_testclient.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_testclient.py b/tests/test_testclient.py index 417849b66..88cac29a5 100644 --- a/tests/test_testclient.py +++ b/tests/test_testclient.py @@ -500,6 +500,5 @@ def test_importing_testclient_does_not_use_deprecated_blockingportal_alias() -> importlib.reload(sys.modules["starlette.testclient"]) assert not any( - issubclass(item.category, DeprecationWarning) and "BlockingPortal" in str(item.message) - for item in recorded + issubclass(item.category, DeprecationWarning) and "BlockingPortal" in str(item.message) for item in recorded ) From d6d12d206ba5b984e354e9c5b695c811e9812d48 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 5 Sep 2026 09:02:07 +0200 Subject: [PATCH 3/3] Remove redundant BlockingPortal import test --- tests/test_testclient.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/test_testclient.py b/tests/test_testclient.py index 88cac29a5..ff67b56eb 100644 --- a/tests/test_testclient.py +++ b/tests/test_testclient.py @@ -1,9 +1,7 @@ from __future__ import annotations -import importlib import itertools import sys -import warnings from asyncio import Task, current_task as asyncio_current_task from collections.abc import AsyncGenerator from contextlib import asynccontextmanager @@ -492,13 +490,3 @@ def test_timeout_deprecation() -> None: ): client = TestClient(mock_service) client.get("/", timeout=1) - - -def test_importing_testclient_does_not_use_deprecated_blockingportal_alias() -> None: - with warnings.catch_warnings(record=True) as recorded: - warnings.simplefilter("always", DeprecationWarning) - importlib.reload(sys.modules["starlette.testclient"]) - - assert not any( - issubclass(item.category, DeprecationWarning) and "BlockingPortal" in str(item.message) for item in recorded - )