Coverage for tests/test_router_events.py: 100%

176 statements  

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

1from contextlib import asynccontextmanager 1opqrstu

2from typing import AsyncGenerator, Dict, Union 1opqrstu

3 

4import pytest 1opqrstu

5from fastapi import APIRouter, FastAPI, Request 1opqrstu

6from fastapi.testclient import TestClient 1opqrstu

7from pydantic import BaseModel 1opqrstu

8 

9 

10class State(BaseModel): 1opqrstu

11 app_startup: bool = False 1opqrstu

12 app_shutdown: bool = False 1opqrstu

13 router_startup: bool = False 1opqrstu

14 router_shutdown: bool = False 1opqrstu

15 sub_router_startup: bool = False 1opqrstu

16 sub_router_shutdown: bool = False 1opqrstu

17 

18 

19@pytest.fixture 1opqrstu

20def state() -> State: 1opqrstu

21 return State() 1opqrstu

22 

23 

24@pytest.mark.filterwarnings( 1opqrstu

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

26) 

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

28 app = FastAPI() 1abcdefg

29 

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

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

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

33 

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

35 def app_startup() -> None: 1abcdefg

36 state.app_startup = True 1abcdefg

37 

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

39 def app_shutdown() -> None: 1abcdefg

40 state.app_shutdown = True 1abcdefg

41 

42 router = APIRouter() 1abcdefg

43 

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

45 def router_startup() -> None: 1abcdefg

46 state.router_startup = True 1abcdefg

47 

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

49 def router_shutdown() -> None: 1abcdefg

50 state.router_shutdown = True 1abcdefg

51 

52 sub_router = APIRouter() 1abcdefg

53 

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

55 def sub_router_startup() -> None: 1abcdefg

56 state.sub_router_startup = True 1abcdefg

57 

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

59 def sub_router_shutdown() -> None: 1abcdefg

60 state.sub_router_shutdown = True 1abcdefg

61 

62 router.include_router(sub_router) 1abcdefg

63 app.include_router(router) 1abcdefg

64 

65 assert state.app_startup is False 1abcdefg

66 assert state.router_startup is False 1abcdefg

67 assert state.sub_router_startup is False 1abcdefg

68 assert state.app_shutdown is False 1abcdefg

69 assert state.router_shutdown is False 1abcdefg

70 assert state.sub_router_shutdown is False 1abcdefg

71 with TestClient(app) as client: 1abcdefg

72 assert state.app_startup is True 1abcdefg

73 assert state.router_startup is True 1abcdefg

74 assert state.sub_router_startup is True 1abcdefg

75 assert state.app_shutdown is False 1abcdefg

76 assert state.router_shutdown is False 1abcdefg

77 assert state.sub_router_shutdown is False 1abcdefg

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

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

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

81 assert state.app_startup is True 1abcdefg

82 assert state.router_startup is True 1abcdefg

83 assert state.sub_router_startup is True 1abcdefg

84 assert state.app_shutdown is True 1abcdefg

85 assert state.router_shutdown is True 1abcdefg

86 assert state.sub_router_shutdown is True 1abcdefg

87 

88 

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

90 @asynccontextmanager 1vwxyzAB

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

92 state.app_startup = True 1vwxyzAB

93 yield 1vwxyzAB

94 state.app_shutdown = True 1vwxyzAB

95 

96 app = FastAPI(lifespan=lifespan) 1vwxyzAB

97 

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

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

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

101 

102 assert state.app_startup is False 1vwxyzAB

103 assert state.app_shutdown is False 1vwxyzAB

104 with TestClient(app) as client: 1vwxyzAB

105 assert state.app_startup is True 1vwxyzAB

106 assert state.app_shutdown is False 1vwxyzAB

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

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

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

110 assert state.app_startup is True 1vwxyzAB

111 assert state.app_shutdown is True 1vwxyzAB

112 

113 

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

115 @asynccontextmanager 1hijklmn

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

117 state.app_startup = True 1hijklmn

118 yield {"app": True} 1hijklmn

119 state.app_shutdown = True 1hijklmn

120 

121 @asynccontextmanager 1hijklmn

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

123 state.router_startup = True 1hijklmn

124 yield {"router": True} 1hijklmn

125 state.router_shutdown = True 1hijklmn

126 

