Coverage for tests/test_dependency_yield_scope.py: 100%

142 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-12-04 08:29 +0000

1import json 1abcdefg

2from typing import Any, Tuple 1abcdefg

3 

4import pytest 1abcdefg

5from fastapi import APIRouter, Depends, FastAPI, HTTPException 1abcdefg

6from fastapi.exceptions import FastAPIError 1abcdefg

7from fastapi.responses import StreamingResponse 1abcdefg

8from fastapi.testclient import TestClient 1abcdefg

9from typing_extensions import Annotated 1abcdefg

10 

11 

12class Session: 1abcdefg

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

14 self.open = True 1JovKhCLpwMiDNqxOjEPryQkFRszSlGTtAUmHVuBWnI

15 

16 

17def dep_session() -> Any: 1abcdefg

18 s = Session() 1JovKhCLpwMiDNqxOjEPryQkFRszSlGTtAUmHVuBWnI

19 yield s 1JovKhCLpwMiDNqxOjEPryQkFRszSlGTtAUmHVuBWnI

20 s.open = False 1JovKhCLpwMiDNqxOjEPryQkFRszSlGTtAUmHVuBWnI

21 

22 

23def raise_after_yield() -> Any: 1abcdefg

24 yield 1XY+#Z0,$12-%34.'56/(78:)9!;*

25 raise HTTPException(status_code=503, detail="Exception after yield") 1XY+#Z0,$12-%34.'56/(78:)9!;*

26 

27 

28SessionFuncDep = Annotated[Session, Depends(dep_session, scope="function")] 1abcdefg

29SessionRequestDep = Annotated[Session, Depends(dep_session, scope="request")] 1abcdefg

30SessionDefaultDep = Annotated[Session, Depends(dep_session)] 1abcdefg

31 

32 

33class NamedSession: 1abcdefg

34 def __init__(self, name: str = "default") -> None: 1abcdefg

35 self.name = name 1ovhpwiqxjrykszltAmuBn

36 self.open = True 1ovhpwiqxjrykszltAmuBn

37 

38 

39def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep) -> Any: 1abcdefg

40 assert session is session_b 1hijklmn

41 named_session = NamedSession(name="named") 1hijklmn

42 yield named_session, session_b 1hijklmn

43 named_session.open = False 1hijklmn

44 

45 

46NamedSessionsDep = Annotated[Tuple[NamedSession, Session], Depends(get_named_session)] 1abcdefg

47 

48 

49def get_named_func_session(session: SessionFuncDep) -> Any: 1abcdefg

50 named_session = NamedSession(name="named") 1opqrstu

51 yield named_session, session 1opqrstu

52 named_session.open = False 1opqrstu

53 

54 

55def get_named_regular_func_session(session: SessionFuncDep) -> Any: 1abcdefg

56 named_session = NamedSession(name="named") 1vwxyzAB

57 return named_session, session 1vwxyzAB

58 

59 

60BrokenSessionsDep = Annotated[ 1abcdefg

61 Tuple[NamedSession, Session], Depends(get_named_func_session) 

62] 

63NamedSessionsFuncDep = Annotated[ 1abcdefg

64 Tuple[NamedSession, Session], Depends(get_named_func_session, scope="function") 

65] 

66 

67RegularSessionsDep = Annotated[ 1abcdefg

68 Tuple[NamedSession, Session], Depends(get_named_regular_func_session) 

69] 

70 

71app = FastAPI() 1abcdefg

72router = APIRouter() 1abcdefg

73 

74 

75@router.get("/") 1abcdefg

76def get_index(): 1abcdefg

77 return {"status": "ok"} 1+#,$-%.'/(:);*

78 

79 

80@app.get("/function-scope") 1abcdefg

81def function_scope(session: SessionFuncDep) -> Any: 1abcdefg

82 def iter_data(): 1JLNPRTV

83 yield json.dumps({"is_open": session.open}) 1JLNPRTV

84 

85 return StreamingResponse(iter_data()) 1JLNPRTV

86 

87 

88@app.get("/request-scope") 1abcdefg

89def request_scope(session: SessionRequestDep) -> Any: 1abcdefg

90 def iter_data(): 1KMOQSUW

91 yield json.dumps({"is_open": session.open}) 1KMOQSUW

92 

93 return StreamingResponse(iter_data()) 1KMOQSUW

94 

95 

96@app.get("/two-scopes") 1abcdefg

97def get_stream_session( 1abcdefg

98 function_session: SessionFuncDep, request_session: SessionRequestDep 

99) -> Any: 

100 def iter_data(): 1CDEFGHI

101 yield json.dumps( 1CDEFGHI

102 {"func_is_open": function_session.open, "req_is_open": request_session.open} 

103 ) 

104 

105 return StreamingResponse(iter_data()) 1CDEFGHI

106 

107 

108@app.get("/sub") 1abcdefg

109def get_sub(sessions: NamedSessionsDep) -> Any: 1abcdefg

110 def iter_data(): 1hijklmn

111 yield json.dumps( 1hijklmn

112 {"named_session_open": sessions[0].open, "session_open": sessions[1].open} 

113 ) 

114 

115 return StreamingResponse(iter_data()) 1hijklmn

116 

117 

118@app.get("/named-function-scope") 1abcdefg

119def get_named_function_scope(sessions: NamedSessionsFuncDep) -> Any: 1abcdefg

120 def iter_data(): 1opqrstu

