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 @@ - +