127 @asynccontextmanager 1hijklmn

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

129 state.sub_router_startup = True 1hijklmn

130 yield {"sub_router": True} 1hijklmn

131 state.sub_router_shutdown = True 1hijklmn

132 

133 sub_router = APIRouter(lifespan=subrouter_lifespan) 1hijklmn

134 

135 router = APIRouter(lifespan=router_lifespan) 1hijklmn

136 router.include_router(sub_router) 1hijklmn

137 

138 app = FastAPI(lifespan=lifespan) 1hijklmn

139 app.include_router(router) 1hijklmn

140 

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

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

143 assert request.state.app 1hijklmn

144 assert request.state.router 1hijklmn

145 assert request.state.sub_router 1hijklmn

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

147 

148 assert state.app_startup is False 1hijklmn

149 assert state.router_startup is False 1hijklmn

150 assert state.sub_router_startup is False 1hijklmn

151 assert state.app_shutdown is False 1hijklmn

152 assert state.router_shutdown is False 1hijklmn

153 assert state.sub_router_shutdown is False 1hijklmn

154 

155 with TestClient(app) as client: 1hijklmn

156 assert state.app_startup is True 1hijklmn

157 assert state.router_startup is True 1hijklmn

158 assert state.sub_router_startup is True 1hijklmn

159 assert state.app_shutdown is False 1hijklmn

160 assert state.router_shutdown is False 1hijklmn

161 assert state.sub_router_shutdown is False 1hijklmn

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

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

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

165 

166 assert state.app_startup is True 1hijklmn

167 assert state.router_startup is True 1hijklmn

168 assert state.sub_router_startup is True 1hijklmn

169 assert state.app_shutdown is True 1hijklmn

170 assert state.router_shutdown is True 1hijklmn

171 assert state.sub_router_shutdown is True 1hijklmn

172 

173 

174def test_router_nested_lifespan_state_overriding_by_parent() -> None: 1opqrstu

175 @asynccontextmanager 1JKLMNOP

176 async def lifespan( 1JKLMNOP

177 app: FastAPI, 

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

179 yield { 1JKLMNOP

180 "app_specific": True, 

181 "overridden": "app", 

182 } 

183 

184 @asynccontextmanager 1JKLMNOP

185 async def router_lifespan( 1JKLMNOP

186 app: FastAPI, 

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

188 yield { 1JKLMNOP

189 "router_specific": True, 

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

191 } 

192 

193 router = APIRouter(lifespan=router_lifespan) 1JKLMNOP

194 app = FastAPI(lifespan=lifespan) 1JKLMNOP

195 app.include_router(router) 1JKLMNOP

196 

197 with TestClient(app) as client: 1JKLMNOP

198 assert client.app_state == { 1JKLMNOP

199 "app_specific": True, 

200 "router_specific": True, 

201 "overridden": "app", 

202 } 

203 

204 

205def test_merged_no_return_lifespans_return_none() -> None: 1opqrstu

206 @asynccontextmanager 1QRSTUVW

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

208 yield 1QRSTUVW

209 

210 @asynccontextmanager 1QRSTUVW

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

212 yield 1QRSTUVW

213 

214 router = APIRouter(lifespan=router_lifespan) 1QRSTUVW

215 app = FastAPI(lifespan=lifespan) 1QRSTUVW

216 app.include_router(router) 1QRSTUVW

217 

218 with TestClient(app) as client: 1QRSTUVW

219 assert not client.app_state 1QRSTUVW

220 

221 

222def test_merged_mixed_state_lifespans() -> None: 1opqrstu

223 @asynccontextmanager 1CDEFGHI

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

225 yield 1CDEFGHI

226 

227 @asynccontextmanager 1CDEFGHI

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

229 yield {"router": True} 1CDEFGHI

230 

231 @asynccontextmanager 1CDEFGHI

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

233 yield 1CDEFGHI

234 

235 sub_router = APIRouter(lifespan=sub_router_lifespan) 1CDEFGHI

236 router = APIRouter(lifespan=router_lifespan) 1CDEFGHI

237 app = FastAPI(lifespan=lifespan) 1CDEFGHI

238 router.include_router(sub_router) 1CDEFGHI

239 app.include_router(router) 1CDEFGHI

240 

241 with TestClient(app) as client: 1CDEFGHI

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