diff --git a/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py b/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py index 8f0c0ea..87ebe70 100644 --- a/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py @@ -284,7 +284,7 @@ class FastAPISwaggerInstrument(SwaggerInstrument[FastAPIConfig]): def bootstrap(self) -> None: config = self.bootstrap_config application = config.app - if config.swagger_path != application.docs_url: + if application.docs_url is not None and config.swagger_path != application.docs_url: warn_at_caller(f"swagger_path differs from docs_url, {application.docs_url} will be used for docs path") if config.swagger_offline_docs: enable_offline_docs(application, static_path=config.swagger_static_path) diff --git a/lite_bootstrap/helpers/fastapi_helpers.py b/lite_bootstrap/helpers/fastapi_helpers.py index 62804cc..120f4dd 100644 --- a/lite_bootstrap/helpers/fastapi_helpers.py +++ b/lite_bootstrap/helpers/fastapi_helpers.py @@ -8,7 +8,7 @@ if import_checker.is_fastapi_installed: from fastapi import FastAPI, Request - from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html, get_swagger_ui_oauth2_redirect_html + from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from starlette.routing import Route @@ -42,11 +42,12 @@ def enable_offline_docs( msg = "No app.openapi_url specified" raise ConfigurationError(msg) - docs_url: str = app.docs_url or "/docs" - redoc_url: str = app.redoc_url or "/redoc" - swagger_ui_oauth2_redirect_url: str = app.swagger_ui_oauth2_redirect_url or "/docs/oauth2-redirect" + docs_url = app.docs_url + redoc_url = app.redoc_url + if not docs_url and not redoc_url: + return - replaced_urls = (docs_url, redoc_url, swagger_ui_oauth2_redirect_url) + replaced_urls = (docs_url, redoc_url) app.router.routes = [ route for route in app.router.routes @@ -58,26 +59,26 @@ def enable_offline_docs( static_dir_path = pathlib.Path(__file__).parent.parent / "static/fastapi_docs" app.mount(static_path, StaticFiles(directory=static_dir_path), name="static") - @app.get(docs_url, include_in_schema=False) - async def custom_swagger_ui_html(request: Request) -> HTMLResponse: - root_path = _safe_root_path(request.scope.get("root_path", "")) - return get_swagger_ui_html( - openapi_url=f"{root_path}{app_openapi_url}", - title=f"{app.title} - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, - swagger_js_url=f"{root_path}{static_path}/swagger-ui-bundle.js", - swagger_css_url=f"{root_path}{static_path}/swagger-ui.css", - ) + if docs_url: - @app.get(swagger_ui_oauth2_redirect_url, include_in_schema=False) - async def swagger_ui_redirect() -> HTMLResponse: - return get_swagger_ui_oauth2_redirect_html() + @app.get(docs_url, include_in_schema=False) + async def custom_swagger_ui_html(request: Request) -> HTMLResponse: + root_path = _safe_root_path(request.scope.get("root_path", "")) + return get_swagger_ui_html( + openapi_url=f"{root_path}{app_openapi_url}", + title=f"{app.title} - Swagger UI", + oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + swagger_js_url=f"{root_path}{static_path}/swagger-ui-bundle.js", + swagger_css_url=f"{root_path}{static_path}/swagger-ui.css", + ) - @app.get(redoc_url, include_in_schema=False) - async def redoc_html(request: Request) -> HTMLResponse: - root_path = _safe_root_path(request.scope.get("root_path", "")) - return get_redoc_html( - openapi_url=f"{root_path}{app_openapi_url}", - title=f"{app.title} - ReDoc", - redoc_js_url=f"{root_path}{static_path}/redoc.standalone.js", - ) + if redoc_url: + + @app.get(redoc_url, include_in_schema=False) + async def redoc_html(request: Request) -> HTMLResponse: + root_path = _safe_root_path(request.scope.get("root_path", "")) + return get_redoc_html( + openapi_url=f"{root_path}{app_openapi_url}", + title=f"{app.title} - ReDoc", + redoc_js_url=f"{root_path}{static_path}/redoc.standalone.js", + ) diff --git a/tests/test_fastapi_bootstrap.py b/tests/test_fastapi_bootstrap.py index c448a8a..a17810f 100644 --- a/tests/test_fastapi_bootstrap.py +++ b/tests/test_fastapi_bootstrap.py @@ -181,6 +181,33 @@ def test_fastapi_bootstrapper_docs_url_differ(fastapi_config: FastAPIConfig) -> bootstrapper.teardown() +def test_offline_docs_respect_both_disabled_routes() -> None: + app = fastapi.FastAPI(docs_url=None, redoc_url=None) + config = FastAPIConfig(application=app, swagger_offline_docs=True) + bootstrapper = FastAPIBootstrapper(bootstrap_config=config) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + application = bootstrapper.bootstrap() + + with TestClient(application) as client: + assert client.get("/docs").status_code == status.HTTP_404_NOT_FOUND + assert client.get("/redoc").status_code == status.HTTP_404_NOT_FOUND + + assert not caught + + +def test_offline_swagger_ui_works_with_redoc_disabled() -> None: + app = fastapi.FastAPI(redoc_url=None) + config = FastAPIConfig(application=app, swagger_offline_docs=True) + + with TestClient(FastAPIBootstrapper(bootstrap_config=config).bootstrap()) as client: + response = client.get("/docs") + assert response.status_code == status.HTTP_200_OK + assert f"{config.swagger_static_path}/swagger-ui-bundle.js" in response.text + assert client.get("/redoc").status_code == status.HTTP_404_NOT_FOUND + + def test_fastapi_bootstrapper_apps_and_kwargs_warning(fastapi_config: FastAPIConfig) -> None: with pytest.warns(UserWarning, match="application_kwargs must be used without application") as caught: dataclasses.replace(fastapi_config, application=fastapi.FastAPI(), application_kwargs={"title": "some title"})