"""
Swagger UI integration for APIFromAnything.
This module provides a highly customizable Swagger UI integration for APIs created
with the APIFromAnything library, with advanced styling and branding options.
"""
import os
import json
from typing import Any, Dict, List, Optional, Union
from pathlib import Path
from starlette.responses import HTMLResponse, JSONResponse
from starlette.routing import Route
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
from apifrom.docs.openapi import OpenAPIGenerator
[docs]
class SwaggerUIConfig:
"""
Configuration for Swagger UI customization.
This class provides extensive configuration options for customizing the
appearance and behavior of the Swagger UI documentation.
"""
def __init__(
self,
theme: str = "default",
deep_linking: bool = True,
display_operation_id: bool = False,
default_models_expand_depth: int = 1,
default_model_expand_depth: int = 1,
default_model_rendering: str = "example",
display_request_duration: bool = True,
doc_expansion: str = "list",
filter: bool = True,
max_displayed_tags: Optional[int] = None,
operations_sorter: Optional[str] = None,
show_extensions: bool = False,
show_common_extensions: bool = False,
tag_sorter: Optional[str] = None,
use_unicode_characters: bool = True,
persist_authorization: bool = False,
syntax_highlight: str = "monokai",
oauth2_redirect_url: Optional[str] = None,
custom_css: Optional[str] = None,
custom_js: Optional[str] = None,
custom_favicon: Optional[str] = None,
custom_swagger_ui_version: str = "5.9.1",
dom_id: str = "#swagger-ui",
layout: str = "StandaloneLayout",
plugins: Optional[List[str]] = None,
presets: Optional[List[str]] = None,
):
"""
Initialize the Swagger UI configuration.
Args:
theme: The theme to use (default, material, muted, outline, flattop)
deep_linking: If set to true, enables deep linking for tags and operations
display_operation_id: Controls the display of operationId in operations list
default_models_expand_depth: The default expansion depth for models (set to -1 completely hide the models)
default_model_expand_depth: The default expansion depth for the model on the model-example section
default_model_rendering: Controls how the model is shown when the API is first rendered
display_request_duration: Controls the display of the request duration (in milliseconds) for Try-It-Out requests
doc_expansion: Controls the default expansion setting for the operations and tags
filter: If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown
max_displayed_tags: If set, limits the number of tagged operations displayed to at most this many
operations_sorter: Apply a sort to the operation list of each API
show_extensions: Controls the display of vendor extension (x-) fields and values
show_common_extensions: Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values
tag_sorter: Apply a sort to the tag list
use_unicode_characters: Controls whether unicode characters are used in rendered descriptions
persist_authorization: If set, will persist authorization data
syntax_highlight: Syntax highlighting theme (agate, arta, monokai, nord, obsidian, etc.)
oauth2_redirect_url: OAuth2 redirect URL
custom_css: URL to a custom CSS file
custom_js: URL to a custom JavaScript file
custom_favicon: URL to a custom favicon
custom_swagger_ui_version: The version of Swagger UI to use
dom_id: The DOM element ID to bind to
layout: The layout to use (BaseLayout, StandaloneLayout)
plugins: The plugins to use
presets: The presets to use
"""
self.theme = theme
self.deep_linking = deep_linking
self.display_operation_id = display_operation_id
self.default_models_expand_depth = default_models_expand_depth
self.default_model_expand_depth = default_model_expand_depth
self.default_model_rendering = default_model_rendering
self.display_request_duration = display_request_duration
self.doc_expansion = doc_expansion
self.filter = filter
self.max_displayed_tags = max_displayed_tags
self.operations_sorter = operations_sorter
self.show_extensions = show_extensions
self.show_common_extensions = show_common_extensions
self.tag_sorter = tag_sorter
self.use_unicode_characters = use_unicode_characters
self.persist_authorization = persist_authorization
self.syntax_highlight = syntax_highlight
self.oauth2_redirect_url = oauth2_redirect_url
self.custom_css = custom_css
self.custom_js = custom_js
self.custom_favicon = custom_favicon
self.custom_swagger_ui_version = custom_swagger_ui_version
self.dom_id = dom_id
self.layout = layout
self.plugins = plugins or []
self.presets = presets or ["SwaggerUIBundle.presets.apis", "SwaggerUIStandalonePreset"]
def to_dict(self) -> Dict[str, Any]:
"""
Convert the configuration to a dictionary.
Returns:
A dictionary with the configuration options.
"""
config = {
"theme": self.theme,
"deepLinking": self.deep_linking,
"displayOperationId": self.display_operation_id,
"defaultModelsExpandDepth": self.default_models_expand_depth,
"defaultModelExpandDepth": self.default_model_expand_depth,
"defaultModelRendering": self.default_model_rendering,
"displayRequestDuration": self.display_request_duration,
"docExpansion": self.doc_expansion,
"filter": self.filter,
"showExtensions": self.show_extensions,
"showCommonExtensions": self.show_common_extensions,
"useUnicodeCharacters": self.use_unicode_characters,
"persistAuthorization": self.persist_authorization,
"dom_id": self.dom_id,
"layout": self.layout,
}
if self.max_displayed_tags is not None:
config["maxDisplayedTags"] = self.max_displayed_tags
if self.operations_sorter is not None:
config["operationsSorter"] = self.operations_sorter
if self.tag_sorter is not None:
config["tagSorter"] = self.tag_sorter
if self.oauth2_redirect_url is not None:
config["oauth2RedirectUrl"] = self.oauth2_redirect_url
if self.plugins:
config["plugins"] = self.plugins
if self.presets:
config["presets"] = self.presets
return config
[docs]
class SwaggerUI:
"""
Swagger UI for APIFromAnything.
This class provides a Swagger UI integration for APIs created with the
APIFromAnything library, with extensive customization options.
"""
def __init__(
self,
openapi_generator: OpenAPIGenerator,
url_prefix: str = "/docs",
config: Optional[SwaggerUIConfig] = None,
assets_dir: Optional[str] = None,
static_files_route: str = "/static",
):
"""
Initialize the Swagger UI.
Args:
openapi_generator: The OpenAPI generator instance
url_prefix: The URL prefix for the documentation
config: The Swagger UI configuration
assets_dir: Directory containing custom assets (CSS, JS, images)
static_files_route: The route for static files
"""
self.openapi_generator = openapi_generator
self.url_prefix = url_prefix.rstrip("/")
self.config = config or SwaggerUIConfig()
self.assets_dir = assets_dir
self.static_files_route = static_files_route
# Set up templates
self.templates_path = Path(__file__).parent / "templates"
if not self.templates_path.exists():
self.templates_path.mkdir(parents=True)
self._create_default_templates()
self.templates = Jinja2Templates(directory=str(self.templates_path))
def _create_default_templates(self) -> None:
"""
Create default templates if they don't exist.
"""
# Create the main template
with open(self.templates_path / "swagger_ui.html", "w") as f:
f.write(self._get_default_template())
# Create the CSS template
with open(self.templates_path / "swagger_ui_custom.css", "w") as f:
f.write(self._get_default_css())
def _get_default_template(self) -> str:
"""
Get the default HTML template.
Returns:
The default HTML template for Swagger UI.
"""
return """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }} - API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@{{ swagger_ui_version }}/swagger-ui.css">
{% if theme %}
<link rel="stylesheet" type="text/css" href="{{ static_url }}/themes/{{ theme }}.css">
{% endif %}
{% if custom_css %}
<link rel="stylesheet" type="text/css" href="{{ custom_css }}">
{% else %}
<style>
{% include "swagger_ui_custom.css" %}
</style>
{% endif %}
{% if custom_favicon %}
<link rel="icon" type="image/png" href="{{ custom_favicon }}">
{% else %}
<link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@{{ swagger_ui_version }}/favicon-32x32.png" sizes="32x32">
{% endif %}
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@{{ swagger_ui_version }}/swagger-ui-bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@{{ swagger_ui_version }}/swagger-ui-standalone-preset.js"></script>
{% if custom_js %}
<script src="{{ custom_js }}"></script>
{% endif %}
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "{{ openapi_url }}",
dom_id: "{{ dom_id }}",
presets: [
{% for preset in presets %}
{{ preset }}{% if not loop.last %},{% endif %}
{% endfor %}
],
layout: "{{ layout }}",
deepLinking: {{ deep_linking|tojson }},
displayOperationId: {{ display_operation_id|tojson }},
defaultModelsExpandDepth: {{ default_models_expand_depth }},
defaultModelExpandDepth: {{ default_model_expand_depth }},
defaultModelRendering: "{{ default_model_rendering }}",
displayRequestDuration: {{ display_request_duration|tojson }},
docExpansion: "{{ doc_expansion }}",
filter: {{ filter|tojson }},
{% if max_displayed_tags %}maxDisplayedTags: {{ max_displayed_tags }},{% endif %}
{% if operations_sorter %}operationsSorter: "{{ operations_sorter }}",{% endif %}
showExtensions: {{ show_extensions|tojson }},
showCommonExtensions: {{ show_common_extensions|tojson }},
{% if tag_sorter %}tagSorter: "{{ tag_sorter }}",{% endif %}
useUnicodeCharacters: {{ use_unicode_characters|tojson }},
persistAuthorization: {{ persist_authorization|tojson }},
syntaxHighlight: {
theme: "{{ syntax_highlight }}"
},
{% if oauth2_redirect_url %}oauth2RedirectUrl: "{{ oauth2_redirect_url }}",{% endif %}
{% if plugins %}
plugins: [
{% for plugin in plugins %}
{{ plugin }}{% if not loop.last %},{% endif %}
{% endfor %}
],
{% endif %}
onComplete: function() {
// Custom event handlers can be added here
console.log("Swagger UI loaded");
// Apply custom theming
applyCustomTheme();
}
});
function applyCustomTheme() {
// Apply additional custom styling if needed
// This function can be extended to apply dynamic themes
}
window.ui = ui;
};
</script>
</body>
</html>
"""
def _get_default_css(self) -> str:
"""
Get the default custom CSS.
Returns:
The default custom CSS for Swagger UI.
"""
return """/* Custom Swagger UI Styling */
/* Header customization */
.swagger-ui .topbar {
background-color: #1a365d;
padding: 15px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.swagger-ui .topbar .wrapper {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1460px;
margin: 0 auto;
padding: 0 20px;
}
.swagger-ui .topbar a {
max-width: 340px;
font-size: 1.5em;
font-weight: 700;
color: white;
text-decoration: none;
}
.swagger-ui .topbar img {
height: 40px;
margin-right: 10px;
}
/* Info section styling */
.swagger-ui .info {
margin: 30px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
.swagger-ui .info .title {
font-size: 36px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 15px 0;
}
.swagger-ui .info .description {
font-size: 16px;
line-height: 1.6;
}
.swagger-ui .info hgroup.main {
margin: 0 0 20px 0;
display: flex;
align-items: center;
}
.swagger-ui .info .version {
font-size: 14px;
background: #e9ecef;
border-radius: 20px;
padding: 4px 12px;
margin-left: 15px;
color: #495057;
}
/* Tag sections */
.swagger-ui .opblock-tag {
font-size: 24px;
margin: 20px 0 10px 0;
padding: 15px;
background: #f1f6ff;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
transition: all 0.3s ease;
}
.swagger-ui .opblock-tag:hover {
background: #e6f0ff;
}
/* Operation blocks */
.swagger-ui .opblock {
margin: 0 0 15px 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: all 0.3s ease;
}
.swagger-ui .opblock:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
.swagger-ui .opblock.opblock-get {
background: rgba(97, 175, 254, 0.1);
border-color: #61affe;
}
.swagger-ui .opblock.opblock-post {
background: rgba(73, 204, 144, 0.1);
border-color: #49cc90;
}
.swagger-ui .opblock.opblock-put {
background: rgba(252, 161, 48, 0.1);
border-color: #fca130;
}
.swagger-ui .opblock.opblock-delete {
background: rgba(249, 62, 62, 0.1);
border-color: #f93e3e;
}
.swagger-ui .opblock.opblock-patch {
background: rgba(80, 227, 194, 0.1);
border-color: #50e3c2;
}
/* Operation summaries */
.swagger-ui .opblock .opblock-summary {
padding: 12px 20px;
}
.swagger-ui .opblock .opblock-summary-method {
min-width: 80px;
text-align: center;
font-size: 14px;
font-weight: 700;
border-radius: 4px;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
}
.swagger-ui .opblock .opblock-summary-path {
font-size: 16px;
font-weight: 600;
font-family: 'Roboto Mono', monospace;
}
/* Request and response sections */
.swagger-ui .opblock-section-header {
background: #f8f9fa;
padding: 12px 20px;
border-radius: 4px 4px 0 0;
}
.swagger-ui .parameters-container {
padding: 15px 20px;
}
.swagger-ui .parameter__name {
font-weight: 600;
font-size: 14px;
}
.swagger-ui .parameter__type {
font-size: 12px;
color: #6c757d;
}
/* Models section */
.swagger-ui .model-container {
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
}
.swagger-ui .model-title {
font-size: 18px;
font-weight: 600;
}
/* Buttons */
.swagger-ui .btn {
border-radius: 4px;
font-weight: 600;
text-transform: uppercase;
font-size: 13px;
padding: 8px 16px;
transition: all 0.2s ease;
}
.swagger-ui .btn.execute {
background-color: #4caf50;
color: white;
box-shadow: 0 2px 5px rgba(76, 175, 80, 0.3);
}
.swagger-ui .btn.execute:hover {
background-color: #43a047;
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.4);
}
.swagger-ui .btn.cancel {
background-color: #f44336;
color: white;
box-shadow: 0 2px 5px rgba(244, 67, 54, 0.3);
}
.swagger-ui .btn.cancel:hover {
background-color: #e53935;
box-shadow: 0 4px 8px rgba(244, 67, 54, 0.4);
}
/* Authorize button */
.swagger-ui .auth-wrapper .authorize {
background-color: #651fff;
color: white;
border-color: #651fff;
box-shadow: 0 2px 5px rgba(101, 31, 255, 0.3);
}
.swagger-ui .auth-wrapper .authorize:hover {
background-color: #5a1fee;
box-shadow: 0 4px 8px rgba(101, 31, 255, 0.4);
}
/* Response section */
.swagger-ui .responses-inner {
padding: 15px 20px;
}
.swagger-ui .responses-inner h4 {
font-size: 16px;
font-weight: 600;
}
.swagger-ui table {
border-collapse: separate;
border-spacing: 0;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.swagger-ui table thead tr th {
background: #f1f6ff;
color: #2c3e50;
font-weight: 600;
padding: 12px 15px;
border: none;
}
.swagger-ui table tbody tr td {
padding: 10px 15px;
border-top: 1px solid #e9ecef;
}
/* Code blocks */
.swagger-ui .highlight-code {
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.swagger-ui .opblock .opblock-summary-method {
min-width: 60px;
font-size: 12px;
}
.swagger-ui .opblock .opblock-summary-path {
font-size: 14px;
}
.swagger-ui .info .title {
font-size: 28px;
}
}
/* Theme-specific styling - will be extended with other theme options */
.theme-material .swagger-ui .opblock {
border-radius: 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}
.theme-outline .swagger-ui .opblock {
background: transparent;
border: 2px solid;
}
.theme-flattop .swagger-ui .opblock {
border-radius: 0;
border-top-width: 4px;
}
.theme-muted .swagger-ui .opblock {
opacity: 0.9;
}
"""
def _create_theme_files(self) -> None:
"""
Create CSS files for the different themes.
"""
themes_dir = self.templates_path / "themes"
if not themes_dir.exists():
themes_dir.mkdir(parents=True)
# Create theme files
themes = {
"material": self._get_material_theme(),
"muted": self._get_muted_theme(),
"outline": self._get_outline_theme(),
"flattop": self._get_flattop_theme(),
}
for name, content in themes.items():
with open(themes_dir / f"{name}.css", "w") as f:
f.write(content)
def _get_material_theme(self) -> str:
"""Get the Material theme CSS."""
return """/* Material Theme for Swagger UI */
.swagger-ui .opblock {
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
border: none;
}
.swagger-ui .opblock:hover {
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
}
.swagger-ui .opblock-tag {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
border-bottom: none;
}
.swagger-ui .btn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
text-transform: uppercase;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
border: none;
}
.swagger-ui .btn:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
.swagger-ui select {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
border: none;
border-radius: 4px;
}
.swagger-ui input[type=text] {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
border: none;
border-radius: 4px;
padding: 8px 12px;
}
.swagger-ui .table-container {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
border-radius: 4px;
overflow: hidden;
}
"""
def _get_muted_theme(self) -> str:
"""Get the Muted theme CSS."""
return """/* Muted Theme for Swagger UI */
.swagger-ui, .swagger-ui .info .title, .swagger-ui .opblock-tag, .swagger-ui .opblock .opblock-summary-operation-id,
.swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-description {
color: #5c6873;
}
.swagger-ui .topbar {
background-color: #546E7A;
}
.swagger-ui .info {
background-color: #f9fafb;
}
.swagger-ui .opblock {
opacity: 0.85;
transition: opacity 0.3s ease;
border: none;
box-shadow: 0 1px 3px rgba(92, 104, 115, 0.1);
}
.swagger-ui .opblock:hover {
opacity: 1;
}
.swagger-ui .opblock-tag {
border-bottom: 1px solid #eceff1;
background: transparent;
}
.swagger-ui .opblock.opblock-get {
background-color: rgba(97, 175, 254, 0.05);
}
.swagger-ui .opblock.opblock-post {
background-color: rgba(73, 204, 144, 0.05);
}
.swagger-ui .opblock.opblock-put {
background-color: rgba(252, 161, 48, 0.05);
}
.swagger-ui .opblock.opblock-delete {
background-color: rgba(249, 62, 62, 0.05);
}
.swagger-ui .btn {
opacity: 0.85;
transition: opacity 0.3s ease;
border-radius: 3px;
}
.swagger-ui .btn:hover {
opacity: 1;
}
"""
def _get_outline_theme(self) -> str:
"""Get the Outline theme CSS."""
return """/* Outline Theme for Swagger UI */
.swagger-ui .opblock {
background: transparent;
border: 2px solid;
}
.swagger-ui .opblock.opblock-get {
border-color: #61affe;
background-color: transparent;
}
.swagger-ui .opblock.opblock-post {
border-color: #49cc90;
background-color: transparent;
}
.swagger-ui .opblock.opblock-put {
border-color: #fca130;
background-color: transparent;
}
.swagger-ui .opblock.opblock-delete {
border-color: #f93e3e;
background-color: transparent;
}
.swagger-ui .opblock.opblock-patch {
border-color: #50e3c2;
background-color: transparent;
}
.swagger-ui .opblock .opblock-summary-method {
border: 2px solid;
}
.swagger-ui .opblock-tag {
border: 2px solid #e8ebee;
background: transparent;
}
.swagger-ui .btn {
border: 2px solid;
background: transparent;
color: inherit;
}
.swagger-ui .btn.execute {
border-color: #4caf50;
color: #4caf50;
}
.swagger-ui .btn.execute:hover {
background-color: #4caf50;
color: white;
}
.swagger-ui .btn.cancel {
border-color: #f44336;
color: #f44336;
}
.swagger-ui .btn.cancel:hover {
background-color: #f44336;
color: white;
}
.swagger-ui .auth-wrapper .authorize {
border-color: #651fff;
color: #651fff;
}
.swagger-ui .auth-wrapper .authorize:hover {
background-color: #651fff;
color: white;
}
"""
def _get_flattop_theme(self) -> str:
"""Get the Flattop theme CSS."""
return """/* Flattop Theme for Swagger UI */
.swagger-ui .opblock {
border-radius: 0;
border: none;
border-top-width: 4px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.swagger-ui .opblock .opblock-summary {
border-bottom: 1px solid #ebebeb;
}
.swagger-ui .opblock .opblock-summary-method {
border-radius: 0;
}
.swagger-ui .opblock-tag {
border-radius: 0;
border-bottom: 4px solid #ebebeb;
background: transparent;
}
.swagger-ui .btn {
border-radius: 0;
text-transform: uppercase;
font-weight: bold;
border: none;
}
.swagger-ui .parameter__name,
.swagger-ui .parameters-col_name {
font-family: monospace;
}
.swagger-ui .topbar {
border-bottom: 4px solid #1f1f1f;
}
.swagger-ui .info {
border-radius: 0;
border-bottom: 4px solid #ebebeb;
}
.swagger-ui table {
border-radius: 0;
}
.swagger-ui .model-container {
border-radius: 0;
}
"""
def _setup_static_files(self) -> Optional[StaticFiles]:
"""
Set up static files for Swagger UI.
Returns:
Static files mounted app if assets_dir is provided, None otherwise.
"""
# Create theme files
self._create_theme_files()
# If assets_dir is provided, mount it
if self.assets_dir:
return StaticFiles(directory=self.assets_dir)
return StaticFiles(directory=str(self.templates_path))
def setup_routes(self) -> List[Route]:
"""
Set up routes for Swagger UI.
Returns:
A list of routes for Swagger UI.
"""
routes = []
# Main Swagger UI route
routes.append(
Route(
f"{self.url_prefix}",
endpoint=self.swagger_ui_html,
methods=["GET"],
name="swagger_ui",
)
)
# OpenAPI JSON route
routes.append(
Route(
f"{self.url_prefix}/openapi.json",
endpoint=self.openapi_json,
methods=["GET"],
name="openapi_json",
)
)
# Mount static files if available
static_files = self._setup_static_files()
if static_files:
routes.append(
Route(
f"{self.static_files_route}{{path:path}}",
endpoint=static_files,
name="swagger_static",
)
)
return routes
async def swagger_ui_html(self, request):
"""
Serve the Swagger UI HTML page.
Args:
request: The request object
Returns:
A response containing the Swagger UI HTML
"""
config = self.config.to_dict()
return self.templates.TemplateResponse(
"swagger_ui.html",
{
"request": request,
"title": self.openapi_generator.config.title,
"openapi_url": f"{self.url_prefix}/openapi.json",
"swagger_ui_version": self.config.custom_swagger_ui_version,
"static_url": self.static_files_route,
"theme": self.config.theme if self.config.theme != "default" else None,
"custom_css": self.config.custom_css,
"custom_js": self.config.custom_js,
"custom_favicon": self.config.custom_favicon,
"dom_id": self.config.dom_id.strip("#"),
"layout": self.config.layout,
"deep_linking": self.config.deep_linking,
"display_operation_id": self.config.display_operation_id,
"default_models_expand_depth": self.config.default_models_expand_depth,
"default_model_expand_depth": self.config.default_model_expand_depth,
"default_model_rendering": self.config.default_model_rendering,
"display_request_duration": self.config.display_request_duration,
"doc_expansion": self.config.doc_expansion,
"filter": self.config.filter,
"max_displayed_tags": self.config.max_displayed_tags,
"operations_sorter": self.config.operations_sorter,
"show_extensions": self.config.show_extensions,
"show_common_extensions": self.config.show_common_extensions,
"tag_sorter": self.config.tag_sorter,
"use_unicode_characters": self.config.use_unicode_characters,
"persist_authorization": self.config.persist_authorization,
"syntax_highlight": self.config.syntax_highlight,
"oauth2_redirect_url": self.config.oauth2_redirect_url,
"plugins": self.config.plugins,
"presets": self.config.presets,
}
)
async def openapi_json(self, request):
"""
Serve the OpenAPI JSON.
Args:
request: The request object.
Returns:
JSON response with the OpenAPI specification.
"""
openapi_spec = self.openapi_generator.generate()
# Add server information if not present
if "servers" not in openapi_spec:
base_url = str(request.base_url).rstrip("/")
openapi_spec["servers"] = [{"url": base_url}]
return JSONResponse(openapi_spec)