Coverage for fastapi/openapi/docs.py: 100%

30 statements  

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

1import json 1$%'()*+

2from typing import Any, Dict, Optional 1$%'()*+

3 

4from annotated_doc import Doc 1$%'()*+

5from fastapi.encoders import jsonable_encoder 1$%'()*+

6from starlette.responses import HTMLResponse 1$%'()*+

7from typing_extensions import Annotated 1$%'()*+

8 

9swagger_ui_default_parameters: Annotated[ 1$%'()*+

10 Dict[str, Any], 

11 Doc( 

12 """ 

13 Default configurations for Swagger UI. 

14 

15 You can use it as a template to add any other configurations needed. 

16 """ 

17 ), 

18] = { 

19 "dom_id": "#swagger-ui", 

20 "layout": "BaseLayout", 

21 "deepLinking": True, 

22 "showExtensions": True, 

23 "showCommonExtensions": True, 

24} 

25 

26 

27def get_swagger_ui_html( 1$%'()*+J

28 *, 

29 openapi_url: Annotated[ 

30 str, 

31 Doc( 

32 """ 

33 The OpenAPI URL that Swagger UI should load and use. 

34 

35 This is normally done automatically by FastAPI using the default URL 

36 `/openapi.json`. 

37 """ 

38 ), 

39 ], 

40 title: Annotated[ 

41 str, 

42 Doc( 

43 """ 

44 The HTML `<title>` content, normally shown in the browser tab. 

45 """ 

46 ), 

47 ], 

48 swagger_js_url: Annotated[ 

49 str, 

50 Doc( 

51 """ 

52 The URL to use to load the Swagger UI JavaScript. 

53 

54 It is normally set to a CDN URL. 

55 """ 

56 ), 

57 ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js", 

58 swagger_css_url: Annotated[ 

59 str, 

60 Doc( 

61 """ 

62 The URL to use to load the Swagger UI CSS. 

63 

64 It is normally set to a CDN URL. 

65 """ 

66 ), 

67 ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css", 

68 swagger_favicon_url: Annotated[ 

69 str, 

70 Doc( 

71 """ 

72 The URL of the favicon to use. It is normally shown in the browser tab. 

73 """ 

74 ), 

75 ] = "https://fastapi.tiangolo.com/img/favicon.png", 

76 oauth2_redirect_url: Annotated[ 

77 Optional[str], 

78 Doc( 

79 """ 

80 The OAuth2 redirect URL, it is normally automatically handled by FastAPI. 

81 """ 

82 ), 

83 ] = None, 

84 init_oauth: Annotated[ 

85 Optional[Dict[str, Any]], 

86 Doc( 

87 """ 

88 A dictionary with Swagger UI OAuth2 initialization configurations. 

89 """ 

90 ), 

91 ] = None, 

92 swagger_ui_parameters: Annotated[ 

93 Optional[Dict[str, Any]], 

94 Doc( 

95 """ 

96 Configuration parameters for Swagger UI. 

97 

98 It defaults to [swagger_ui_default_parameters][fastapi.openapi.docs.swagger_ui_default_parameters]. 

99 """ 

100 ), 

101 ] = None, 

102) -> HTMLResponse: 

103 """ 

104 Generate and return the HTML that loads Swagger UI for the interactive 

105 API docs (normally served at `/docs`). 

106 

107 You would only call this function yourself if you needed to override some parts, 

108 for example the URLs to use to load Swagger UI's JavaScript and CSS. 

109 

110 Read more about it in the 

111 [FastAPI docs for Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/) 

112 and the [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/). 

113 """ 

114 current_swagger_ui_parameters = swagger_ui_default_parameters.copy() 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

115 if swagger_ui_parameters: 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

116 current_swagger_ui_parameters.update(swagger_ui_parameters) 1ijkmnoqrsuvwyzACDEGHI

117 

118 html = f""" 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

119 <!DOCTYPE html> 

120 <html> 

121 <head> 

122 <link type="text/css" rel="stylesheet" href="{swagger_css_url}"> 

123 <link rel="shortcut icon" href="{swagger_favicon_url}"> 

124 <title>{title}</title> 

125 </head> 

126 <body> 

127 <div id="swagger-ui"> 

128 </div> 

129 <script src="{swagger_js_url}"></script> 

130 <!-- `SwaggerUIBundle` is now available on the page --> 

131 <script> 

132 const ui = SwaggerUIBundle({{ 

133 url: '{openapi_url}', 

134 """ 

135 

136 for key, value in current_swagger_ui_parameters.items(): 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

137 html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n" 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

138 

139 if oauth2_redirect_url: 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

140 html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}'," 1KLhaijkMNOPlbmnoQRSTpcqrsUVWXtduvwYZ01xeyzA2345BfCDE6789FgGHI!#

141 

142 html += """ 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

143 presets: [ 

144 SwaggerUIBundle.presets.apis, 

145 SwaggerUIBundle.SwaggerUIStandalonePreset 

146 ], 

147 })""" 

148 

149 if init_oauth: 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

150 html += f""" 1hlptxBF

151 ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))}) 

152 """ 

153 

154 html += """ 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

155 </script> 

156 </body> 

157 </html> 

158 """ 

159 return HTMLResponse(html) 2K L , - . h a i j k M N O P / : ; l b m n o Q R S T = ? @ p c q r s U V W X [ ] ^ t d u v w Y Z 0 1 _ ` { x e y z A 2 3 4 5 | } ~ B f C D E 6 7 8 9 abJ bbF g G H I ! #

160 

161 

162def get_redoc_html( 2$ % ' ( ) * + cb

163 *, 

164 openapi_url: Annotated[ 

165 str, 

166 Doc( 

167 """ 

168 The OpenAPI URL that ReDoc should load and use. 

169 

170 This is normally done automatically by FastAPI using the default URL 

171 `/openapi.json`. 

172 """ 

173 ), 

174 ], 

175 title: Annotated[ 

176 str, 

177 Doc( 

178 """ 

179 The HTML `<title>` content, normally shown in the browser tab. 

180 """ 

181 ), 

182 ], 

183 redoc_js_url: Annotated[ 

184 str, 

185 Doc( 

186 """ 

187 The URL to use to load the ReDoc JavaScript. 

188 

189 It is normally set to a CDN URL. 

190 """ 

191 ), 

192 ] = "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js", 

193 redoc_favicon_url: Annotated[ 

194 str, 

195 Doc( 

196 """ 

197 The URL of the favicon to use. It is normally shown in the browser tab. 

198 """ 

199 ), 

200 ] = "https://fastapi.tiangolo.com/img/favicon.png", 

201 with_google_fonts: Annotated[ 

202 bool, 

203 Doc( 

204 """ 

205 Load and use Google Fonts. 

206 """ 

207 ), 

208 ] = True, 

209) -> HTMLResponse: 

210 """ 

211 Generate and return the HTML response that loads ReDoc for the alternative 

212 API docs (normally served at `/redoc`). 

213 

214 You would only call this function yourself if you needed to override some parts, 

215 for example the URLs to use to load ReDoc's JavaScript and CSS. 

216 

217 Read more about it in the 

218 [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/). 

219 """ 

220 html = f""" 2dbebfbgba hbibjbkblbmbb nbobpbqbrbsbc tbubvbwbxbybd zbAbBbCbDbEbe FbGbHbIbJbKbf LbMbNbObPbcbg QbRb

221 <!DOCTYPE html> 

222 <html> 

223 <head> 

224 <title>{title}</title> 

225 <!-- needed for adaptive design --> 

226 <meta charset="utf-8"/> 

227 <meta name="viewport" content="width=device-width, initial-scale=1"> 

228 """ 

229 if with_google_fonts: 2dbebfbgba hbibjbkblbmbb nbobpbqbrbsbc tbubvbwbxbybd zbAbBbCbDbEbe FbGbHbIbJbKbf LbMbNbObPbcbg QbRb

230 html += """ 2dbebfbgba hbibjbkblbmbb nbobpbqbrbsbc tbubvbwbxbybd zbAbBbCbDbEbe FbGbHbIbJbKbf LbMbNbObPbcbg QbRb

231 <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet"> 

232 """ 

233 html += f""" 2dbebfbgba hbibjbkblbmbb nbobpbqbrbsbc tbubvbwbxbybd zbAbBbCbDbEbe FbGbHbIbJbKbf LbMbNbObPbcbg QbRb

234 <link rel="shortcut icon" href="{redoc_favicon_url}"> 

235 <!-- 

236 ReDoc doesn't change outer page styles 

237 --> 

238 <style> 

239 body {{ 

240 margin: 0; 

241 padding: 0; 

242 }} 

243 </style> 

244 </head> 

245 <body> 

246 <noscript> 

247 ReDoc requires Javascript to function. Please enable it to browse the documentation. 

248 </noscript> 

249 <redoc spec-url="{openapi_url}"></redoc> 

250 <script src="{redoc_js_url}"> </script> 

251 </body> 

252 </html> 

253 """ 

254 return HTMLResponse(html) 2dbebfbgba hbibjbkblbmbb nbobpbqbrbsbc tbubvbwbxbybd zbAbBbCbDbEbe FbGbHbIbJbKbf LbMbNbObPbcbg QbRb

255 

256 

257def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: 1$%'()*+

258 """ 

259 Generate the HTML response with the OAuth2 redirection for Swagger UI. 

260 

261 You normally don't need to use or change this. 

262 """ 

263 # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html 

264 html = """ 2SbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*b+b,b

265 <!doctype html> 

266 <html lang="en-US"> 

267 <head> 

268 <title>Swagger UI: OAuth2 Redirect</title> 

269 </head> 

270 <body> 

271 <script> 

272 'use strict'; 

273 function run () { 

274 var oauth2 = window.opener.swaggerUIRedirectOauth2; 

275 var sentState = oauth2.state; 

276 var redirectUrl = oauth2.redirectUrl; 

277 var isValid, qp, arr; 

278 

279 if (/code|token|error/.test(window.location.hash)) { 

280 qp = window.location.hash.substring(1).replace('?', '&'); 

281 } else { 

282 qp = location.search.substring(1); 

283 } 

284 

285 arr = qp.split("&"); 

286 arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';}); 

287 qp = qp ? JSON.parse('{' + arr.join() + '}', 

288 function (key, value) { 

289 return key === "" ? value : decodeURIComponent(value); 

290 } 

291 ) : {}; 

292 

293 isValid = qp.state === sentState; 

294 

295 if (( 

296 oauth2.auth.schema.get("flow") === "accessCode" || 

297 oauth2.auth.schema.get("flow") === "authorizationCode" || 

298 oauth2.auth.schema.get("flow") === "authorization_code" 

299 ) && !oauth2.auth.code) { 

300 if (!isValid) { 

301 oauth2.errCb({ 

302 authId: oauth2.auth.name, 

303 source: "auth", 

304 level: "warning", 

305 message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server." 

306 }); 

307 } 

308 

309 if (qp.code) { 

310 delete oauth2.state; 

311 oauth2.auth.code = qp.code; 

312 oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl}); 

313 } else { 

314 let oauthErrorMsg; 

315 if (qp.error) { 

316 oauthErrorMsg = "["+qp.error+"]: " + 

317 (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") + 

318 (qp.error_uri ? "More info: "+qp.error_uri : ""); 

319 } 

320 

321 oauth2.errCb({ 

322 authId: oauth2.auth.name, 

323 source: "auth", 

324 level: "error", 

325 message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server." 

326 }); 

327 } 

328 } else { 

329 oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl}); 

330 } 

331 window.close(); 

332 } 

333 

334 if (document.readyState !== 'loading') { 

335 run(); 

336 } else { 

337 document.addEventListener('DOMContentLoaded', function () { 

338 run(); 

339 }); 

340 } 

341 </script> 

342 </body> 

343 </html> 

344 """ 

345 return HTMLResponse(content=html) 2SbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*b+b,b