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

1from contextlib import contextmanager 1abcdefg

2from typing import Any, Generator 1abcdefg

3 

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

9 

10 

11class Session: 1abcdefg

12 def __init__(self) -> None: 1abcdefg

13 self.data = ["foo", "bar", "baz"] 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3

14 self.open = True 1JohpKCXLqirMDYNsjtOEZPukvQF0RwlxSG1TymzUH2VAnBWI3

15 

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

22 

23 

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

31 

32 

33def dep_session() -> Any: 1abcdefg

34 with acquire_session() as s: 1KCXMDYOEZQF0SG1UH2WI3

35 yield s 1KCXMDYOEZQF0SG1UH2WI3

36 

37 

38def broken_dep_session() -> Any: 1abcdefg

39 with acquire_session() as s: 1JohpLqirNsjtPukvRwlxTymzVAnB

40 s.open = False 1JohpLqirNsjtPukvRwlxTymzVAnB

41 yield s 1JohpLqirNsjtPukvRwlxTymzVAnB

42 

43 

44SessionDep = Annotated[Session, Depends(dep_session)] 1abcdefg

45BrokenSessionDep = Annotated[Session, Depends(broken_dep_session)] 1abcdefg

46 

47app = FastAPI() 1abcdefg

48 

49 

50@app.get("/data") 1abcdefg

51def get_data(session: SessionDep) -> Any: 1abcdefg

52 data = list(session) 1KMOQSUW

53 return data 1KMOQSUW

54 

55 

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

60 

61 return StreamingResponse(iter_data()) 1XYZ0123

62 

63 

64@app.get("/stream-session") 1abcdefg

65def get_stream_session(session: SessionDep) -> Any: 1abcdefg

66 def iter_data(): 1CDEFGHI

67 yield from session 1CDEFGHI

68 

69 return StreamingResponse(iter_data()) 1CDEFGHI

70 

71 

72@app.get("/broken-session-data") 1abcdefg

73def get_broken_session_data(session: BrokenSessionDep) -> Any: 1abcdefg

74 return list(session) 1JoLqNsPuRwTyVA

75 

76 

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

81 

82 return StreamingResponse(iter_data()) 1hpirjtkvlxmznB

83 

84 

85client = TestClient(app) 1abcdefg

86 

87 

88def test_regular_no_stream(): 1abcdefg

89 response = client.get("/data") 1KMOQSUW

90 assert response.json() == ["foo", "bar", "baz"] 1KMOQSUW

91 

92 

93def test_stream_simple(): 1abcdefg

94 response = client.get("/stream-simple") 1XYZ0123

95 assert response.text == "xyz" 1XYZ0123

96 

97 

98def test_stream_session(): 1abcdefg

99 response = client.get("/stream-session") 1CDEFGHI

100 assert response.text == "foobarbaz" 1CDEFGHI

101 

102 

103def test_broken_session_data(): 1abcdefg

104 with pytest.raises(ValueError, match="Session closed"): 1JLNPRTV

105 client.get("/broken-session-data") 1JLNPRTV

106 

107 

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

113 

114 

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

119 

120 

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