diff --git a/src/phoenix/server/app.py b/src/phoenix/server/app.py index 8c3f422f07..ae18b0885b 100644 --- a/src/phoenix/server/app.py +++ b/src/phoenix/server/app.py @@ -65,6 +65,7 @@ from phoenix.server.api.routers.v1 import V1_ROUTES from phoenix.server.api.schema import schema from phoenix.server.grpc_server import GrpcServer +from phoenix.server.openapi.docs import get_swagger_ui_html from phoenix.server.telemetry import initialize_opentelemetry_tracer_provider from phoenix.trace.schemas import Span @@ -239,6 +240,10 @@ async def openapi_schema(request: Request) -> Response: return schemas.OpenAPIResponse(request=request) +async def api_docs(request: Request) -> Response: + return get_swagger_ui_html(openapi_url="/schema", title="arize-phoenix API") + + def create_app( database_url: str, export_path: Path, @@ -353,6 +358,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: {"path": export_path}, ), ), + Route( + "/docs", + api_docs, + ), Route( "/graphql", graphql, diff --git a/src/phoenix/server/openapi/__init__.py b/src/phoenix/server/openapi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/phoenix/server/openapi/docs.py b/src/phoenix/server/openapi/docs.py new file mode 100644 index 0000000000..78bd6ff93b --- /dev/null +++ b/src/phoenix/server/openapi/docs.py @@ -0,0 +1,218 @@ +import json +from typing import Any, Dict, Optional + +from starlette.responses import HTMLResponse + +swagger_ui_default_parameters: Dict[str, Any] = { + "dom_id": "#swagger-ui", + "layout": "BaseLayout", + "deepLinking": True, + "showExtensions": True, + "showCommonExtensions": True, +} + + +def get_swagger_ui_html( + *, + openapi_url: str = "/schema", + title: str, + swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js", + swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css", + swagger_favicon_url: str = "/favicon.ico", + oauth2_redirect_url: Optional[str] = None, + init_oauth: Optional[str] = None, + swagger_ui_parameters: Optional[Dict[str, Any]] = None, +) -> HTMLResponse: + """ + Generate and return the HTML that loads Swagger UI for the interactive API + docs (normally served at `/docs`). + """ + current_swagger_ui_parameters = swagger_ui_default_parameters.copy() + if swagger_ui_parameters: + current_swagger_ui_parameters.update(swagger_ui_parameters) + + html = f""" + + + + + + {title} + + +
+
+ + + + + + """ + return HTMLResponse(html) + + +def get_redoc_html( + *, + openapi_url: str, + title: str, + redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js", + redoc_favicon_url: str = "/favicon.ico", + with_google_fonts: bool = True, +) -> HTMLResponse: + """ + Generate and return the HTML response that loads ReDoc for the alternative + API docs (normally served at `/redoc`). + + + """ + html = f""" + + + + {title} + + + + """ + if with_google_fonts: + html += """ + + """ # noqa: E501 + html += f""" + + + + + + + + + + + """ + return HTMLResponse(html) + + +# Not needed now but copy-pasting for future reference +def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: + """ + Generate the HTML response with the OAuth2 redirection for Swagger UI. + + You normally don't need to use or change this. + """ + # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html + html = """ + + + + Swagger UI: OAuth2 Redirect + + + + + + """ # noqa: E501 + return HTMLResponse(content=html)