diff --git a/CHANGELOG.md b/CHANGELOG.md index 2715e487..ff887161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.2] - 2023-12-15 :christmas_tree: + +- Upgrades default SwaggerUI files to version 5, by @sinisaos +- Fixes #427, handling WebSocket errors according to ASGI specification, by @Klavionik +- Adds support for custom files URLs for ReDoc and Swagger UI, by @joshua-auchincloss + ## [2.0.1] - 2023-12-09 :mount_fuji: - Fixes #441 causing the `refresh_token` endpoint for OpenID Connect integrations to not work when authentication is required by default. -- Fixes #427, handling WebSocket errors according to ASGI specification. - Fixes #443, raising a detailed exception when more than one application is sharing the same instance of `Router` - Fixes #438 and #436, restoring support for `uvicorn` used programmatically diff --git a/blacksheep/__init__.py b/blacksheep/__init__.py index 11d29bcd..0f4a559e 100644 --- a/blacksheep/__init__.py +++ b/blacksheep/__init__.py @@ -3,7 +3,7 @@ used types to reduce the verbosity of the imports statements. """ __author__ = "Roberto Prevato " -__version__ = "2.0.1" +__version__ = "2.0.2" from .contents import Content as Content from .contents import FormContent as FormContent diff --git a/blacksheep/server/openapi/ui.py b/blacksheep/server/openapi/ui.py index c713694c..d6b32adb 100644 --- a/blacksheep/server/openapi/ui.py +++ b/blacksheep/server/openapi/ui.py @@ -1,12 +1,31 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Callable +from typing import Callable, Optional from blacksheep.messages import Request, Response from blacksheep.server.files.static import get_response_for_static_content from blacksheep.server.resources import get_resource_file_content from blacksheep.utils.time import utcnow +SWAGGER_UI_JS_URL = ( + "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" +) +SWAGGER_UI_CSS_URL = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" +SWAGGER_UI_FONT = None + +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 UIFilesOptions: + js_url: str + css_url: Optional[str] = None + fonts_url: Optional[str] = None + @dataclass class UIOptions: @@ -15,9 +34,17 @@ class UIOptions: class UIProvider(ABC): - def __init__(self, ui_path: str) -> None: + ui_files: UIFilesOptions + ui_path: str + + def __init__( + self, + ui_path: str, + ui_files: Optional[UIFilesOptions] = None, + ) -> None: super().__init__() self.ui_path = ui_path + self.ui_files = ui_files if ui_files else self.default_ui_files @abstractmethod def build_ui(self, options: UIOptions) -> None: @@ -31,10 +58,18 @@ def get_ui_handler(self) -> Callable[[Request], Response]: Returns a request handler for the route that serves a UI. """ + @property + def default_ui_files(self) -> UIFilesOptions: + ... + class SwaggerUIProvider(UIProvider): - def __init__(self, ui_path: str = "/docs") -> None: - super().__init__(ui_path) + def __init__( + self, + ui_path: str = "/docs", + ui_files_options: Optional[UIFilesOptions] = None, + ) -> None: + super().__init__(ui_path, ui_files_options) self._ui_html: bytes = b"" @@ -46,6 +81,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_URL##", self.ui_files.js_url) + .replace("##CSS_URL##", self.ui_files.css_url or "") ) def build_ui(self, options: UIOptions) -> None: @@ -61,10 +98,16 @@ def get_open_api_ui(request: Request) -> Response: return get_open_api_ui + @property + 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") -> None: - super().__init__(ui_path) + def __init__( + self, ui_path: str = "/redocs", ui_files: Optional[UIFilesOptions] = None + ) -> None: + super().__init__(ui_path, ui_files) self._ui_html: bytes = b"" @@ -76,6 +119,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_URL##", self.ui_files.js_url) + .replace("##FONT_URL##", self.ui_files.fonts_url or "") ) def build_ui(self, options: UIOptions) -> None: @@ -90,3 +135,7 @@ def get_open_api_ui(request: Request) -> Response: ) return get_open_api_ui + + @property + 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/res/redoc-ui.html b/blacksheep/server/res/redoc-ui.html index 5d26bc7a..036902e4 100644 --- a/blacksheep/server/res/redoc-ui.html +++ b/blacksheep/server/res/redoc-ui.html @@ -2,10 +2,10 @@ ##PAGE_TITLE## - - - - + + + + + + + + """.strip() diff --git a/itests/utils.py b/itests/utils.py index 1244c1d2..582bd3c0 100644 --- a/itests/utils.py +++ b/itests/utils.py @@ -71,3 +71,7 @@ def get_sleep_time(): if os.name == "nt": return 1.5 return 0.5 + + +def get_test_files_url(url: str): + return f"my-cdn-foo:{url}"