From ade0f07d53f354408c9b873e2ad17c85e1dd7bfc Mon Sep 17 00:00:00 2001 From: Pedro Brochado Date: Mon, 13 May 2024 15:49:08 -0300 Subject: [PATCH 1/3] Add filename filter for performance gain closes: #25 --- mkdocs_swagger_ui_tag/plugin.py | 177 ++++++++++++--------- tests/fixtures/mkdocs-filter-filenames.yml | 6 + tests/test_builds.py | 22 ++- 3 files changed, 128 insertions(+), 77 deletions(-) create mode 100644 tests/fixtures/mkdocs-filter-filenames.yml diff --git a/mkdocs_swagger_ui_tag/plugin.py b/mkdocs_swagger_ui_tag/plugin.py index d5e8ff0..7be11dd 100644 --- a/mkdocs_swagger_ui_tag/plugin.py +++ b/mkdocs_swagger_ui_tag/plugin.py @@ -12,7 +12,16 @@ from mkdocs.config import config_options from mkdocs.plugins import BasePlugin -log = logging.getLogger(__name__) +try: + # mkdocs logging utility for mkdocs 1.5+ + # https://www.mkdocs.org/dev-guide/plugins/#logging-in-plugins + from mkdocs.plugins import get_plugin_logger + + log = get_plugin_logger(__name__) +except ImportError: + # compat + log = logging.getLogger(f"mkdocs.plugins.{__name__}") # type: ignore + base_path = os.path.dirname(os.path.abspath(__file__)) @@ -56,6 +65,10 @@ class SwaggerUIPlugin(BasePlugin): ("validatorUrl", config_options.Type(str, default="none")), ("extra_css", config_options.Type(list, default=[])), ("dark_scheme_name", config_options.Type(str, default="slate")), + ( + "filter_filenames", + config_options.ListOfItems(config_options.Type(str), default=[]), + ), ) def on_pre_page(self, page, config, files, **kwargs): @@ -107,94 +120,105 @@ def on_post_page(self, output, page, config, **kwargs): Add javascript code to update iframe height Create a html with Swagger UI for iframe """ + # Using filename filter for performance + # https://github.com/blueswen/mkdocs-swagger-ui-tag/issues/25 + if filter_list := self.config["filter_filenames"]: + if page.file.name not in filter_list: + return output soup = BeautifulSoup(output, "html.parser") swagger_ui_list = soup.find_all("swagger-ui") + + # No tags found, we can return earlier + if len(swagger_ui_list) == 0: + return output + + # Regular processing iframe_id_list = [] grouped_list = [] - if len(swagger_ui_list) > 0: - css_dir = utils.get_relative_url( - utils.normalize_url("assets/stylesheets/"), page.url - ) - js_dir = utils.get_relative_url( - utils.normalize_url("assets/javascripts/"), page.url + log.info(f"Processing file '{page.file.src_uri}'") + css_dir = utils.get_relative_url( + utils.normalize_url("assets/stylesheets/"), page.url + ) + js_dir = utils.get_relative_url( + utils.normalize_url("assets/javascripts/"), page.url + ) + default_oauth2_redirect_file = utils.get_relative_url( + utils.normalize_url("assets/swagger-ui/oauth2-redirect.html"), page.url + ) + env = Environment( + loader=FileSystemLoader(os.path.join(base_path, "swagger-ui")) + ) + template = env.get_template("swagger.html") + extra_css_files = list( + map( + lambda f: utils.get_relative_url(utils.normalize_url(f), page.url), + self.config["extra_css"], ) - default_oauth2_redirect_file = utils.get_relative_url( - utils.normalize_url("assets/swagger-ui/oauth2-redirect.html"), page.url + ) + + page_dir = os.path.dirname( + os.path.join(config["site_dir"], urlunquote(page.url)) + ) + if not os.path.exists(page_dir): + os.makedirs(page_dir) + + def render_template(openapi_spec_url, swagger_ui_ele): + cur_options = self.process_options(config, swagger_ui_ele) + cur_oath2_prop = self.process_oath2_prop(swagger_ui_ele) + oauth2_redirect_url = cur_options.pop("oauth2RedirectUrl", "") + if not oauth2_redirect_url: + oauth2_redirect_url = default_oauth2_redirect_file + + template_output = template.render( + css_dir=css_dir, + extra_css_files=extra_css_files, + js_dir=js_dir, + background=self.config["background"], + id="{{ID_PLACEHOLDER}}", # ID is unknown yet - it's the hash of the content. + openapi_spec_url=openapi_spec_url, + oauth2_redirect_url=oauth2_redirect_url, + validatorUrl=self.config["validatorUrl"], + options_str=json.dumps(cur_options, indent=4)[1:-1], + oath2_prop_str=json.dumps(cur_oath2_prop), ) - env = Environment( - loader=FileSystemLoader(os.path.join(base_path, "swagger-ui")) + cur_id = hashlib.sha256(template_output.encode()).hexdigest()[:8] + iframe_filename = f"swagger-{cur_id}.html" + template_output = template_output.replace("{{ID_PLACEHOLDER}}", cur_id) + with open(os.path.join(page_dir, iframe_filename), "w") as f: + f.write(template_output) + self.replace_with_iframe(soup, swagger_ui_ele, cur_id, iframe_filename) + return cur_id + + for swagger_ui_ele in swagger_ui_list: + if swagger_ui_ele.has_attr("grouped"): + grouped_list.append(swagger_ui_ele) + continue + + openapi_spec_url = self.path_to_url( + page.file, swagger_ui_ele.get("src", "") ) - template = env.get_template("swagger.html") - extra_css_files = list( - map( - lambda f: utils.get_relative_url(utils.normalize_url(f), page.url), - self.config["extra_css"], + iframe_id_list.append( + render_template( + openapi_spec_url=openapi_spec_url, swagger_ui_ele=swagger_ui_ele ) ) - page_dir = os.path.dirname( - os.path.join(config["site_dir"], urlunquote(page.url)) - ) - if not os.path.exists(page_dir): - os.makedirs(page_dir) - - def render_template(openapi_spec_url, swagger_ui_ele): - cur_options = self.process_options(config, swagger_ui_ele) - cur_oath2_prop = self.process_oath2_prop(swagger_ui_ele) - oauth2_redirect_url = cur_options.pop("oauth2RedirectUrl", "") - if not oauth2_redirect_url: - oauth2_redirect_url = default_oauth2_redirect_file - - template_output = template.render( - css_dir=css_dir, - extra_css_files=extra_css_files, - js_dir=js_dir, - background=self.config["background"], - id="{{ID_PLACEHOLDER}}", # ID is unknown yet - it's the hash of the content. - openapi_spec_url=openapi_spec_url, - oauth2_redirect_url=oauth2_redirect_url, - validatorUrl=self.config["validatorUrl"], - options_str=json.dumps(cur_options, indent=4)[1:-1], - oath2_prop_str=json.dumps(cur_oath2_prop), - ) - cur_id = hashlib.sha256(template_output.encode()).hexdigest()[:8] - iframe_filename = f"swagger-{cur_id}.html" - template_output = template_output.replace("{{ID_PLACEHOLDER}}", cur_id) - with open(os.path.join(page_dir, iframe_filename), "w") as f: - f.write(template_output) - self.replace_with_iframe(soup, swagger_ui_ele, cur_id, iframe_filename) - return cur_id - - for swagger_ui_ele in swagger_ui_list: - if swagger_ui_ele.has_attr("grouped"): - grouped_list.append(swagger_ui_ele) - continue - - openapi_spec_url = self.path_to_url( - page.file, swagger_ui_ele.get("src", "") - ) - iframe_id_list.append( - render_template( - openapi_spec_url=openapi_spec_url, swagger_ui_ele=swagger_ui_ele - ) - ) - - if grouped_list: - openapi_spec_url = [] - for swagger_ui_ele in grouped_list: - cur_url = self.path_to_url(page.file, swagger_ui_ele.get("src", "")) - cur_name = swagger_ui_ele.get("name", swagger_ui_ele.get("src", "")) - openapi_spec_url.append({"url": cur_url, "name": cur_name}) + if grouped_list: + openapi_spec_url = [] + for swagger_ui_ele in grouped_list: + cur_url = self.path_to_url(page.file, swagger_ui_ele.get("src", "")) + cur_name = swagger_ui_ele.get("name", swagger_ui_ele.get("src", "")) + openapi_spec_url.append({"url": cur_url, "name": cur_name}) - # only use options from first grouped swagger ui tag - render_template( - openapi_spec_url=openapi_spec_url, swagger_ui_ele=grouped_list[0] - ) - # only keep first grouped swagger ui tag - for rest_swagger_ui_ele in grouped_list[1:]: - rest_swagger_ui_ele.extract() + # only use options from first grouped swagger ui tag + render_template( + openapi_spec_url=openapi_spec_url, swagger_ui_ele=grouped_list[0] + ) + # only keep first grouped swagger ui tag + for rest_swagger_ui_ele in grouped_list[1:]: + rest_swagger_ui_ele.extract() js_code = soup.new_tag("script") # trigger from iframe body ResizeObserver @@ -371,6 +395,7 @@ def process_oath2_prop(self, swagger_ui_ele): def on_post_build(self, config, **kwargs): """Copy Swagger UI css and js files to assets directory""" + log.info("Copying swagger ui assets.") output_base_path = os.path.join(config["site_dir"], "assets") css_path = os.path.join(output_base_path, "stylesheets") for file_name in os.listdir( diff --git a/tests/fixtures/mkdocs-filter-filenames.yml b/tests/fixtures/mkdocs-filter-filenames.yml new file mode 100644 index 0000000..76a44d5 --- /dev/null +++ b/tests/fixtures/mkdocs-filter-filenames.yml @@ -0,0 +1,6 @@ +site_name: test mkdocs_swagger_ui_tag +use_directory_urls: true + +plugins: + - swagger-ui-tag: + filter_filenames: ["index"] diff --git a/tests/test_builds.py b/tests/test_builds.py index c3a5255..f4e4834 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -526,7 +526,7 @@ def test_empty(tmp_path): file = testproject_path / "site/empty/index.html" contents = file.read_text(encoding="utf8") - validate_additional_script_code(contents, exists=True) + validate_additional_script_code(contents, exists=False) def test_error(tmp_path): @@ -548,3 +548,23 @@ def test_template(tmp_path): iframe_content_list = validate_iframe(contents, file.parent) assert len(iframe_content_list) == 2 + + +def test_filter_filenames(tmp_path): + mkdocs_file = "mkdocs-filter-filenames.yml" + testproject_path = validate_mkdocs_file( + tmp_path, + f"tests/fixtures/{mkdocs_file}", + ) + file = testproject_path / "site/index.html" + contents = file.read_text(encoding="utf8") + iframe_content_list = validate_iframe(contents, file.parent) + assert len(iframe_content_list) == 1 + + file = testproject_path / "site/empty/index.html" + contents = file.read_text(encoding="utf8") + validate_additional_script_code(contents, exists=False) + + file = testproject_path / "site/multiple/index.html" + contents = file.read_text(encoding="utf8") + validate_additional_script_code(contents, exists=False) From 04b12efe9a0165a665a82595d3fa335facdbac4e Mon Sep 17 00:00:00 2001 From: blueswen Date: Thu, 23 May 2024 22:39:45 +0800 Subject: [PATCH 2/3] Rename filter_filenames to filter_files and add test --- mkdocs_swagger_ui_tag/plugin.py | 17 +++-------- ...-filenames.yml => mkdocs-filter-files.yml} | 4 ++- tests/test_builds.py | 30 +++++++++++++++++-- 3 files changed, 35 insertions(+), 16 deletions(-) rename tests/fixtures/{mkdocs-filter-filenames.yml => mkdocs-filter-files.yml} (52%) diff --git a/mkdocs_swagger_ui_tag/plugin.py b/mkdocs_swagger_ui_tag/plugin.py index 7be11dd..b426440 100644 --- a/mkdocs_swagger_ui_tag/plugin.py +++ b/mkdocs_swagger_ui_tag/plugin.py @@ -66,7 +66,7 @@ class SwaggerUIPlugin(BasePlugin): ("extra_css", config_options.Type(list, default=[])), ("dark_scheme_name", config_options.Type(str, default="slate")), ( - "filter_filenames", + "filter_files", config_options.ListOfItems(config_options.Type(str), default=[]), ), ) @@ -120,10 +120,10 @@ def on_post_page(self, output, page, config, **kwargs): Add javascript code to update iframe height Create a html with Swagger UI for iframe """ - # Using filename filter for performance + # Using file filter for performance # https://github.com/blueswen/mkdocs-swagger-ui-tag/issues/25 - if filter_list := self.config["filter_filenames"]: - if page.file.name not in filter_list: + if filter_list := self.config["filter_files"]: + if page.file.src_path not in filter_list: return output soup = BeautifulSoup(output, "html.parser") @@ -239,16 +239,7 @@ def render_template(openapi_spec_url, swagger_ui_ele): for (var i = 0; i < iframes.length; i++) { iframe_id_list.push(iframes[i].getAttribute("id")) } - """ - if len(iframe_id_list) == 0: - js_code.string += """ - let ticking = true; - """ - else: - js_code.string += """ let ticking = false; - """ - js_code.string += """ document.addEventListener('scroll', function(e) { if (!ticking) { window.requestAnimationFrame(()=> { diff --git a/tests/fixtures/mkdocs-filter-filenames.yml b/tests/fixtures/mkdocs-filter-files.yml similarity index 52% rename from tests/fixtures/mkdocs-filter-filenames.yml rename to tests/fixtures/mkdocs-filter-files.yml index 76a44d5..8c18af7 100644 --- a/tests/fixtures/mkdocs-filter-filenames.yml +++ b/tests/fixtures/mkdocs-filter-files.yml @@ -3,4 +3,6 @@ use_directory_urls: true plugins: - swagger-ui-tag: - filter_filenames: ["index"] + filter_files: + - index.md + - sub_dir/page_in_sub_dir.md diff --git a/tests/test_builds.py b/tests/test_builds.py index f4e4834..1b584a4 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -3,6 +3,7 @@ import os import re import shutil +from unittest.mock import MagicMock # other 3rd party from bs4 import BeautifulSoup @@ -550,8 +551,8 @@ def test_template(tmp_path): assert len(iframe_content_list) == 2 -def test_filter_filenames(tmp_path): - mkdocs_file = "mkdocs-filter-filenames.yml" +def test_filter_files(tmp_path): + mkdocs_file = "mkdocs-filter-files.yml" testproject_path = validate_mkdocs_file( tmp_path, f"tests/fixtures/{mkdocs_file}", @@ -561,6 +562,11 @@ def test_filter_filenames(tmp_path): iframe_content_list = validate_iframe(contents, file.parent) assert len(iframe_content_list) == 1 + file = testproject_path / "site/sub_dir/page_in_sub_dir/index.html" + contents = file.read_text(encoding="utf8") + iframe_content_list = validate_iframe(contents, file.parent) + assert len(iframe_content_list) == 1 + file = testproject_path / "site/empty/index.html" contents = file.read_text(encoding="utf8") validate_additional_script_code(contents, exists=False) @@ -568,3 +574,23 @@ def test_filter_filenames(tmp_path): file = testproject_path / "site/multiple/index.html" contents = file.read_text(encoding="utf8") validate_additional_script_code(contents, exists=False) + + +def test_import_error(monkeypatch): + # Simulate ImportError for 'get_plugin_logger' + with monkeypatch.context() as m: + m.setattr( + "mkdocs.plugins.get_plugin_logger", MagicMock(side_effect=ImportError) + ) + + # Reload the module to apply the monkeypatch + import importlib + + from mkdocs_swagger_ui_tag import plugin + + importlib.reload(plugin) + # Test that the fallback logger is used + assert plugin.log.name == f"mkdocs.plugins.{plugin.__name__}" + + # Ensure the logger works without raising errors + plugin.log.info("Test message") From a70354be6c3d2344287f854abb9044f2c3e88dc7 Mon Sep 17 00:00:00 2001 From: Pedro Brochado Date: Mon, 27 May 2024 11:46:39 -0300 Subject: [PATCH 3/3] Fix windows path issue --- mkdocs_swagger_ui_tag/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mkdocs_swagger_ui_tag/plugin.py b/mkdocs_swagger_ui_tag/plugin.py index b426440..067d140 100644 --- a/mkdocs_swagger_ui_tag/plugin.py +++ b/mkdocs_swagger_ui_tag/plugin.py @@ -123,7 +123,8 @@ def on_post_page(self, output, page, config, **kwargs): # Using file filter for performance # https://github.com/blueswen/mkdocs-swagger-ui-tag/issues/25 if filter_list := self.config["filter_files"]: - if page.file.src_path not in filter_list: + filter_list = [os.path.normpath(f) for f in filter_list] + if os.path.normpath(page.file.src_path) not in filter_list: return output soup = BeautifulSoup(output, "html.parser")