diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14efeda8..e8a5cf4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2.0a12] - 2023-11-?? :fallen_leaf:
- Adds support for Python 3.12, by @bymoye
+- Adds support for custom files URLs for ReDoc and Swagger UI, by @joshua-auchincloss
- Replaces `pkg_resources` with `importlib.resources` for all supported Python
versions except for `3.8`.
- Runs tests against Pydantic `2.4.2` instead of Pydantic `2.0` to check
diff --git a/blacksheep/server/openapi/common.py b/blacksheep/server/openapi/common.py
index 000337c4..2b73269e 100644
--- a/blacksheep/server/openapi/common.py
+++ b/blacksheep/server/openapi/common.py
@@ -34,7 +34,7 @@
from blacksheep.server.routing import Route, Router
from blacksheep.utils.time import utcnow
-from .ui import CdnOptions, SwaggerUIProvider, UIOptions, UIProvider
+from .ui import SwaggerUIProvider, UIOptions, UIProvider
T = TypeVar("T")
@@ -167,7 +167,6 @@ def __init__(
yaml_spec_path: str = "/openapi.yaml",
preferred_format: Format = Format.JSON,
anonymous_access: bool = True,
- swagger_ui_cdn: Optional[CdnOptions] = None,
) -> None:
self._handlers_docs: Dict[Any, EndpointDocs] = {}
self.use_docstrings: bool = True
@@ -178,9 +177,7 @@ def __init__(
self._yaml_docs: bytes = b""
self.preferred_format = preferred_format
self.anonymous_access = anonymous_access
- self.ui_providers: List[UIProvider] = [
- SwaggerUIProvider(ui_path, swagger_ui_cdn)
- ]
+ self.ui_providers: List[UIProvider] = [SwaggerUIProvider(ui_path)]
self._types_schemas = {}
self.events = OpenAPIEvents(self)
self.handle_optional_response_with_404 = True
diff --git a/blacksheep/server/openapi/ui.py b/blacksheep/server/openapi/ui.py
index 8eab34bc..bcf9ac7e 100644
--- a/blacksheep/server/openapi/ui.py
+++ b/blacksheep/server/openapi/ui.py
@@ -7,24 +7,26 @@
from blacksheep.server.resources import get_resource_file_content
from blacksheep.utils.time import utcnow
-SWAGGER_UI_CDN = (
+SWAGGER_UI_JS_URL = (
"https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.30.0/swagger-ui-bundle.js"
)
-SWAGGER_UI_CSS = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.30.0/swagger-ui.css"
+SWAGGER_UI_CSS_URL = (
+ "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.30.0/swagger-ui.css"
+)
SWAGGER_UI_FONT = None
-REDOC_UI_CDN = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
-REDOC_UI_CSS = None
-REDOC_UI_FONT = (
+REDOC_UI_JS_URL = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
+REDOC_UI_CSS_URL = None
+REDOC_UI_FONT_URL = (
"https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
)
@dataclass
-class CdnOptions:
- js_cdn_url: str
- css_cdn_url: Optional[str] = None
- fontset_cdn_url: Optional[str] = None
+class UIFilesOptions:
+ js_url: str
+ css_url: Optional[str] = None
+ fonts_url: Optional[str] = None
@dataclass
@@ -34,17 +36,17 @@ class UIOptions:
class UIProvider(ABC):
- cdn: CdnOptions
+ ui_files: UIFilesOptions
ui_path: str
def __init__(
self,
ui_path: str,
- cdn: Optional[CdnOptions] = None,
+ ui_files: Optional[UIFilesOptions] = None,
) -> None:
super().__init__()
self.ui_path = ui_path
- self.cdn = cdn if cdn else self.default_cdn
+ self.ui_files = ui_files if ui_files else self.default_ui_files
@abstractmethod
def build_ui(self, options: UIOptions) -> None:
@@ -59,7 +61,7 @@ def get_ui_handler(self) -> Callable[[Request], Response]:
"""
@property
- def default_cdn(self) -> CdnOptions:
+ def default_ui_files(self) -> UIFilesOptions:
...
@@ -67,9 +69,9 @@ class SwaggerUIProvider(UIProvider):
def __init__(
self,
ui_path: str = "/docs",
- cdn: Optional[CdnOptions] = None,
+ ui_files_options: Optional[UIFilesOptions] = None,
) -> None:
- super().__init__(ui_path, cdn)
+ super().__init__(ui_path, ui_files_options)
self._ui_html: bytes = b""
@@ -81,8 +83,8 @@ def get_openapi_ui_html(self, options: UIOptions) -> str:
get_resource_file_content("swagger-ui.html")
.replace("##SPEC_URL##", options.spec_url)
.replace("##PAGE_TITLE##", options.page_title)
- .replace("##JS_CDN##", self.cdn.js_cdn_url)
- .replace("##CSS_CDN##", self.cdn.css_cdn_url)
+ .replace("##JS_URL##", self.ui_files.js_url)
+ .replace("##CSS_URL##", self.ui_files.css_url or "")
)
def build_ui(self, options: UIOptions) -> None:
@@ -99,15 +101,15 @@ def get_open_api_ui(request: Request) -> Response:
return get_open_api_ui
@property
- def default_cdn(self) -> CdnOptions:
- return CdnOptions(SWAGGER_UI_CDN, SWAGGER_UI_CSS, SWAGGER_UI_FONT)
+ def default_ui_files(self) -> UIFilesOptions:
+ return UIFilesOptions(SWAGGER_UI_JS_URL, SWAGGER_UI_CSS_URL, SWAGGER_UI_FONT)
class ReDocUIProvider(UIProvider):
def __init__(
- self, ui_path: str = "/redocs", cdn: Optional[CdnOptions] = None
+ self, ui_path: str = "/redocs", ui_files: Optional[UIFilesOptions] = None
) -> None:
- super().__init__(ui_path, cdn)
+ super().__init__(ui_path, ui_files)
self._ui_html: bytes = b""
@@ -119,8 +121,8 @@ def get_openapi_ui_html(self, options: UIOptions) -> str:
get_resource_file_content("redoc-ui.html")
.replace("##SPEC_URL##", options.spec_url)
.replace("##PAGE_TITLE##", options.page_title)
- .replace("##JS_CDN##", self.cdn.js_cdn_url)
- .replace("##FONT_CDN##", self.cdn.fontset_cdn_url)
+ .replace("##JS_URL##", self.ui_files.js_url)
+ .replace("##FONT_URL##", self.ui_files.fonts_url or "")
)
def build_ui(self, options: UIOptions) -> None:
@@ -137,5 +139,5 @@ def get_open_api_ui(request: Request) -> Response:
return get_open_api_ui
@property
- def default_cdn(self) -> CdnOptions:
- return CdnOptions(REDOC_UI_CDN, REDOC_UI_CSS, REDOC_UI_FONT)
+ def default_ui_files(self) -> UIFilesOptions:
+ return UIFilesOptions(REDOC_UI_JS_URL, REDOC_UI_CSS_URL, REDOC_UI_FONT_URL)
diff --git a/blacksheep/server/openapi/v3.py b/blacksheep/server/openapi/v3.py
index 44224f77..3ee247ce 100644
--- a/blacksheep/server/openapi/v3.py
+++ b/blacksheep/server/openapi/v3.py
@@ -69,7 +69,6 @@
ResponseStatusType,
response_status_to_str,
)
-from .ui import CdnOptions
try:
from pydantic import BaseModel # type: ignore
@@ -377,7 +376,6 @@ def __init__(
SecuritySchemes,
]
] = None,
- swagger_ui_cdn: Optional[CdnOptions] = None,
) -> None:
super().__init__(
ui_path=ui_path,
@@ -385,7 +383,6 @@ def __init__(
yaml_spec_path=yaml_spec_path,
preferred_format=preferred_format,
anonymous_access=anonymous_access,
- swagger_ui_cdn=swagger_ui_cdn,
)
self.info = info
self._tags = tags
diff --git a/blacksheep/server/res/redoc-ui.html b/blacksheep/server/res/redoc-ui.html
index 3736a0e7..e97662ec 100644
--- a/blacksheep/server/res/redoc-ui.html
+++ b/blacksheep/server/res/redoc-ui.html
@@ -5,7 +5,7 @@
-
+