Coverage for tests/test_router_events.py: 100%

176 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-13 13:38 +0000

1from contextlib import asynccontextmanager 1klmno

2from typing import AsyncGenerator, Dict, Union 1klmno

3 

4import pytest 1klmno

5from fastapi import APIRouter, FastAPI, Request 1klmno

6from fastapi.testclient import TestClient 1klmno

7from pydantic import BaseModel 1klmno

8 

9 

10class State(BaseModel): 1klmno

11 app_startup: bool = False 1klmno

12 app_shutdown: bool = False 1klmno

13 router_startup: bool = False 1klmno

14 router_shutdown: bool = False 1klmno

15 sub_router_startup: bool = False 1klmno

16 sub_router_shutdown: bool = False 1klmno

17 

18 

19@pytest.fixture 1klmno

20def state() -> State: 1klmno

21 return State() 1klmno

22 

23 

24@pytest.mark.filterwarnings( 1klmno

25 r"ignore:\s*on_event is deprecated, use lifespan event handlers instead.*:DeprecationWarning" 

26) 

27def test_router_events(state: State) -> None: 1klmno

28 app = FastAPI() 1abcde

29 

30 @app.get("/") 1abcde

31 def main() -> Dict[str, str]: 1abcde

32 return {"message": "Hello World"} 1abcde

33 

34 @app.on_event("startup") 1abcde

35 def app_startup() -> None: 1abcde

36 state.app_startup = True 1abcde

37 

38 @app.on_event("shutdown") 1abcde

39 def app_shutdown() -> None: 1abcde

40 state.app_shutdown = True 1abcde

41 

42 router = APIRouter() 1abcde

43 

44 @router.on_event("startup") 1abcde

45 def router_startup() -> None: 1abcde

46 state.router_startup = True 1abcde

47 

48 @router.on_event("shutdown") 1abcde

49 def router_shutdown() -> None: 1abcde

50 state.router_shutdown = True 1abcde

51 

52 sub_router = APIRouter() 1abcde

53 

54 @sub_router.on_event("startup") 1abcde

55 def sub_router_startup() -> None: 1abcde

56 state.sub_router_startup = True 1abcde

57 

58 @sub_router.on_event("shutdown") 1abcde

59 def sub_router_shutdown() -> None: 1abcde

60 state.sub_router_shutdown = True 1abcde

61 

62 router.include_router(sub_router) 1abcde

63 app.include_router(router) 1abcde

64 

65 assert state.app_startup is False 1abcde

66 assert state.router_startup is False 1abcde

67 assert state.sub_router_startup is False 1abcde

68 assert state.app_shutdown is False 1abcde

69 assert state.router_shutdown is False 1abcde

70 assert state.sub_router_shutdown is False 1abcde

71 with TestClient(app) as client: 1abcde

72 assert state.app_startup is True 1abcde

73 assert state.router_startup is True 1abcde

74 assert state.sub_router_startup is True 1abcde

75 assert state.app_shutdown is False 1abcde

76 assert state.router_shutdown is False 1abcde

77 assert state.sub_router_shutdown is False 1abcde

78 response = client.get("/") 1abcde

79 assert response.status_code == 200, response.text 1abcde

80 assert response.json() == {"message": "Hello World"} 1abcde

81 assert state.app_startup is True 1abcde

82 assert state.router_startup is True 1abcde

83 assert state.sub_router_startup is True 1abcde

84 assert state.app_shutdown is True 1abcde

85 assert state.router_shutdown is True 1abcde

86 assert state.sub_router_shutdown is True 1abcde

87 

88 

89def test_app_lifespan_state(state: State) -> None: 1klmno

90 @asynccontextmanager 1pqrst

91 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1pqrst

92 state.app_startup = True 1pqrst

93 yield 1pqrst

94 state.app_shutdown = True 1pqrst

95 

96 app = FastAPI(lifespan=lifespan) 1pqrst

97 

98 @app.get("/") 1pqrst

99 def main() -> Dict[str, str]: 1pqrst

100 return {"message": "Hello World"} 1pqrst

101 

102 assert state.app_startup is False 1pqrst

103 assert state.app_shutdown is False 1pqrst

104 with TestClient(app) as client: 1pqrst

105 assert state.app_startup is True 1pqrst

106 assert state.app_shutdown is False 1pqrst

107 response = client.get("/") 1pqrst

108 assert response.status_code == 200, response.text 1pqrst

109 assert response.json() == {"message": "Hello World"} 1pqrst

110 assert state.app_startup is True 1pqrst

111 assert state.app_shutdown is True 1pqrst

112 

113 

114def test_router_nested_lifespan_state(state: State) -> None: 1klmno

115 @asynccontextmanager 1fghij

116 async def lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: 1fghij

117 state.app_startup = True 1fghij

