From: John Riebold Date: Sun, 16 Jan 2022 19:26:24 +0000 (-0800) Subject: ✨ Enable configuring Swagger UI parameters (#2568) X-Git-Tag: 0.72.0~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a85aa125d24acb225b7b722bb4f3e331a21ec267;p=thirdparty%2Ffastapi%2Ffastapi.git ✨ Enable configuring Swagger UI parameters (#2568) Co-authored-by: Artem Ivanov Co-authored-by: Sebastián Ramírez --- diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md index d8d280ba67..d1b14bc003 100644 --- a/docs/en/docs/advanced/extending-openapi.md +++ b/docs/en/docs/advanced/extending-openapi.md @@ -233,3 +233,82 @@ Now, to be able to test that everything works, create a *path operation*: Now, you should be able to disconnect your WiFi, go to your docs at http://127.0.0.1:8000/docs, and reload the page. And even without Internet, you would be able to see the docs for your API and interact with it. + +## Configuring Swagger UI + +You can configure some extra Swagger UI parameters. + +To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. + +`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly. + +FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. + +### Disable Syntax Highlighting + +For example, you could disable syntax highlighting in Swagger UI. + +Without changing the settings, syntax highlighting is enabled by default: + + + +But you can disable it by setting `syntaxHighlight` to `False`: + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial003.py!} +``` + +...and then Swagger UI won't show the syntax highlighting anymore: + + + +### Change the Theme + +The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle): + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial004.py!} +``` + +That configuration would change the syntax highlighting color theme: + + + +### Change Default Swagger UI Parameters + +FastAPI includes some default configuration parameters appropriate for most of the use cases. + +It includes these default configurations: + +```Python +{!../../../fastapi/openapi/docs.py[ln:7-13]!} +``` + +You can override any of them by setting a different value in the argument `swagger_ui_parameters`. + +For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`: + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial005.py!} +``` + +### Other Swagger UI Parameters + +To see all the other possible configurations you can use, read the official docs for Swagger UI parameters. + +### JavaScript-only settings + +Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions). + +FastAPI also includes these JavaScript-only `presets` settings: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +These are **JavaScript** objects, not strings, so you can't pass them from Python code directly. + +If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need. diff --git a/docs/en/docs/img/tutorial/extending-openapi/image02.png b/docs/en/docs/img/tutorial/extending-openapi/image02.png new file mode 100644 index 0000000000..91453fb56f Binary files /dev/null and b/docs/en/docs/img/tutorial/extending-openapi/image02.png differ diff --git a/docs/en/docs/img/tutorial/extending-openapi/image03.png b/docs/en/docs/img/tutorial/extending-openapi/image03.png new file mode 100644 index 0000000000..e45a77d519 Binary files /dev/null and b/docs/en/docs/img/tutorial/extending-openapi/image03.png differ diff --git a/docs/en/docs/img/tutorial/extending-openapi/image04.png b/docs/en/docs/img/tutorial/extending-openapi/image04.png new file mode 100644 index 0000000000..394d2bb431 Binary files /dev/null and b/docs/en/docs/img/tutorial/extending-openapi/image04.png differ diff --git a/docs_src/extending_openapi/tutorial003.py b/docs_src/extending_openapi/tutorial003.py new file mode 100644 index 0000000000..6c24ce7583 --- /dev/null +++ b/docs_src/extending_openapi/tutorial003.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(swagger_ui_parameters={"syntaxHighlight": False}) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/extending_openapi/tutorial004.py b/docs_src/extending_openapi/tutorial004.py new file mode 100644 index 0000000000..cc569ce450 --- /dev/null +++ b/docs_src/extending_openapi/tutorial004.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"}) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/extending_openapi/tutorial005.py b/docs_src/extending_openapi/tutorial005.py new file mode 100644 index 0000000000..b4449f5c6a --- /dev/null +++ b/docs_src/extending_openapi/tutorial005.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(swagger_ui_parameters={"deepLinking": False}) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/fastapi/applications.py b/fastapi/applications.py index 0c25026e2b..d71d4190ab 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -65,6 +65,7 @@ class FastAPI(Starlette): callbacks: Optional[List[BaseRoute]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, + swagger_ui_parameters: Optional[Dict[str, Any]] = None, **extra: Any, ) -> None: self._debug: bool = debug @@ -120,6 +121,7 @@ class FastAPI(Starlette): self.redoc_url = redoc_url self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url self.swagger_ui_init_oauth = swagger_ui_init_oauth + self.swagger_ui_parameters = swagger_ui_parameters self.extra = extra self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {} @@ -174,6 +176,7 @@ class FastAPI(Starlette): title=self.title + " - Swagger UI", oauth2_redirect_url=oauth2_redirect_url, init_oauth=self.swagger_ui_init_oauth, + swagger_ui_parameters=self.swagger_ui_parameters, ) self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False) diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index fd22e4e8c1..1be90d188f 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -4,6 +4,14 @@ from typing import Any, Dict, Optional from fastapi.encoders import jsonable_encoder from starlette.responses import HTMLResponse +swagger_ui_default_parameters = { + "dom_id": "#swagger-ui", + "layout": "BaseLayout", + "deepLinking": True, + "showExtensions": True, + "showCommonExtensions": True, +} + def get_swagger_ui_html( *, @@ -14,7 +22,11 @@ def get_swagger_ui_html( swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png", oauth2_redirect_url: Optional[str] = None, init_oauth: Optional[Dict[str, Any]] = None, + swagger_ui_parameters: Optional[Dict[str, Any]] = None, ) -> HTMLResponse: + current_swagger_ui_parameters = swagger_ui_default_parameters.copy() + if swagger_ui_parameters: + current_swagger_ui_parameters.update(swagger_ui_parameters) html = f""" @@ -34,19 +46,17 @@ def get_swagger_ui_html( url: '{openapi_url}', """ + for key, value in current_swagger_ui_parameters.items(): + html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n" + if oauth2_redirect_url: html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}'," html += """ - dom_id: '#swagger-ui', - presets: [ + presets: [ SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset ], - layout: "BaseLayout", - deepLinking: true, - showExtensions: true, - showCommonExtensions: true })""" if init_oauth: diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial003.py b/tests/test_tutorial/test_extending_openapi/test_tutorial003.py new file mode 100644 index 0000000000..0184dd9f83 --- /dev/null +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial003.py @@ -0,0 +1,41 @@ +from fastapi.testclient import TestClient + +from docs_src.extending_openapi.tutorial003 import app + +client = TestClient(app) + + +def test_swagger_ui(): + response = client.get("/docs") + assert response.status_code == 200, response.text + assert ( + '"syntaxHighlight": false' in response.text + ), "syntaxHighlight should be included and converted to JSON" + assert ( + '"dom_id": "#swagger-ui"' in response.text + ), "default configs should be preserved" + assert "presets: [" in response.text, "default configs should be preserved" + assert ( + "SwaggerUIBundle.presets.apis," in response.text + ), "default configs should be preserved" + assert ( + "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text + ), "default configs should be preserved" + assert ( + '"layout": "BaseLayout",' in response.text + ), "default configs should be preserved" + assert ( + '"deepLinking": true,' in response.text + ), "default configs should be preserved" + assert ( + '"showExtensions": true,' in response.text + ), "default configs should be preserved" + assert ( + '"showCommonExtensions": true,' in response.text + ), "default configs should be preserved" + + +def test_get_users(): + response = client.get("/users/foo") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello foo"} diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial004.py b/tests/test_tutorial/test_extending_openapi/test_tutorial004.py new file mode 100644 index 0000000000..4f7615126a --- /dev/null +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial004.py @@ -0,0 +1,44 @@ +from fastapi.testclient import TestClient + +from docs_src.extending_openapi.tutorial004 import app + +client = TestClient(app) + + +def test_swagger_ui(): + response = client.get("/docs") + assert response.status_code == 200, response.text + assert ( + '"syntaxHighlight": false' not in response.text + ), "not used parameters should not be included" + assert ( + '"syntaxHighlight.theme": "obsidian"' in response.text + ), "parameters with middle dots should be included in a JSON compatible way" + assert ( + '"dom_id": "#swagger-ui"' in response.text + ), "default configs should be preserved" + assert "presets: [" in response.text, "default configs should be preserved" + assert ( + "SwaggerUIBundle.presets.apis," in response.text + ), "default configs should be preserved" + assert ( + "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text + ), "default configs should be preserved" + assert ( + '"layout": "BaseLayout",' in response.text + ), "default configs should be preserved" + assert ( + '"deepLinking": true,' in response.text + ), "default configs should be preserved" + assert ( + '"showExtensions": true,' in response.text + ), "default configs should be preserved" + assert ( + '"showCommonExtensions": true,' in response.text + ), "default configs should be preserved" + + +def test_get_users(): + response = client.get("/users/foo") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello foo"} diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial005.py b/tests/test_tutorial/test_extending_openapi/test_tutorial005.py new file mode 100644 index 0000000000..24aeb93db3 --- /dev/null +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial005.py @@ -0,0 +1,44 @@ +from fastapi.testclient import TestClient + +from docs_src.extending_openapi.tutorial005 import app + +client = TestClient(app) + + +def test_swagger_ui(): + response = client.get("/docs") + assert response.status_code == 200, response.text + assert ( + '"deepLinking": false,' in response.text + ), "overridden configs should be preserved" + assert ( + '"deepLinking": true' not in response.text + ), "overridden configs should not include the old value" + assert ( + '"syntaxHighlight": false' not in response.text + ), "not used parameters should not be included" + assert ( + '"dom_id": "#swagger-ui"' in response.text + ), "default configs should be preserved" + assert "presets: [" in response.text, "default configs should be preserved" + assert ( + "SwaggerUIBundle.presets.apis," in response.text + ), "default configs should be preserved" + assert ( + "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text + ), "default configs should be preserved" + assert ( + '"layout": "BaseLayout",' in response.text + ), "default configs should be preserved" + assert ( + '"showExtensions": true,' in response.text + ), "default configs should be preserved" + assert ( + '"showCommonExtensions": true,' in response.text + ), "default configs should be preserved" + + +def test_get_users(): + response = client.get("/users/foo") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello foo"}