From d3193451060e3e9005cbffc2e7d1f5b06db744c0 Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Wed, 6 Mar 2024 10:34:23 -0500 Subject: [PATCH 01/12] Add test for self-hosted docs --- tests/test_offline_docs/__init__.py | 0 tests/test_offline_docs/static/swagger.css | 1 + tests/test_offline_docs/static/swagger.js | 3 ++ .../test_root_path_with_static_mount.py | 51 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 tests/test_offline_docs/__init__.py create mode 100644 tests/test_offline_docs/static/swagger.css create mode 100644 tests/test_offline_docs/static/swagger.js create mode 100644 tests/test_offline_docs/test_root_path_with_static_mount.py diff --git a/tests/test_offline_docs/__init__.py b/tests/test_offline_docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_offline_docs/static/swagger.css b/tests/test_offline_docs/static/swagger.css new file mode 100644 index 000000000..9f365baaa --- /dev/null +++ b/tests/test_offline_docs/static/swagger.css @@ -0,0 +1 @@ +.swagger-ui{color:#3b4151;} \ No newline at end of file diff --git a/tests/test_offline_docs/static/swagger.js b/tests/test_offline_docs/static/swagger.js new file mode 100644 index 000000000..7cc06f2f8 --- /dev/null +++ b/tests/test_offline_docs/static/swagger.js @@ -0,0 +1,3 @@ +function foo() { + return "foo" +} \ No newline at end of file diff --git a/tests/test_offline_docs/test_root_path_with_static_mount.py b/tests/test_offline_docs/test_root_path_with_static_mount.py new file mode 100644 index 000000000..97d00fe42 --- /dev/null +++ b/tests/test_offline_docs/test_root_path_with_static_mount.py @@ -0,0 +1,51 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from fastapi.openapi.docs import get_swagger_ui_html +from fastapi.testclient import TestClient + +root_path = "/api" + +app = FastAPI( + title="FastAPI", + root_path=root_path, + docs_url=None, + redoc_url=None, +) + +app.mount("/static", StaticFiles(directory="tests/test_offline_docs/static"), name="static") + +@app.get("/") +async def custom_swagger_ui_html(): + """ + Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. + """ + + return get_swagger_ui_html( + openapi_url="/openapi.json", + title=app.title, + swagger_js_url="/static/swagger.js", + swagger_css_url="/static/swagger.css", + ) + + +client = TestClient(app) + +def test_static_assets(): + response = client.get("/") + assert response.status_code == 200 + assert response.headers["Content-Type"] == "text/html; charset=utf-8" + swagger_html = response.text + + response = client.get("/openapi.json") + assert response.status_code == 200 + + response = client.get("/static/swagger.js") + assert response.status_code == 200 + + response = client.get("/static/swagger.css") + assert response.status_code == 200 + + assert "/static/swagger.js" in swagger_html + assert "/static/swagger.css" in swagger_html + + From 329bffa38f28ca79c2f091fb0fd160341039eb9f Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Mon, 23 Jun 2025 22:49:22 -0400 Subject: [PATCH 02/12] Add tests for root_path with offline docs --- docs_src/custom_docs_ui/tutorial002.py | 18 ++- tests/test_offline_docs/static/redoc.js | 3 + .../test_root_path_with_static_mount.py | 149 ++++++++++++++---- 3 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 tests/test_offline_docs/static/redoc.js diff --git a/docs_src/custom_docs_ui/tutorial002.py b/docs_src/custom_docs_ui/tutorial002.py index 23ea368f8..9925d5236 100644 --- a/docs_src/custom_docs_ui/tutorial002.py +++ b/docs_src/custom_docs_ui/tutorial002.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Request from fastapi.openapi.docs import ( get_redoc_html, get_swagger_ui_html, @@ -12,13 +12,14 @@ app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): +async def custom_swagger_ui_html(req: Request): + root_path = req.scope.get("root_path", "").rstrip("/") return get_swagger_ui_html( - openapi_url=app.openapi_url, + openapi_url=f"{root_path}/{app.openapi_url}", title=app.title + " - Swagger UI", oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, - swagger_js_url="/static/swagger-ui-bundle.js", - swagger_css_url="/static/swagger-ui.css", + swagger_js_url=f"{root_path}/static/swagger-ui-bundle.js", + swagger_css_url=f"{root_path}/static/swagger-ui.css", ) @@ -28,11 +29,12 @@ async def swagger_ui_redirect(): @app.get("/redoc", include_in_schema=False) -async def redoc_html(): +async def redoc_html(req: Request): + root_path = req.scope.get("root_path", "").rstrip("/") return get_redoc_html( - openapi_url=app.openapi_url, + openapi_url=f"{root_path}/{app.openapi_url}", title=app.title + " - ReDoc", - redoc_js_url="/static/redoc.standalone.js", + redoc_js_url=f"{root_path}/static/redoc.standalone.js", ) diff --git a/tests/test_offline_docs/static/redoc.js b/tests/test_offline_docs/static/redoc.js new file mode 100644 index 000000000..f05278909 --- /dev/null +++ b/tests/test_offline_docs/static/redoc.js @@ -0,0 +1,3 @@ +function bar() { + return "bar" +} \ No newline at end of file diff --git a/tests/test_offline_docs/test_root_path_with_static_mount.py b/tests/test_offline_docs/test_root_path_with_static_mount.py index 97d00fe42..ebe203492 100644 --- a/tests/test_offline_docs/test_root_path_with_static_mount.py +++ b/tests/test_offline_docs/test_root_path_with_static_mount.py @@ -1,36 +1,62 @@ -from fastapi import FastAPI +import pytest + +from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles -from fastapi.openapi.docs import get_swagger_ui_html +from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html from fastapi.testclient import TestClient -root_path = "/api" -app = FastAPI( - title="FastAPI", - root_path=root_path, - docs_url=None, - redoc_url=None, +@pytest.mark.parametrize( + ["root_path", "using_test_client"], + [ + ("/api", True), + ("/api", False), + ("", True), + ("", False), + ] ) - -app.mount("/static", StaticFiles(directory="tests/test_offline_docs/static"), name="static") - -@app.get("/") -async def custom_swagger_ui_html(): - """ - Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. - """ - - return get_swagger_ui_html( - openapi_url="/openapi.json", - title=app.title, - swagger_js_url="/static/swagger.js", - swagger_css_url="/static/swagger.css", +def test_swagger_docs_with_static_assets( + root_path: str, + using_test_client: bool, +): + app_kwargs = {} + client_kwargs = {} + if not using_test_client: + app_kwargs = { + "root_path": root_path + } + if using_test_client: + client_kwargs = { + "root_path": root_path + } + + app = FastAPI( + title="FastAPI", + docs_url=None, + redoc_url=None, + **app_kwargs, ) + + app.mount("/static", StaticFiles(directory="tests/test_offline_docs/static"), name="static") + + @app.get("/") + async def custom_swagger_ui_html( + req: Request + ): + """ + Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. + """ + root_path = req.scope.get("root_path", "").rstrip("/") + return get_swagger_ui_html( + openapi_url=f"{root_path}/openapi.json", + title=app.title, + swagger_js_url=f"{root_path}/static/swagger.js", + swagger_css_url=f"{root_path}/static/swagger.css", + ) -client = TestClient(app) + client = TestClient(app, **client_kwargs) -def test_static_assets(): response = client.get("/") assert response.status_code == 200 assert response.headers["Content-Type"] == "text/html; charset=utf-8" @@ -39,13 +65,80 @@ def test_static_assets(): response = client.get("/openapi.json") assert response.status_code == 200 - response = client.get("/static/swagger.js") + response = client.get(f"{root_path}/openapi.json") + assert response.status_code == 200 + + response = client.get(f"{root_path}/static/swagger.js") assert response.status_code == 200 - response = client.get("/static/swagger.css") + response = client.get(f"{root_path}/static/swagger.css") assert response.status_code == 200 - assert "/static/swagger.js" in swagger_html - assert "/static/swagger.css" in swagger_html + assert f"{root_path}/static/swagger.js" in swagger_html + assert f"{root_path}/static/swagger.css" in swagger_html + + +@pytest.mark.parametrize( + ["root_path", "using_test_client"], + [ + ("/api", True), + ("/api", False), + ("", True), + ("", False), + ] +) +def test_redoc_docs_with_static_assets( + root_path: str, + using_test_client: bool, +): + app_kwargs = {} + client_kwargs = {} + if not using_test_client: + app_kwargs = { + "root_path": root_path + } + if using_test_client: + client_kwargs = { + "root_path": root_path + } + + app = FastAPI( + title="FastAPI", + docs_url=None, + redoc_url=None, + **app_kwargs, + ) + + app.mount("/static", StaticFiles(directory="tests/test_offline_docs/static"), name="static") + + @app.get("/") + async def custom_redoc_html( + req: Request + ): + """ + Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. + """ + root_path = req.scope.get("root_path", "").rstrip("/") + return get_redoc_html( + openapi_url=f"{root_path}/openapi.json", + title=app.title, + redoc_js_url=f"{root_path}/static/redoc.js", + ) + client = TestClient(app, **client_kwargs) + + response = client.get("/") + assert response.status_code == 200 + assert response.headers["Content-Type"] == "text/html; charset=utf-8" + redoc_html = response.text + + response = client.get("/openapi.json") + assert response.status_code == 200 + + response = client.get(f"{root_path}/openapi.json") + assert response.status_code == 200 + + response = client.get(f"{root_path}/static/redoc.js") + assert response.status_code == 200 + assert f"{root_path}/static/redoc.js" in redoc_html \ No newline at end of file From 0de2e7c3e925c9d64a7ad17004e7e940217c641f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 03:00:38 +0000 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_offline_docs/static/redoc.js | 2 +- tests/test_offline_docs/static/swagger.css | 2 +- tests/test_offline_docs/static/swagger.js | 2 +- .../test_root_path_with_static_mount.py | 54 +++++++++---------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/tests/test_offline_docs/static/redoc.js b/tests/test_offline_docs/static/redoc.js index f05278909..faba2abd5 100644 --- a/tests/test_offline_docs/static/redoc.js +++ b/tests/test_offline_docs/static/redoc.js @@ -1,3 +1,3 @@ function bar() { return "bar" -} \ No newline at end of file +} diff --git a/tests/test_offline_docs/static/swagger.css b/tests/test_offline_docs/static/swagger.css index 9f365baaa..310063de6 100644 --- a/tests/test_offline_docs/static/swagger.css +++ b/tests/test_offline_docs/static/swagger.css @@ -1 +1 @@ -.swagger-ui{color:#3b4151;} \ No newline at end of file +.swagger-ui{color:#3b4151;} diff --git a/tests/test_offline_docs/static/swagger.js b/tests/test_offline_docs/static/swagger.js index 7cc06f2f8..23c5b08a4 100644 --- a/tests/test_offline_docs/static/swagger.js +++ b/tests/test_offline_docs/static/swagger.js @@ -1,3 +1,3 @@ function foo() { return "foo" -} \ No newline at end of file +} diff --git a/tests/test_offline_docs/test_root_path_with_static_mount.py b/tests/test_offline_docs/test_root_path_with_static_mount.py index ebe203492..312ae771c 100644 --- a/tests/test_offline_docs/test_root_path_with_static_mount.py +++ b/tests/test_offline_docs/test_root_path_with_static_mount.py @@ -1,8 +1,7 @@ import pytest - from fastapi import FastAPI, Request +from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html from fastapi.staticfiles import StaticFiles -from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html from fastapi.testclient import TestClient @@ -13,7 +12,7 @@ from fastapi.testclient import TestClient ("/api", False), ("", True), ("", False), - ] + ], ) def test_swagger_docs_with_static_assets( root_path: str, @@ -22,27 +21,25 @@ def test_swagger_docs_with_static_assets( app_kwargs = {} client_kwargs = {} if not using_test_client: - app_kwargs = { - "root_path": root_path - } + app_kwargs = {"root_path": root_path} if using_test_client: - client_kwargs = { - "root_path": root_path - } - + client_kwargs = {"root_path": root_path} + app = FastAPI( title="FastAPI", docs_url=None, redoc_url=None, **app_kwargs, ) - - app.mount("/static", StaticFiles(directory="tests/test_offline_docs/static"), name="static") + + app.mount( + "/static", + StaticFiles(directory="tests/test_offline_docs/static"), + name="static", + ) @app.get("/") - async def custom_swagger_ui_html( - req: Request - ): + async def custom_swagger_ui_html(req: Request): """ Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. """ @@ -54,7 +51,6 @@ def test_swagger_docs_with_static_assets( swagger_css_url=f"{root_path}/static/swagger.css", ) - client = TestClient(app, **client_kwargs) response = client.get("/") @@ -85,7 +81,7 @@ def test_swagger_docs_with_static_assets( ("/api", False), ("", True), ("", False), - ] + ], ) def test_redoc_docs_with_static_assets( root_path: str, @@ -94,27 +90,25 @@ def test_redoc_docs_with_static_assets( app_kwargs = {} client_kwargs = {} if not using_test_client: - app_kwargs = { - "root_path": root_path - } + app_kwargs = {"root_path": root_path} if using_test_client: - client_kwargs = { - "root_path": root_path - } - + client_kwargs = {"root_path": root_path} + app = FastAPI( title="FastAPI", docs_url=None, redoc_url=None, **app_kwargs, ) - - app.mount("/static", StaticFiles(directory="tests/test_offline_docs/static"), name="static") + + app.mount( + "/static", + StaticFiles(directory="tests/test_offline_docs/static"), + name="static", + ) @app.get("/") - async def custom_redoc_html( - req: Request - ): + async def custom_redoc_html(req: Request): """ Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. """ @@ -141,4 +135,4 @@ def test_redoc_docs_with_static_assets( response = client.get(f"{root_path}/static/redoc.js") assert response.status_code == 200 - assert f"{root_path}/static/redoc.js" in redoc_html \ No newline at end of file + assert f"{root_path}/static/redoc.js" in redoc_html From 5faf2ed451eb8498c59c8894734bd4bdd5ca7368 Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Mon, 23 Jun 2025 23:04:30 -0400 Subject: [PATCH 04/12] Fix a missing param in the docs --- docs_src/custom_docs_ui/tutorial002.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs_src/custom_docs_ui/tutorial002.py b/docs_src/custom_docs_ui/tutorial002.py index 9925d5236..953098dd7 100644 --- a/docs_src/custom_docs_ui/tutorial002.py +++ b/docs_src/custom_docs_ui/tutorial002.py @@ -17,7 +17,7 @@ async def custom_swagger_ui_html(req: Request): return get_swagger_ui_html( openapi_url=f"{root_path}/{app.openapi_url}", title=app.title + " - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + oauth2_redirect_url=f"{root_path}/{app.swagger_ui_oauth2_redirect_url}", swagger_js_url=f"{root_path}/static/swagger-ui-bundle.js", swagger_css_url=f"{root_path}/static/swagger-ui.css", ) From a63a0e8093ecfa505d5312bedfc901a2147a0c8a Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Mon, 23 Jun 2025 23:11:59 -0400 Subject: [PATCH 05/12] Fix comment in test --- tests/test_offline_docs/test_root_path_with_static_mount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_offline_docs/test_root_path_with_static_mount.py b/tests/test_offline_docs/test_root_path_with_static_mount.py index 312ae771c..2def4e112 100644 --- a/tests/test_offline_docs/test_root_path_with_static_mount.py +++ b/tests/test_offline_docs/test_root_path_with_static_mount.py @@ -110,7 +110,7 @@ def test_redoc_docs_with_static_assets( @app.get("/") async def custom_redoc_html(req: Request): """ - Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. + Sets up a localized version of the Redoc docs that can be run without assets from the Internet. """ root_path = req.scope.get("root_path", "").rstrip("/") return get_redoc_html( From 75050069ab2187cb7beff537eb65e23178160ccf Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Tue, 24 Jun 2025 09:11:09 -0400 Subject: [PATCH 06/12] Update custom_docs_ui tests instead --- docs_src/custom_docs_ui/tutorial002.py | 12 +- tests/test_offline_docs/__init__.py | 0 tests/test_offline_docs/static/redoc.js | 3 - tests/test_offline_docs/static/swagger.css | 1 - tests/test_offline_docs/static/swagger.js | 3 - .../test_root_path_with_static_mount.py | 138 ------------------ .../test_custom_docs_ui/test_tutorial002.py | 18 ++- 7 files changed, 23 insertions(+), 152 deletions(-) delete mode 100644 tests/test_offline_docs/__init__.py delete mode 100644 tests/test_offline_docs/static/redoc.js delete mode 100644 tests/test_offline_docs/static/swagger.css delete mode 100644 tests/test_offline_docs/static/swagger.js delete mode 100644 tests/test_offline_docs/test_root_path_with_static_mount.py diff --git a/docs_src/custom_docs_ui/tutorial002.py b/docs_src/custom_docs_ui/tutorial002.py index 953098dd7..91df12710 100644 --- a/docs_src/custom_docs_ui/tutorial002.py +++ b/docs_src/custom_docs_ui/tutorial002.py @@ -1,3 +1,5 @@ +import os + from fastapi import FastAPI, Request from fastapi.openapi.docs import ( get_redoc_html, @@ -6,7 +8,9 @@ from fastapi.openapi.docs import ( ) from fastapi.staticfiles import StaticFiles -app = FastAPI(docs_url=None, redoc_url=None) +root_path = os.getenv("ROOT_PATH", "") + +app = FastAPI(docs_url=None, redoc_url=None, root_path=root_path) app.mount("/static", StaticFiles(directory="static"), name="static") @@ -15,9 +19,9 @@ app.mount("/static", StaticFiles(directory="static"), name="static") async def custom_swagger_ui_html(req: Request): root_path = req.scope.get("root_path", "").rstrip("/") return get_swagger_ui_html( - openapi_url=f"{root_path}/{app.openapi_url}", + openapi_url=f"{root_path}{app.openapi_url}", title=app.title + " - Swagger UI", - oauth2_redirect_url=f"{root_path}/{app.swagger_ui_oauth2_redirect_url}", + oauth2_redirect_url=f"{root_path}{app.swagger_ui_oauth2_redirect_url}", swagger_js_url=f"{root_path}/static/swagger-ui-bundle.js", swagger_css_url=f"{root_path}/static/swagger-ui.css", ) @@ -32,7 +36,7 @@ async def swagger_ui_redirect(): async def redoc_html(req: Request): root_path = req.scope.get("root_path", "").rstrip("/") return get_redoc_html( - openapi_url=f"{root_path}/{app.openapi_url}", + openapi_url=f"{root_path}{app.openapi_url}", title=app.title + " - ReDoc", redoc_js_url=f"{root_path}/static/redoc.standalone.js", ) diff --git a/tests/test_offline_docs/__init__.py b/tests/test_offline_docs/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_offline_docs/static/redoc.js b/tests/test_offline_docs/static/redoc.js deleted file mode 100644 index faba2abd5..000000000 --- a/tests/test_offline_docs/static/redoc.js +++ /dev/null @@ -1,3 +0,0 @@ -function bar() { - return "bar" -} diff --git a/tests/test_offline_docs/static/swagger.css b/tests/test_offline_docs/static/swagger.css deleted file mode 100644 index 310063de6..000000000 --- a/tests/test_offline_docs/static/swagger.css +++ /dev/null @@ -1 +0,0 @@ -.swagger-ui{color:#3b4151;} diff --git a/tests/test_offline_docs/static/swagger.js b/tests/test_offline_docs/static/swagger.js deleted file mode 100644 index 23c5b08a4..000000000 --- a/tests/test_offline_docs/static/swagger.js +++ /dev/null @@ -1,3 +0,0 @@ -function foo() { - return "foo" -} diff --git a/tests/test_offline_docs/test_root_path_with_static_mount.py b/tests/test_offline_docs/test_root_path_with_static_mount.py deleted file mode 100644 index 2def4e112..000000000 --- a/tests/test_offline_docs/test_root_path_with_static_mount.py +++ /dev/null @@ -1,138 +0,0 @@ -import pytest -from fastapi import FastAPI, Request -from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html -from fastapi.staticfiles import StaticFiles -from fastapi.testclient import TestClient - - -@pytest.mark.parametrize( - ["root_path", "using_test_client"], - [ - ("/api", True), - ("/api", False), - ("", True), - ("", False), - ], -) -def test_swagger_docs_with_static_assets( - root_path: str, - using_test_client: bool, -): - app_kwargs = {} - client_kwargs = {} - if not using_test_client: - app_kwargs = {"root_path": root_path} - if using_test_client: - client_kwargs = {"root_path": root_path} - - app = FastAPI( - title="FastAPI", - docs_url=None, - redoc_url=None, - **app_kwargs, - ) - - app.mount( - "/static", - StaticFiles(directory="tests/test_offline_docs/static"), - name="static", - ) - - @app.get("/") - async def custom_swagger_ui_html(req: Request): - """ - Sets up a localized version of the Swagger (OpenAPI) docs that can be run without assets from the Internet. - """ - root_path = req.scope.get("root_path", "").rstrip("/") - return get_swagger_ui_html( - openapi_url=f"{root_path}/openapi.json", - title=app.title, - swagger_js_url=f"{root_path}/static/swagger.js", - swagger_css_url=f"{root_path}/static/swagger.css", - ) - - client = TestClient(app, **client_kwargs) - - response = client.get("/") - assert response.status_code == 200 - assert response.headers["Content-Type"] == "text/html; charset=utf-8" - swagger_html = response.text - - response = client.get("/openapi.json") - assert response.status_code == 200 - - response = client.get(f"{root_path}/openapi.json") - assert response.status_code == 200 - - response = client.get(f"{root_path}/static/swagger.js") - assert response.status_code == 200 - - response = client.get(f"{root_path}/static/swagger.css") - assert response.status_code == 200 - - assert f"{root_path}/static/swagger.js" in swagger_html - assert f"{root_path}/static/swagger.css" in swagger_html - - -@pytest.mark.parametrize( - ["root_path", "using_test_client"], - [ - ("/api", True), - ("/api", False), - ("", True), - ("", False), - ], -) -def test_redoc_docs_with_static_assets( - root_path: str, - using_test_client: bool, -): - app_kwargs = {} - client_kwargs = {} - if not using_test_client: - app_kwargs = {"root_path": root_path} - if using_test_client: - client_kwargs = {"root_path": root_path} - - app = FastAPI( - title="FastAPI", - docs_url=None, - redoc_url=None, - **app_kwargs, - ) - - app.mount( - "/static", - StaticFiles(directory="tests/test_offline_docs/static"), - name="static", - ) - - @app.get("/") - async def custom_redoc_html(req: Request): - """ - Sets up a localized version of the Redoc docs that can be run without assets from the Internet. - """ - root_path = req.scope.get("root_path", "").rstrip("/") - return get_redoc_html( - openapi_url=f"{root_path}/openapi.json", - title=app.title, - redoc_js_url=f"{root_path}/static/redoc.js", - ) - - client = TestClient(app, **client_kwargs) - - response = client.get("/") - assert response.status_code == 200 - assert response.headers["Content-Type"] == "text/html; charset=utf-8" - redoc_html = response.text - - response = client.get("/openapi.json") - assert response.status_code == 200 - - response = client.get(f"{root_path}/openapi.json") - assert response.status_code == 200 - - response = client.get(f"{root_path}/static/redoc.js") - assert response.status_code == 200 - - assert f"{root_path}/static/redoc.js" in redoc_html diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index 712618807..19551173c 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -4,24 +4,33 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient +root_path = "/api" + @pytest.fixture(scope="module") def client(): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) + os.environ["ROOT_PATH"] = root_path from docs_src.custom_docs_ui.tutorial002 import app with TestClient(app) as client: yield client + + os.environ.pop("ROOT_PATH", None) static_dir.rmdir() def test_swagger_ui_html(client: TestClient): response = client.get("/docs") assert response.status_code == 200, response.text - assert "/static/swagger-ui-bundle.js" in response.text - assert "/static/swagger-ui.css" in response.text + assert f"{root_path}/static/swagger-ui-bundle.js" in response.text + assert f"{root_path}/static/swagger-ui.css" in response.text + assert f"{root_path}/docs/oauth2-redirect" in response.text + + response = client.get(f"{root_path}/openapi.json") + assert response.status_code == 200 def test_swagger_ui_oauth2_redirect_html(client: TestClient): @@ -33,7 +42,10 @@ def test_swagger_ui_oauth2_redirect_html(client: TestClient): def test_redoc_html(client: TestClient): response = client.get("/redoc") assert response.status_code == 200, response.text - assert "/static/redoc.standalone.js" in response.text + assert f"{root_path}/static/redoc.standalone.js" in response.text + + response = client.get(f"{root_path}/openapi.json") + assert response.status_code == 200 def test_api(client: TestClient): From cb3ed45236085fe43d2f4c089bbce04a7ed49910 Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Tue, 24 Jun 2025 18:00:39 -0400 Subject: [PATCH 07/12] Update highlights --- docs/en/docs/how-to/custom-docs-ui-assets.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/docs/how-to/custom-docs-ui-assets.md b/docs/en/docs/how-to/custom-docs-ui-assets.md index 9d2238e4f..37c541d7f 100644 --- a/docs/en/docs/how-to/custom-docs-ui-assets.md +++ b/docs/en/docs/how-to/custom-docs-ui-assets.md @@ -118,7 +118,7 @@ After that, your file structure could look like: * Import `StaticFiles`. * "Mount" a `StaticFiles()` instance in a specific path. -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9,15] *} ### Test the static files @@ -144,7 +144,7 @@ The same as when using a custom CDN, the first step is to disable the automatic To disable them, set their URLs to `None` when creating your `FastAPI` app: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[13] *} ### Include the custom docs for static files @@ -160,7 +160,7 @@ Again, you can reuse FastAPI's internal functions to create the HTML pages for t And similarly for ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[4:8,18:27,30:32,35:42] *} /// tip @@ -176,7 +176,7 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect Now, to be able to test that everything works, create a *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[45:47] *} ### Test Static Files UI From 1ea085e5135cd4bcd3ab8c2d051cc791c7a8104c Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Tue, 24 Jun 2025 18:10:58 -0400 Subject: [PATCH 08/12] Update other highlighting sections --- docs/de/docs/how-to/custom-docs-ui-assets.md | 8 ++++---- docs/es/docs/how-to/custom-docs-ui-assets.md | 8 ++++---- docs/pt/docs/how-to/custom-docs-ui-assets.md | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/de/docs/how-to/custom-docs-ui-assets.md b/docs/de/docs/how-to/custom-docs-ui-assets.md index f68902b99..4ec77e47f 100644 --- a/docs/de/docs/how-to/custom-docs-ui-assets.md +++ b/docs/de/docs/how-to/custom-docs-ui-assets.md @@ -118,7 +118,7 @@ Danach könnte Ihre Dateistruktur wie folgt aussehen: * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9,15] *} ### Die statischen Dateien testen @@ -144,7 +144,7 @@ Wie bei der Verwendung eines benutzerdefinierten CDN besteht der erste Schritt d Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[13] *} ### Die benutzerdefinierten Dokumentationen, mit statischen Dateien, hinzufügen @@ -160,7 +160,7 @@ Auch hier können Sie die internen Funktionen von FastAPI wiederverwenden, um di Und genau so für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[4:8,18:27,30:32,35:42] *} /// tip | Tipp @@ -176,7 +176,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[45:47] *} ### Benutzeroberfläche, mit statischen Dateien, testen diff --git a/docs/es/docs/how-to/custom-docs-ui-assets.md b/docs/es/docs/how-to/custom-docs-ui-assets.md index 0a03ff330..c536cdc40 100644 --- a/docs/es/docs/how-to/custom-docs-ui-assets.md +++ b/docs/es/docs/how-to/custom-docs-ui-assets.md @@ -118,7 +118,7 @@ Después de eso, tu estructura de archivos podría verse así: * Importa `StaticFiles`. * "Monta" una instance de `StaticFiles()` en un path específico. -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9,15] *} ### Prueba los archivos estáticos @@ -144,7 +144,7 @@ Igual que cuando usas un CDN personalizado, el primer paso es desactivar la docu Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[13] *} ### Incluye la documentación personalizada para archivos estáticos @@ -160,7 +160,7 @@ Nuevamente, puedes reutilizar las funciones internas de FastAPI para crear las p Y de manera similar para ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[4:8,18:27,30:32,35:42] *} /// tip | Consejo @@ -176,7 +176,7 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d Ahora, para poder probar que todo funciona, crea una *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[45:47] *} ### Prueba la UI de Archivos Estáticos diff --git a/docs/pt/docs/how-to/custom-docs-ui-assets.md b/docs/pt/docs/how-to/custom-docs-ui-assets.md index b7de6c8bd..d994e9844 100644 --- a/docs/pt/docs/how-to/custom-docs-ui-assets.md +++ b/docs/pt/docs/how-to/custom-docs-ui-assets.md @@ -118,7 +118,7 @@ Depois disso, sua estrutura de arquivos deve se parecer com: * Importe `StaticFiles`. * "Monte" a instância `StaticFiles()` em um caminho específico. -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9,15] *} ### Teste os arquivos estáticos @@ -144,7 +144,7 @@ Da mesma forma que ao usar um CDN personalizado, o primeiro passo é desativar a Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[13] *} ### Incluir a documentação personalizada para arquivos estáticos @@ -160,7 +160,7 @@ Novamente, você pode reutilizar as funções internas do FastAPI para criar as E de forma semelhante para o ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[4:8,18:27,30:32,35:42] *} /// tip | Dica @@ -176,7 +176,7 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux Agora, para poder testar se tudo funciona, crie uma *operação de rota*: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[45:47] *} ### Teste a UI de Arquivos Estáticos From d7029b2d71a68143c068daa4f1162dd58b769b57 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Sun, 6 Jul 2025 21:53:05 +0200 Subject: [PATCH 09/12] Fix and improve tests for `dics_src/custom_docs_ui/tutorial002.py` --- .../test_custom_docs_ui/test_tutorial002.py | 80 ++++++++++++++----- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index 19551173c..c6e8083e8 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -1,54 +1,94 @@ +import importlib import os from pathlib import Path +from typing import TypedDict import pytest from fastapi.testclient import TestClient -root_path = "/api" - - -@pytest.fixture(scope="module") -def client(): +ROOT_PATH = "/api" + + +class Params(TypedDict): + app_root_path: str + asgi_root_path: str + request_prefix: str + + +@pytest.fixture( + params=[ + Params(app_root_path="", asgi_root_path="", request_prefix=""), + Params(app_root_path="/api", asgi_root_path="", request_prefix="/api"), + Params(app_root_path="/api", asgi_root_path="", request_prefix=""), + Params(app_root_path="", asgi_root_path="/api", request_prefix="/api"), + Params(app_root_path="", asgi_root_path="/api", request_prefix=""), + ], + ids=[ + "Without root_path, request without prefix", + "FastAPI(root_path=root_path), request with prefix", + "FastAPI(root_path=root_path), request without prefix", + "TestClient(root_path=root_path), request with prefix", + "TestClient(root_path=root_path), request without prefix", + ], +) +def params(request: pytest.FixtureRequest): + return request.param + + +@pytest.fixture() +def client(params: Params, monkeypatch): static_dir: Path = Path(os.getcwd()) / "static" - print(static_dir) static_dir.mkdir(exist_ok=True) - os.environ["ROOT_PATH"] = root_path - from docs_src.custom_docs_ui.tutorial002 import app - with TestClient(app) as client: + monkeypatch.setenv("ROOT_PATH", params["app_root_path"]) + from docs_src.custom_docs_ui import tutorial002 + importlib.reload(tutorial002) + app = tutorial002.app + + with TestClient(app, root_path=params["asgi_root_path"]) as client: yield client - os.environ.pop("ROOT_PATH", None) static_dir.rmdir() -def test_swagger_ui_html(client: TestClient): - response = client.get("/docs") +def test_swagger_ui_html(client: TestClient, params: Params): + request_prefix = params["request_prefix"] + root_path = params["app_root_path"] or params["asgi_root_path"] + + response = client.get(f"{request_prefix}/docs") assert response.status_code == 200, response.text assert f"{root_path}/static/swagger-ui-bundle.js" in response.text assert f"{root_path}/static/swagger-ui.css" in response.text assert f"{root_path}/docs/oauth2-redirect" in response.text - response = client.get(f"{root_path}/openapi.json") + response = client.get(f"{request_prefix}/openapi.json") assert response.status_code == 200 -def test_swagger_ui_oauth2_redirect_html(client: TestClient): - response = client.get("/docs/oauth2-redirect") +def test_swagger_ui_oauth2_redirect_html(client: TestClient, params: Params): + request_prefix = params["request_prefix"] + + response = client.get(f"{request_prefix}/docs/oauth2-redirect") assert response.status_code == 200, response.text assert "window.opener.swaggerUIRedirectOauth2" in response.text -def test_redoc_html(client: TestClient): - response = client.get("/redoc") +def test_redoc_html(client: TestClient, params: Params): + request_prefix = params["request_prefix"] + root_path = params["app_root_path"] or params["asgi_root_path"] + + response = client.get(f"{request_prefix}/redoc") + assert response.status_code == 200, response.text assert f"{root_path}/static/redoc.standalone.js" in response.text - response = client.get(f"{root_path}/openapi.json") + response = client.get(f"{request_prefix}/openapi.json") assert response.status_code == 200 -def test_api(client: TestClient): - response = client.get("/users/john") +def test_api(client: TestClient, params: Params): + request_prefix = params["request_prefix"] + + response = client.get(f"{request_prefix}/users/john") assert response.status_code == 200, response.text assert response.json()["message"] == "Hello john" From 268583552313cd3516ef2c8479204208d6af1909 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Jul 2025 19:53:52 +0000 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index c6e8083e8..6b547c062 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -42,6 +42,7 @@ def client(params: Params, monkeypatch): monkeypatch.setenv("ROOT_PATH", params["app_root_path"]) from docs_src.custom_docs_ui import tutorial002 + importlib.reload(tutorial002) app = tutorial002.app From 393d02a30723f1e25764a188fb8123ceb967b611 Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Thu, 10 Jul 2025 21:38:55 -0400 Subject: [PATCH 11/12] Update custom_docs_ui/tutorial001 and tests --- docs/de/docs/how-to/custom-docs-ui-assets.md | 6 +- docs/en/docs/how-to/custom-docs-ui-assets.md | 6 +- docs/es/docs/how-to/custom-docs-ui-assets.md | 6 +- docs/pt/docs/how-to/custom-docs-ui-assets.md | 6 +- docs_src/custom_docs_ui/tutorial001.py | 20 +++-- .../test_custom_docs_ui/test_tutorial001.py | 73 ++++++++++++++++--- .../test_custom_docs_ui/test_tutorial002.py | 2 - 7 files changed, 86 insertions(+), 33 deletions(-) diff --git a/docs/de/docs/how-to/custom-docs-ui-assets.md b/docs/de/docs/how-to/custom-docs-ui-assets.md index 4ec77e47f..342b66460 100644 --- a/docs/de/docs/how-to/custom-docs-ui-assets.md +++ b/docs/de/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Der erste Schritt besteht darin, die automatischen Dokumentationen zu deaktivier Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} ### Die benutzerdefinierten Dokumentationen hinzufügen @@ -34,7 +34,7 @@ Sie können die internen Funktionen von FastAPI wiederverwenden, um die HTML-Sei Und genau so für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} /// tip | Tipp @@ -50,7 +50,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} ### Es ausprobieren diff --git a/docs/en/docs/how-to/custom-docs-ui-assets.md b/docs/en/docs/how-to/custom-docs-ui-assets.md index 37c541d7f..7bc0f441f 100644 --- a/docs/en/docs/how-to/custom-docs-ui-assets.md +++ b/docs/en/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ The first step is to disable the automatic docs, as by default, those use the de To disable them, set their URLs to `None` when creating your `FastAPI` app: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} ### Include the custom docs @@ -34,7 +34,7 @@ You can reuse FastAPI's internal functions to create the HTML pages for the docs And similarly for ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} /// tip @@ -50,7 +50,7 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect Now, to be able to test that everything works, create a *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} ### Test it diff --git a/docs/es/docs/how-to/custom-docs-ui-assets.md b/docs/es/docs/how-to/custom-docs-ui-assets.md index c536cdc40..1b355eb76 100644 --- a/docs/es/docs/how-to/custom-docs-ui-assets.md +++ b/docs/es/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ El primer paso es desactivar la documentación automática, ya que por defecto, Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} ### Incluye la documentación personalizada @@ -34,7 +34,7 @@ Puedes reutilizar las funciones internas de FastAPI para crear las páginas HTML Y de manera similar para ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} /// tip | Consejo @@ -50,7 +50,7 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d Ahora, para poder probar que todo funciona, crea una *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} ### Pruébalo diff --git a/docs/pt/docs/how-to/custom-docs-ui-assets.md b/docs/pt/docs/how-to/custom-docs-ui-assets.md index d994e9844..48496fbe5 100644 --- a/docs/pt/docs/how-to/custom-docs-ui-assets.md +++ b/docs/pt/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ O primeiro passo é desativar a documentação automática, pois por padrão, el Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} ### Incluir a documentação personalizada @@ -34,7 +34,7 @@ Você pode reutilizar as funções internas do FastAPI para criar as páginas HT E de forma semelhante para o ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} /// tip | Dica @@ -50,7 +50,7 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux Agora, para poder testar se tudo funciona, crie uma *operação de rota*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} ### Teste diff --git a/docs_src/custom_docs_ui/tutorial001.py b/docs_src/custom_docs_ui/tutorial001.py index 1cfcce19a..fef8affe1 100644 --- a/docs_src/custom_docs_ui/tutorial001.py +++ b/docs_src/custom_docs_ui/tutorial001.py @@ -1,19 +1,24 @@ -from fastapi import FastAPI +import os + +from fastapi import FastAPI, Request from fastapi.openapi.docs import ( get_redoc_html, get_swagger_ui_html, get_swagger_ui_oauth2_redirect_html, ) -app = FastAPI(docs_url=None, redoc_url=None) +root_path = os.getenv("ROOT_PATH", "") + +app = FastAPI(docs_url=None, redoc_url=None, root_path=root_path) @app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): +async def custom_swagger_ui_html(req: Request): + root_path = req.scope.get("root_path", "").rstrip("/") return get_swagger_ui_html( - openapi_url=app.openapi_url, + openapi_url=f"{root_path}{app.openapi_url}", title=app.title + " - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + oauth2_redirect_url=f"{root_path}{app.swagger_ui_oauth2_redirect_url}", swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js", swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css", ) @@ -25,9 +30,10 @@ async def swagger_ui_redirect(): @app.get("/redoc", include_in_schema=False) -async def redoc_html(): +async def redoc_html(req: Request): + root_path = req.scope.get("root_path", "").rstrip("/") return get_redoc_html( - openapi_url=app.openapi_url, + openapi_url=f"{root_path}{app.openapi_url}", title=app.title + " - ReDoc", redoc_js_url="https://unpkg.com/redoc@2/bundles/redoc.standalone.js", ) diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py index cb8e8c224..b197de1f8 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py @@ -1,42 +1,91 @@ +import importlib import os from pathlib import Path +from typing import TypedDict import pytest from fastapi.testclient import TestClient -@pytest.fixture(scope="module") -def client(): +class Params(TypedDict): + app_root_path: str + asgi_root_path: str + request_prefix: str + + +@pytest.fixture( + params=[ + Params(app_root_path="", asgi_root_path="", request_prefix=""), + Params(app_root_path="/api", asgi_root_path="", request_prefix="/api"), + Params(app_root_path="/api", asgi_root_path="", request_prefix=""), + Params(app_root_path="", asgi_root_path="/api", request_prefix="/api"), + Params(app_root_path="", asgi_root_path="/api", request_prefix=""), + ], + ids=[ + "Without root_path, request without prefix", + "FastAPI(root_path=root_path), request with prefix", + "FastAPI(root_path=root_path), request without prefix", + "TestClient(root_path=root_path), request with prefix", + "TestClient(root_path=root_path), request without prefix", + ], +) +def params(request: pytest.FixtureRequest): + return request.param + + +@pytest.fixture +def client(params: Params, monkeypatch): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) - from docs_src.custom_docs_ui.tutorial001 import app + monkeypatch.setenv("ROOT_PATH", params["app_root_path"]) + from docs_src.custom_docs_ui import tutorial001 + + importlib.reload(tutorial001) + app = tutorial001.app - with TestClient(app) as client: + with TestClient(app, root_path=params["asgi_root_path"]) as client: yield client + static_dir.rmdir() -def test_swagger_ui_html(client: TestClient): - response = client.get("/docs") +def test_swagger_ui_html(client: TestClient, params: Params): + request_prefix = params["request_prefix"] + root_path = params["app_root_path"] or params["asgi_root_path"] + + response = client.get(f"{request_prefix}/docs") assert response.status_code == 200, response.text assert "https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" in response.text assert "https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" in response.text + assert f"{root_path}/docs/oauth2-redirect" in response.text + + response = client.get(f"{request_prefix}/openapi.json") + assert response.status_code == 200 + +def test_swagger_ui_oauth2_redirect_html(client: TestClient, params: Params): + request_prefix = params["request_prefix"] -def test_swagger_ui_oauth2_redirect_html(client: TestClient): - response = client.get("/docs/oauth2-redirect") + response = client.get(f"{request_prefix}/docs/oauth2-redirect") assert response.status_code == 200, response.text assert "window.opener.swaggerUIRedirectOauth2" in response.text -def test_redoc_html(client: TestClient): - response = client.get("/redoc") +def test_redoc_html(client: TestClient, params: Params): + request_prefix = params["request_prefix"] + + response = client.get(f"{request_prefix}/redoc") assert response.status_code == 200, response.text assert "https://unpkg.com/redoc@2/bundles/redoc.standalone.js" in response.text + response = client.get(f"{request_prefix}/openapi.json") + assert response.status_code == 200 + + +def test_api(client: TestClient, params: Params): + request_prefix = params["request_prefix"] -def test_api(client: TestClient): - response = client.get("/users/john") + response = client.get(f"{request_prefix}/users/john") assert response.status_code == 200, response.text assert response.json()["message"] == "Hello john" diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index 6b547c062..35226b685 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -6,8 +6,6 @@ from typing import TypedDict import pytest from fastapi.testclient import TestClient -ROOT_PATH = "/api" - class Params(TypedDict): app_root_path: str From cdddd0fef556e47295d18f4aa9434404070bc27f Mon Sep 17 00:00:00 2001 From: Robert Anthony Date: Thu, 10 Jul 2025 21:50:34 -0400 Subject: [PATCH 12/12] Fix highlighting --- docs/de/docs/how-to/custom-docs-ui-assets.md | 6 +++--- docs/en/docs/how-to/custom-docs-ui-assets.md | 6 +++--- docs/es/docs/how-to/custom-docs-ui-assets.md | 6 +++--- docs/pt/docs/how-to/custom-docs-ui-assets.md | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/de/docs/how-to/custom-docs-ui-assets.md b/docs/de/docs/how-to/custom-docs-ui-assets.md index 342b66460..cd2287a31 100644 --- a/docs/de/docs/how-to/custom-docs-ui-assets.md +++ b/docs/de/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Der erste Schritt besteht darin, die automatischen Dokumentationen zu deaktivier Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[12] *} ### Die benutzerdefinierten Dokumentationen hinzufügen @@ -34,7 +34,7 @@ Sie können die internen Funktionen von FastAPI wiederverwenden, um die HTML-Sei Und genau so für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[4:8,15:24,27:29,32:39] *} /// tip | Tipp @@ -50,7 +50,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[42:44] *} ### Es ausprobieren diff --git a/docs/en/docs/how-to/custom-docs-ui-assets.md b/docs/en/docs/how-to/custom-docs-ui-assets.md index 7bc0f441f..611cfca1e 100644 --- a/docs/en/docs/how-to/custom-docs-ui-assets.md +++ b/docs/en/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ The first step is to disable the automatic docs, as by default, those use the de To disable them, set their URLs to `None` when creating your `FastAPI` app: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[12] *} ### Include the custom docs @@ -34,7 +34,7 @@ You can reuse FastAPI's internal functions to create the HTML pages for the docs And similarly for ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[4:8,15:24,27:29,32:39] *} /// tip @@ -50,7 +50,7 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect Now, to be able to test that everything works, create a *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[42:44] *} ### Test it diff --git a/docs/es/docs/how-to/custom-docs-ui-assets.md b/docs/es/docs/how-to/custom-docs-ui-assets.md index 1b355eb76..ae65ce2b3 100644 --- a/docs/es/docs/how-to/custom-docs-ui-assets.md +++ b/docs/es/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ El primer paso es desactivar la documentación automática, ya que por defecto, Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[12] *} ### Incluye la documentación personalizada @@ -34,7 +34,7 @@ Puedes reutilizar las funciones internas de FastAPI para crear las páginas HTML Y de manera similar para ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[4:8,15:24,27:29,32:39] *} /// tip | Consejo @@ -50,7 +50,7 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d Ahora, para poder probar que todo funciona, crea una *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[42:44] *} ### Pruébalo diff --git a/docs/pt/docs/how-to/custom-docs-ui-assets.md b/docs/pt/docs/how-to/custom-docs-ui-assets.md index 48496fbe5..b9d619cd6 100644 --- a/docs/pt/docs/how-to/custom-docs-ui-assets.md +++ b/docs/pt/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ O primeiro passo é desativar a documentação automática, pois por padrão, el Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[11] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[12] *} ### Incluir a documentação personalizada @@ -34,7 +34,7 @@ Você pode reutilizar as funções internas do FastAPI para criar as páginas HT E de forma semelhante para o ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[3:7,14:23,26:28,31:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[4:8,15:24,27:29,32:39] *} /// tip | Dica @@ -50,7 +50,7 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux Agora, para poder testar se tudo funciona, crie uma *operação de rota*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[41:43] *} +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[42:44] *} ### Teste