Skip to content

Commit 52a79c4

Browse files
committed
backport: coverage — reset sse-starlette's module-global exit Event between tests
sse-starlette <3.0 stores an anyio.Event on AppStatus the first time an EventSourceResponse runs, bound to that test's event loop. Under the in-process bridge (one process, per-test event loops) every subsequent SSE response — both the [sse] leg and connect_with_oauth's streamable-HTTP responses — fails with 'bound to a different event loop'. v1's own transport tests run uvicorn in a subprocess and so never share a process across event loops. The previous commit's >=2.1.0 dev floor was insufficient (2.1.0 still has the class attribute; 3.x switched to a ContextVar). An autouse fixture that resets the attribute after each test handles all versions including the 1.6.1 runtime floor, so the dev floor is reverted and lowest-direct CI again exercises the runtime constraint. Verified: 1598 tests / 100.00% coverage on both highest and lowest-direct.
1 parent 04980e3 commit 52a79c4

3 files changed

Lines changed: 19 additions & 7 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@ dev = [
6363
"inline-snapshot>=0.23.0",
6464
"dirty-equals>=0.9.0",
6565
"coverage[toml]==7.10.7",
66-
# tests/interaction runs the streamable-HTTP and SSE apps in-process; sse-starlette 1.x
67-
# keeps a module-global anyio.Event (`AppStatus.should_exit_event`) bound to the first
68-
# test's event loop, which breaks every subsequent in-process SSE response. The runtime
69-
# floor stays >=1.6.1 (works under uvicorn); only the test environment needs >=2.x.
70-
"sse-starlette>=2.1.0",
7166
]
7267
docs = [
7368
"mkdocs>=1.6.1",

tests/interaction/conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""Shared fixtures for the interaction suite."""
22

3+
from collections.abc import Iterator
4+
35
import pytest
6+
from sse_starlette.sse import AppStatus
47

58
from tests.interaction._connect import Connect, connect_in_memory, connect_over_sse, connect_over_streamable_http
69

@@ -29,6 +32,22 @@ def pytest_configure(config: pytest.Config) -> None:
2932
}
3033

3134

35+
@pytest.fixture(autouse=True)
36+
def _reset_sse_starlette_exit_event() -> Iterator[None]:
37+
"""Reset sse-starlette's module-global exit Event after each test.
38+
39+
sse-starlette <3.0 stores an `anyio.Event` on the `AppStatus` class the first time an
40+
`EventSourceResponse` runs; that Event is bound to the test's event loop and breaks every
41+
subsequent in-process SSE response (RuntimeError "bound to a different event loop", surfacing
42+
as 5-second timeouts in `connect_with_oauth` and "Child exited" on the [sse] leg). v1's own
43+
transport tests run uvicorn in a subprocess and so never share a process across event loops.
44+
sse-starlette 3.x switched to a ContextVar (`_exit_event_context`) and has no such attribute.
45+
"""
46+
yield
47+
if hasattr(AppStatus, "should_exit_event"): # pragma: no branch
48+
AppStatus.should_exit_event = None # pragma: lax no cover
49+
50+
3251
@pytest.fixture(params=sorted(_FACTORIES))
3352
def connect(request: pytest.FixtureRequest) -> Connect:
3453
"""The transport-parametrized connection factory: a test using it runs once per transport.

uv.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)