|
5 | 5 | import anyio |
6 | 6 | import httpx |
7 | 7 | import pytest |
| 8 | +import sse_starlette |
8 | 9 | from anyio.abc import TaskGroup |
9 | 10 | from inline_snapshot import snapshot |
| 11 | +from packaging import version |
10 | 12 | from pydantic import AnyUrl |
11 | | -from sse_starlette.sse import AppStatus |
12 | 13 | from starlette.applications import Starlette |
13 | 14 | from starlette.requests import Request |
14 | 15 | from starlette.responses import Response |
|
33 | 34 | Tool, |
34 | 35 | ) |
35 | 36 |
|
| 37 | +SSE_STARLETTE_VERSION = version.parse(sse_starlette.__version__) |
| 38 | +NEEDS_RESET = SSE_STARLETTE_VERSION < version.parse("3.0.0") |
| 39 | + |
36 | 40 |
|
37 | 41 | @pytest.fixture(autouse=True) |
38 | 42 | def reset_sse_app_status(): |
39 | 43 | """Reset sse-starlette's global AppStatus singleton before each test. |
40 | 44 |
|
41 | | - This is necessary because AppStatus.should_exit_event (a global anyio.Event) gets bound |
42 | | - to one event loop but accessed from others during parallel test execution (xdist workers), |
43 | | - causing RuntimeError("bound to a different event loop"), which prevents the SSE server |
44 | | - from responding (leaving status at 499) and causes ClosedResourceError during teardown. |
| 45 | + AppStatus.should_exit_event is a global asyncio.Event that gets bound to |
| 46 | + an event loop. This ensures each test gets a fresh Event and prevents |
| 47 | + RuntimeError("bound to a different event loop") during parallel test |
| 48 | + execution with pytest-xdist. |
| 49 | +
|
| 50 | + NOTE: This fixture is only necessary for sse-starlette < 3.0.0. |
| 51 | + Version 3.0+ eliminated the global state issue entirely by using |
| 52 | + context-local events instead of module-level singletons, providing |
| 53 | + automatic test isolation without manual cleanup. |
| 54 | +
|
| 55 | + See <https://github.com/sysid/sse-starlette/pull/141> for more details. |
45 | 56 | """ |
46 | | - AppStatus.should_exit_event = anyio.Event() |
| 57 | + if not NEEDS_RESET: |
| 58 | + yield |
| 59 | + return |
| 60 | + |
| 61 | + # lazy import to avoid import errors |
| 62 | + from sse_starlette.sse import AppStatus |
| 63 | + |
| 64 | + # Setup: Reset before test |
| 65 | + AppStatus.should_exit_event = anyio.Event() # type: ignore[attr-defined] |
| 66 | + |
| 67 | + yield |
| 68 | + |
| 69 | + # Teardown: Reset after test to prevent contamination |
| 70 | + AppStatus.should_exit_event = anyio.Event() # type: ignore[attr-defined] |
47 | 71 |
|
48 | 72 |
|
49 | 73 | SERVER_NAME = "test_server_for_SSE" |
|
0 commit comments