Coverage for tests/test_dependency_after_yield_streaming.py: 100%
80 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-04 08:29 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-04 08:29 +0000
1from contextlib import contextmanager 1abcdefg
2from typing import Any, Generator 1abcdefg
4import pytest 1abcdefg
5from fastapi import Depends, FastAPI 1abcdefg
6from fastapi.responses import StreamingResponse 1abcdefg
7from fastapi.testclient import TestClient 1abcdefg
8from typing_extensions import Annotated 1abcdefg
11class Session: 1abcdefg
12 def __init__(self) -> None: 1abcdefg
13 self.data = ["foo", "bar", "baz"] 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3
14 self.open = True 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3
16 def __iter__(self) -> Generator[str, None, None]: 1abcdefg
17 for item in self.data: 1JohpKCLqirMDNsjtOEPukvQFRwlxSGTymzUHVAnBWI
18 if self.open: 1JohpKCLqirMDNsjtOEPukvQFRwlxSGTymzUHVAnBWI
19 yield item 1KCMDOEQFSGUHWI
20 else:
21 raise ValueError("Session closed") 1JohpLqirNsjtPukvRwlxTymzVAnB
24@contextmanager 1abcdefg
25def acquire_session() -> Generator[Session, None, None]: 1abcdefg
26 session = Session() 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3
27 try: 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3
28 yield session 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3
29 finally:
30 session.open = False 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3
33def dep_session() -> Any: 1abcdefg
34 with acquire_session() as s: 1KCXMDYOEZQF0SG1UH2WI3
35 yield s 1KCXMDYOEZQF0SG1UH2WI3
38def broken_dep_session() -> Any: 1abcdefg
39 with acquire_session() as s: 1JohpLqirNsjtPukvRwlxTymzVAnB
40 s.open = False 1JohpLqirNsjtPukvRwlxTymzVAnB
41 yield s 1JohpLqirNsjtPukvRwlxTymzVAnB
44SessionDep = Annotated[Session, Depends(dep_session)] 1abcdefg
45BrokenSessionDep = Annotated[Session, Depends(broken_dep_session)] 1abcdefg
47app = FastAPI() 1abcdefg
50@app.get("/data") 1abcdefg
51def get_data(session: SessionDep) -> Any: 1abcdefg
52 data = list(session) 1KMOQSUW
53 return data 1KMOQSUW
56@app.get("/stream-simple") 1abcdefg
57def get_stream_simple(session: SessionDep) -> Any: 1abcdefg
58 def iter_data(): 1XYZ0123
59 yield from ["x", "y", "z"] 1XYZ0123
61 return StreamingResponse(iter_data()) 1XYZ0123
64@app.get("/stream-session") 1abcdefg
65def get_stream_session(session: SessionDep) -> Any: 1abcdefg
66 def iter_data(): 1CDEFGHI
67 yield from session 1CDEFGHI
69 return StreamingResponse(iter_data()) 1CDEFGHI
72@app.get("/broken-session-data") 1abcdefg
73def get_broken_session_data(session: BrokenSessionDep) -> Any: 1abcdefg
74 return list(session) 1JoLqNsPuRwTyVA
77@app.get("/broken-session-stream") 1abcdefg
78def get_broken_session_stream(session: BrokenSessionDep) -> Any: 1abcdefg
79 def iter_data(): 1hpirjtkvlxmznB
80 yield from session 1hpirjtkvlxmznB
82 return StreamingResponse(iter_data()) 1hpirjtkvlxmznB
85client = TestClient(app) 1abcdefg
88def test_regular_no_stream(): 1abcdefg
89 response = client.get("/data") 1KMOQSUW
90 assert response.json() == ["foo", "bar", "baz"] 1KMOQSUW
93def test_stream_simple(): 1abcdefg
94 response = client.get("/stream-simple") 1XYZ0123
95 assert response.text == "xyz" 1XYZ0123
98def test_stream_session(): 1abcdefg
99 response = client.get("/stream-session") 1CDEFGHI
100 assert response.text == "foobarbaz" 1CDEFGHI
103def test_broken_session_data(): 1abcdefg
104 with pytest.raises(ValueError, match="Session closed"): 1JLNPRTV
105 client.get("/broken-session-data") 1JLNPRTV
108def test_broken_session_data_no_raise(): 1abcdefg
109 client = TestClient(app, raise_server_exceptions=False) 1oqsuwyA
110 response = client.get("/broken-session-data") 1oqsuwyA
111 assert response.status_code == 500 1oqsuwyA
112 assert response.text == "Internal Server Error" 1oqsuwyA
115def test_broken_session_stream_raise(): 1abcdefg
116 # Can raise ValueError on Pydantic v2 and ExceptionGroup on Pydantic v1
117 with pytest.raises((ValueError, Exception)): 1prtvxzB
118 client.get("/broken-session-stream") 1prtvxzB
121def test_broken_session_stream_no_raise(): 1abcdefg
122 """
123 When a dependency with yield raises after the streaming response already started
124 the 200 status code is already sent, but there's still an error in the server
125 afterwards, an exception is raised and captured or shown in the server logs.
126 """
127 with TestClient(app, raise_server_exceptions=False) as client: 1hijklmn
128 response = client.get("/broken-session-stream") 1hijklmn
129 assert response.status_code == 200 1hijklmn
130 assert response.text == "" 1hijklmn