121 yield json.dumps( 1opqrstu

122 {"named_session_open": sessions[0].open, "session_open": sessions[1].open} 

123 ) 

124 

125 return StreamingResponse(iter_data()) 1opqrstu

126 

127 

128@app.get("/regular-function-scope") 1abcdefg

129def get_regular_function_scope(sessions: RegularSessionsDep) -> Any: 1abcdefg

130 def iter_data(): 1vwxyzAB

131 yield json.dumps( 1vwxyzAB

132 {"named_session_open": sessions[0].open, "session_open": sessions[1].open} 

133 ) 

134 

135 return StreamingResponse(iter_data()) 1vwxyzAB

136 

137 

138app.include_router( 1abcdefg

139 prefix="/router-scope-function", 

140 router=router, 

141 dependencies=[Depends(raise_after_yield, scope="function")], 

142) 

143 

144app.include_router( 1abcdefg

145 prefix="/router-scope-request", 

146 router=router, 

147 dependencies=[Depends(raise_after_yield, scope="request")], 

148) 

149 

150client = TestClient(app) 1abcdefg

151 

152 

153def test_function_scope() -> None: 1abcdefg

154 response = client.get("/function-scope") 1JLNPRTV

155 assert response.status_code == 200 1JLNPRTV

156 data = response.json() 1JLNPRTV

157 assert data["is_open"] is False 1JLNPRTV

158 

159 

160def test_request_scope() -> None: 1abcdefg

161 response = client.get("/request-scope") 1KMOQSUW

162 assert response.status_code == 200 1KMOQSUW

163 data = response.json() 1KMOQSUW

164 assert data["is_open"] is True 1KMOQSUW

165 

166 

167def test_two_scopes() -> None: 1abcdefg

168 response = client.get("/two-scopes") 1CDEFGHI

169 assert response.status_code == 200 1CDEFGHI

170 data = response.json() 1CDEFGHI

171 assert data["func_is_open"] is False 1CDEFGHI

172 assert data["req_is_open"] is True 1CDEFGHI

173 

174 

175def test_sub() -> None: 1abcdefg

176 response = client.get("/sub") 1hijklmn

177 assert response.status_code == 200 1hijklmn

178 data = response.json() 1hijklmn

179 assert data["named_session_open"] is True 1hijklmn

180 assert data["session_open"] is True 1hijklmn

181 

182 

183def test_broken_scope() -> None: 1abcdefg

184 with pytest.raises( 1=?@[]^_

185 FastAPIError, 

186 match='The dependency "get_named_func_session" has a scope of "request", it cannot depend on dependencies with scope "function"', 

187 ): 

188 

189 @app.get("/broken-scope") 1=?@[]^_

190 def get_broken(sessions: BrokenSessionsDep) -> Any: # pragma: no cover 1=?@[]^_

191 pass 

192 

193 

194def test_named_function_scope() -> None: 1abcdefg

195 response = client.get("/named-function-scope") 1opqrstu

196 assert response.status_code == 200 1opqrstu

197 data = response.json() 1opqrstu

198 assert data["named_session_open"] is False 1opqrstu

199 assert data["session_open"] is False 1opqrstu

200 

201 

202def test_regular_function_scope() -> None: 1abcdefg

203 response = client.get("/regular-function-scope") 1vwxyzAB

204 assert response.status_code == 200 1vwxyzAB

205 data = response.json() 1vwxyzAB

206 assert data["named_session_open"] is True 1vwxyzAB

207 assert data["session_open"] is False 1vwxyzAB

208 

209 

210def test_router_level_dep_scope_function() -> None: 1abcdefg

211 response = client.get("/router-scope-function/") 1+,-./:;

212 assert response.status_code == 503 1+,-./:;

213 assert response.json() == {"detail": "Exception after yield"} 1+,-./:;

214 

215 

216def test_router_level_dep_scope_request() -> None: 1abcdefg

217 with TestClient(app, raise_server_exceptions=False) as client: 1#$%'()*

218 response = client.get("/router-scope-request/") 1#$%'()*

219 assert response.status_code == 200 1#$%'()*

220 assert response.json() == {"status": "ok"} 1#$%'()*

221 

222 

223def test_app_level_dep_scope_function() -> None: 1abcdefg

224 app = FastAPI(dependencies=[Depends(raise_after_yield, scope="function")]) 1XZ13579

225 

226 @app.get("/app-scope-function") 1XZ13579

227 def get_app_scope_function(): 1XZ13579

228 return {"status": "ok"} 1XZ13579

229 

230 with TestClient(app) as client: 1XZ13579

231 response = client.get("/app-scope-function") 1XZ13579

232 assert response.status_code == 503 1XZ13579

233 assert response.json() == {"detail": "Exception after yield"} 1XZ13579

234 

235 

236def test_app_level_dep_scope_request() -> None: 1abcdefg

237 app = FastAPI(dependencies=[Depends(raise_after_yield, scope="request")]) 1Y02468!

238 

239 @app.get("/app-scope-request") 1Y02468!

240 def get_app_scope_request(): 1Y02468!

241 return {"status": "ok"} 1Y02468!

242 

243 with TestClient(app, raise_server_exceptions=False) as client: 1Y02468!

244 response = client.get("/app-scope-request") 1Y02468!

245 assert response.status_code == 200 1Y02468!

246 assert response.json() == {"status": "ok"} 1Y02468!