118 yield {"app": True} 1fghij

119 state.app_shutdown = True 1fghij

120 

121 @asynccontextmanager 1fghij

122 async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: 1fghij

123 state.router_startup = True 1fghij

124 yield {"router": True} 1fghij

125 state.router_shutdown = True 1fghij

126 

127 @asynccontextmanager 1fghij

128 async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: 1fghij

129 state.sub_router_startup = True 1fghij

130 yield {"sub_router": True} 1fghij

131 state.sub_router_shutdown = True 1fghij

132 

133 sub_router = APIRouter(lifespan=subrouter_lifespan) 1fghij

134 

135 router = APIRouter(lifespan=router_lifespan) 1fghij

136 router.include_router(sub_router) 1fghij

137 

138 app = FastAPI(lifespan=lifespan) 1fghij

139 app.include_router(router) 1fghij

140 

141 @app.get("/") 1fghij

142 def main(request: Request) -> Dict[str, str]: 1fghij

143 assert request.state.app 1fghij

144 assert request.state.router 1fghij

145 assert request.state.sub_router 1fghij

146 return {"message": "Hello World"} 1fghij

147 

148 assert state.app_startup is False 1fghij

149 assert state.router_startup is False 1fghij

150 assert state.sub_router_startup is False 1fghij

151 assert state.app_shutdown is False 1fghij

152 assert state.router_shutdown is False 1fghij

153 assert state.sub_router_shutdown is False 1fghij

154 

155 with TestClient(app) as client: 1fghij

156 assert state.app_startup is True 1fghij

157 assert state.router_startup is True 1fghij

158 assert state.sub_router_startup is True 1fghij

159 assert state.app_shutdown is False 1fghij

160 assert state.router_shutdown is False 1fghij

161 assert state.sub_router_shutdown is False 1fghij

162 response = client.get("/") 1fghij

163 assert response.status_code == 200, response.text 1fghij

164 assert response.json() == {"message": "Hello World"} 1fghij

165 

166 assert state.app_startup is True 1fghij

167 assert state.router_startup is True 1fghij

168 assert state.sub_router_startup is True 1fghij

169 assert state.app_shutdown is True 1fghij

170 assert state.router_shutdown is True 1fghij

171 assert state.sub_router_shutdown is True 1fghij

172 

173 

174def test_router_nested_lifespan_state_overriding_by_parent() -> None: 1klmno

175 @asynccontextmanager 1zABCD

176 async def lifespan( 1zABCD

177 app: FastAPI, 

178 ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]: 

179 yield { 1zABCD

180 "app_specific": True, 

181 "overridden": "app", 

182 } 

183 

184 @asynccontextmanager 1zABCD

185 async def router_lifespan( 1zABCD

186 app: FastAPI, 

187 ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]: 

188 yield { 1zABCD

189 "router_specific": True, 

190 "overridden": "router", # should override parent 

191 } 

192 

193 router = APIRouter(lifespan=router_lifespan) 1zABCD

194 app = FastAPI(lifespan=lifespan) 1zABCD

195 app.include_router(router) 1zABCD

196 

197 with TestClient(app) as client: 1zABCD

198 assert client.app_state == { 1zABCD

199 "app_specific": True, 

200 "router_specific": True, 

201 "overridden": "app", 

202 } 

203 

204 

205def test_merged_no_return_lifespans_return_none() -> None: 1klmno

206 @asynccontextmanager 1EFGHI

207 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1EFGHI

208 yield 1EFGHI

209 

210 @asynccontextmanager 1EFGHI

211 async def router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1EFGHI

212 yield 1EFGHI

213 

214 router = APIRouter(lifespan=router_lifespan) 1EFGHI

215 app = FastAPI(lifespan=lifespan) 1EFGHI

216 app.include_router(router) 1EFGHI

217 

218 with TestClient(app) as client: 1EFGHI

219 assert not client.app_state 1EFGHI

220 

221 

222def test_merged_mixed_state_lifespans() -> None: 1klmno

223 @asynccontextmanager 1uvwxy

224 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1uvwxy

225 yield 1uvwxy

226 

227 @asynccontextmanager 1uvwxy

228 async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: 1uvwxy

229 yield {"router": True} 1uvwxy

230 

231 @asynccontextmanager 1uvwxy

232 async def sub_router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1uvwxy

233 yield 1uvwxy

234 

235 sub_router = APIRouter(lifespan=sub_router_lifespan) 1uvwxy

236 router = APIRouter(lifespan=router_lifespan) 1uvwxy

237 app = FastAPI(lifespan=lifespan) 1uvwxy

238 router.include_router(sub_router) 1uvwxy

239 app.include_router(router) 1uvwxy

240 

241 with TestClient(app) as client: 1uvwxy

242 assert client.app_state == {"router": True} 1uvwxy