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

31 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-04-06 01:24 +0000

1import json 1YZ01

2from typing import Annotated, Any 1YZ01

3 

4from annotated_doc import Doc 1YZ01

5from fastapi.encoders import jsonable_encoder 1YZ01

6from starlette.responses import HTMLResponse 1YZ01

7 

8 

9def _html_safe_json(value: Any) -> str: 1YZ01

10 """Serialize a value to JSON with HTML special characters escaped. 

11 

12 This prevents injection when the JSON is embedded inside a <script> tag. 

13 """ 

14 return ( 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

15 json.dumps(value) 

16 .replace("<", "\\u003c") 

17 .replace(">", "\\u003e") 

18 .replace("&", "\\u0026") 

19 ) 

20 

21 

22swagger_ui_default_parameters: Annotated[ 1YZ01

23 dict[str, Any], 

24 Doc( 

25 """ 

26 Default configurations for Swagger UI. 

27 

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

29 """ 

30 ), 

31] = { 

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

33 "layout": "BaseLayout", 

34 "deepLinking": True, 

35 "showExtensions": True, 

36 "showCommonExtensions": True, 

37} 

38 

39 

40def get_swagger_ui_html( 1YZ01

41 *, 

42 openapi_url: Annotated[ 

43 str, 

44 Doc( 

45 """ 

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

47 

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

49 `/openapi.json`. 

50 

51 Read more about it in the 

52 [FastAPI docs for Conditional OpenAPI](https://fastapi.tiangolo.com/how-to/conditional-openapi/#conditional-openapi-from-settings-and-env-vars) 

53 """ 

54 ), 

55 ], 

56 title: Annotated[ 

57 str, 

58 Doc( 

59 """ 

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

61 

62 Read more about it in the 

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

64 """ 

65 ), 

66 ], 

67 swagger_js_url: Annotated[ 

68 str, 

69 Doc( 

70 """ 

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

72 

73 It is normally set to a CDN URL. 

74 

75 Read more about it in the 

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

77 """ 

78 ), 

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

80 swagger_css_url: Annotated[ 

81 str, 

82 Doc( 

83 """ 

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

85 

86 It is normally set to a CDN URL. 

87 

88 Read more about it in the 

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

90 """ 

91 ), 

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

93 swagger_favicon_url: Annotated[ 

94 str, 

95 Doc( 

96 """ 

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

98 """ 

99 ), 

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

101 oauth2_redirect_url: Annotated[ 

102 str | None, 

103 Doc( 

104 """ 

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

106 

107 Read more about it in the 

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

109 """ 

110 ), 

111 ] = None, 

112 init_oauth: Annotated[ 

113 dict[str, Any] | None, 

114 Doc( 

115 """ 

116 A dictionary with Swagger UI OAuth2 initialization configurations. 

117 

118 Read more about the available configuration options in the 

119 [Swagger UI docs](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/). 

120 """ 

121 ), 

122 ] = None, 

123 swagger_ui_parameters: Annotated[ 

124 dict[str, Any] | None, 

125 Doc( 

126 """ 

127 Configuration parameters for Swagger UI. 

128 

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

130 

131 Read more about it in the 

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

133 """ 

134 ), 

135 ] = None, 

136) -> HTMLResponse: 

137 """ 

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

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

140 

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

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

143 

144 Read more about it in the 

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

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

147 """ 

148 current_swagger_ui_parameters = swagger_ui_default_parameters.copy() 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

149 if swagger_ui_parameters: 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

150 current_swagger_ui_parameters.update(swagger_ui_parameters) 1tefg~DijkLmno

151 

152 html = f""" 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

153 <!DOCTYPE html> 

154 <html> 

155 <head> 

156 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 

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

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

159 <title>{title}</title> 

160 </head> 

161 <body> 

162 <div id="swagger-ui"> 

163 </div> 

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

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

166 <script> 

167 const ui = SwaggerUIBundle({{ 

168 url: '{openapi_url}', 

169 """ 

170 

171 for key, value in current_swagger_ui_parameters.items(): 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

172 html += f"{_html_safe_json(key)}: {_html_safe_json(jsonable_encoder(value))},\n" 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

173 

174 if oauth2_redirect_url: 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

175 html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}'," 1pqdaefguvwxyzAhbijkEFGHIlcmnoMNO

176 

177 html += """ 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

178 presets: [ 

179 SwaggerUIBundle.presets.apis, 

180 SwaggerUIBundle.SwaggerUIStandalonePreset 

181 ], 

182 })""" 

183 

184 if init_oauth: 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

185 html += f""" 2r s d abB C h J K l

186 ui.initOAuth({_html_safe_json(jsonable_encoder(init_oauth))}) 

187 """ 

188 

189 html += """ 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

190 </script> 

191 </body> 

192 </html> 

193 """ 

194 return HTMLResponse(html) 1pqPQRrstdaefguvwxyzASTUBCDhbijkEFGHIVWXJKLlcmnoMNO

195 

196 

197def get_redoc_html( 1YZ01

198 *, 

199 openapi_url: Annotated[ 

200 str, 

201 Doc( 

202 """ 

203 The OpenAPI URL that ReDoc should load and use. 

204 

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

206 `/openapi.json`. 

207 

208 Read more about it in the 

209 [FastAPI docs for Conditional OpenAPI](https://fastapi.tiangolo.com/how-to/conditional-openapi/#conditional-openapi-from-settings-and-env-vars) 

210 """ 

211 ), 

212 ], 

213 title: Annotated[ 

214 str, 

215 Doc( 

216 """ 

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

218 

219 Read more about it in the 

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

221 """ 

222 ), 

223 ], 

224 redoc_js_url: Annotated[ 

225 str, 

226 Doc( 

227 """ 

228 The URL to use to load the ReDoc JavaScript. 

229 

230 It is normally set to a CDN URL. 

231 

232 Read more about it in the 

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

234 """ 

235 ), 

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

237 redoc_favicon_url: Annotated[ 

238 str, 

239 Doc( 

240 """ 

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

242 """ 

243 ), 

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

245 with_google_fonts: Annotated[ 

246 bool, 

247 Doc( 

248 """ 

249 Load and use Google Fonts. 

250 """ 

251 ), 

252 ] = True, 

253) -> HTMLResponse: 

254 """ 

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

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

257 

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

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

260 

261 Read more about it in the 

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

263 """ 

264 html = f""" 12345a6789!#$%b'()*+,c-.

265 <!DOCTYPE html> 

266 <html> 

267 <head> 

268 <title>{title}</title> 

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

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

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

272 """ 

273 if with_google_fonts: 12345a6789!#$%b'()*+,c-.

274 html += """ 12345a6789!#$%b'()*+,c-.

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

276 """ 

277 html += f""" 12345a6789!#$%b'()*+,c-.

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

279 <!-- 

280 ReDoc doesn't change outer page styles 

281 --> 

282 <style> 

283 body {{ 

284 margin: 0; 

285 padding: 0; 

286 }} 

287 </style> 

288 </head> 

289 <body> 

290 <noscript> 

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

292 </noscript> 

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

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

295 </body> 

296 </html> 

297 """ 

298 return HTMLResponse(html) 12345a6789!#$%b'()*+,c-.

299 

300 

301def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: 1YZ01

302 """ 

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

304 

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

306 """ 

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

308 html = """ 1/:;=?@[]^_`{|}

309 <!doctype html> 

310 <html lang="en-US"> 

311 <head> 

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

313 </head> 

314 <body> 

315 <script> 

316 'use strict'; 

317 function run () { 

318 var oauth2 = window.opener.swaggerUIRedirectOauth2; 

319 var sentState = oauth2.state; 

320 var redirectUrl = oauth2.redirectUrl; 

321 var isValid, qp, arr; 

322 

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

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

325 } else { 

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

327 } 

328 

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

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

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

332 function (key, value) { 

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

334 } 

335 ) : {}; 

336 

337 isValid = qp.state === sentState; 

338 

339 if (( 

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

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

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

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

344 if (!isValid) { 

345 oauth2.errCb({ 

346 authId: oauth2.auth.name, 

347 source: "auth", 

348 level: "warning", 

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

350 }); 

351 } 

352 

353 if (qp.code) { 

354 delete oauth2.state; 

355 oauth2.auth.code = qp.code; 

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

357 } else { 

358 let oauthErrorMsg; 

359 if (qp.error) { 

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

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

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

363 } 

364 

365 oauth2.errCb({ 

366 authId: oauth2.auth.name, 

367 source: "auth", 

368 level: "error", 

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

370 }); 

371 } 

372 } else { 

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

374 } 

375 window.close(); 

376 } 

377 

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

379 run(); 

380 } else { 

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

382 run(); 

383 }); 

384 } 

385 </script> 

386 </body> 

387 </html> 

388 """ 

389 return HTMLResponse(content=html) 1/:;=?@[]^_`{|}