From d799d2f3903bce44fb751f8cf3fb8078d25549da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 27 Apr 2024 17:11:10 +0200 Subject: [PATCH 1/6] feat: Support blank line between `::: path` and YAML options Issue-450: https://github.com/mkdocstrings/mkdocstrings/issues/450 --- src/mkdocstrings/extension.py | 4 ++++ tests/test_extension.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index bef8c799..23e90cff 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -116,6 +116,10 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None: block, the_rest = self.detab(block) + if not block and blocks and blocks[0].startswith((" handler:", " options:")): + # YAML options were separated from the `:::` line by a blank line. + block = blocks.pop(0) + if match: identifier = match["name"] heading_level = match["heading"].count("#") diff --git a/tests/test_extension.py b/tests/test_extension.py index affd6c6a..976f376c 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -172,6 +172,11 @@ def test_use_options_yaml_key(ext_markdown: Markdown) -> None: assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 2") +def test_use_yaml_options_after_blank_line(ext_markdown: Markdown) -> None: + """Check that YAML options are detected even after a blank line.""" + assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n\n options:\n heading_level: 2") + + @pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"admonition": {}}]}], indirect=["ext_markdown"]) def test_removing_duplicated_headings(ext_markdown: Markdown) -> None: """Assert duplicated headings are removed from the output.""" From 1532b59a6efd99fed846cf7edfd0b26525700d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 27 Apr 2024 17:12:48 +0200 Subject: [PATCH 2/6] feat: Support `once` parameter in logging methods, allowing to log a message only once with a given logger This will be useful when issuing warning messages in templates, for example when deprecating things, as we don't want to show the message dozens of time (each time the template is used), but rather just once. --- src/mkdocstrings/loggers.py | 51 ++++++++++++++++++++++++++--- tests/test_loggers.py | 64 +++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 tests/test_loggers.py diff --git a/src/mkdocstrings/loggers.py b/src/mkdocstrings/loggers.py index 63502474..9d7408cc 100644 --- a/src/mkdocstrings/loggers.py +++ b/src/mkdocstrings/loggers.py @@ -17,7 +17,7 @@ except ImportError: TEMPLATES_DIRS: Sequence[Path] = () else: - TEMPLATES_DIRS = tuple(mkdocstrings_handlers.__path__) # type: ignore[arg-type] + TEMPLATES_DIRS = tuple(mkdocstrings_handlers.__path__) if TYPE_CHECKING: @@ -25,7 +25,25 @@ class LoggerAdapter(logging.LoggerAdapter): - """A logger adapter to prefix messages.""" + """A logger adapter to prefix messages. + + This adapter also adds an additional parameter to logging methods + called `once`: if `True`, the message will only be logged once. + + Examples: + In Python code: + + >>> logger = get_logger("myplugin") + >>> logger.debug("This is a debug message.") + >>> logger.info("This is an info message.", once=True) + + In Jinja templates (logger available in context as `log`): + + ```jinja + {{ log.debug("This is a debug message.") }} + {{ log.info("This is an info message.", once=True) }} + ``` + """ def __init__(self, prefix: str, logger: logging.Logger): """Initialize the object. @@ -36,6 +54,7 @@ def __init__(self, prefix: str, logger: logging.Logger): """ super().__init__(logger, {}) self.prefix = prefix + self._logged: set[tuple[LoggerAdapter, str]] = set() def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, Any]: """Process the message. @@ -49,11 +68,32 @@ def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, Any] """ return f"{self.prefix}: {msg}", kwargs + def log(self, level: int, msg: object, *args: object, **kwargs: object) -> None: + """Log a message. + + Arguments: + level: The logging level. + msg: The message. + *args: Additional arguments passed to parent method. + **kwargs: Additional keyword arguments passed to parent method. + """ + if kwargs.pop("once", False): + if (key := (self, str(msg))) in self._logged: + return + self._logged.add(key) + super().log(level, msg, *args, **kwargs) # type: ignore[arg-type] + class TemplateLogger: """A wrapper class to allow logging in templates. - Attributes: + The logging methods provided by this class all accept + two parameters: + + - `msg`: The message to log. + - `once`: If `True`, the message will only be logged once. + + Methods: debug: Function to log a DEBUG message. info: Function to log an INFO message. warning: Function to log a WARNING message. @@ -85,18 +125,19 @@ def get_template_logger_function(logger_func: Callable) -> Callable: """ @pass_context - def wrapper(context: Context, msg: str | None = None) -> str: + def wrapper(context: Context, msg: str | None = None, **kwargs: Any) -> str: """Log a message. Arguments: context: The template context, automatically provided by Jinja. msg: The message to log. + **kwargs: Additional arguments passed to the logger function. Returns: An empty string. """ template_path = get_template_path(context) - logger_func(f"{template_path}: {msg or 'Rendering'}") + logger_func(f"{template_path}: {msg or 'Rendering'}", **kwargs) return "" return wrapper diff --git a/tests/test_loggers.py b/tests/test_loggers.py new file mode 100644 index 00000000..1644c0f0 --- /dev/null +++ b/tests/test_loggers.py @@ -0,0 +1,64 @@ +"""Tests for the loggers module.""" + +from unittest.mock import MagicMock + +import pytest + +from mkdocstrings.loggers import get_logger, get_template_logger + + +@pytest.mark.parametrize( + "kwargs", + [ + {}, + {"once": False}, + {"once": True}, + ], +) +def test_logger(kwargs: dict, caplog: pytest.LogCaptureFixture) -> None: + """Test logger methods. + + Parameters: + kwargs: Keyword arguments passed to the logger methods. + """ + logger = get_logger("mkdocstrings.test") + caplog.set_level(0) + for _ in range(2): + logger.debug("Debug message", **kwargs) + logger.info("Info message", **kwargs) + logger.warning("Warning message", **kwargs) + logger.error("Error message", **kwargs) + logger.critical("Critical message", **kwargs) + if kwargs.get("once", False): + assert len(caplog.records) == 5 + else: + assert len(caplog.records) == 10 + + +@pytest.mark.parametrize( + "kwargs", + [ + {}, + {"once": False}, + {"once": True}, + ], +) +def test_template_logger(kwargs: dict, caplog: pytest.LogCaptureFixture) -> None: + """Test template logger methods. + + Parameters: + kwargs: Keyword arguments passed to the template logger methods. + """ + logger = get_template_logger() + mock = MagicMock() + caplog.set_level(0) + for _ in range(2): + logger.debug(mock, "Debug message", **kwargs) + logger.info(mock, "Info message", **kwargs) + logger.warning(mock, "Warning message", **kwargs) + logger.error(mock, "Error message", **kwargs) + logger.critical(mock, "Critical message", **kwargs) + if kwargs.get("once", False): + assert len(caplog.records) == 5 + else: + assert len(caplog.records) == 10 From 253d215426f28939d544502fb1032b2c796c34ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 27 Apr 2024 17:13:45 +0200 Subject: [PATCH 3/6] docs: Load inventories for MkDocs and Markdown --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 860ce66e..b46e9872 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,8 @@ plugins: - https://docs.python.org/3/objects.inv - https://installer.readthedocs.io/en/stable/objects.inv # demonstration purpose in the docs - https://mkdocstrings.github.io/autorefs/objects.inv + - https://www.mkdocs.org/objects.inv + - https://python-markdown.github.io/objects.inv paths: [src] options: docstring_options: From 7ff1681d417bd68b8a7ce6f9487638bda03e3710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 27 Apr 2024 17:13:54 +0200 Subject: [PATCH 4/6] docs: Enable parameter headings --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index b46e9872..30afc977 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -148,6 +148,7 @@ plugins: heading_level: 1 inherited_members: true merge_init_into_class: true + parameter_headings: true separate_signature: true show_root_heading: true show_root_full_path: false From c5b5f697c83271d961c7ac795412d6b4964ba2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 27 Apr 2024 17:53:41 +0200 Subject: [PATCH 5/6] refactor: Allow specifying name of template loggers --- src/mkdocstrings/handlers/base.py | 3 ++- src/mkdocstrings/loggers.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py index f52e17dc..27c22db1 100644 --- a/src/mkdocstrings/handlers/base.py +++ b/src/mkdocstrings/handlers/base.py @@ -74,6 +74,7 @@ class BaseHandler: To add custom CSS, add an `extra_css` variable or create an 'style.css' file beside the templates. """ + # TODO: Make name mandatory? name: str = "" """The handler's name, for example "python".""" domain: str = "default" @@ -132,7 +133,7 @@ def __init__(self, handler: str, theme: str, custom_templates: str | None = None auto_reload=False, # Editing a template in the middle of a build is not useful. ) self.env.filters["any"] = do_any - self.env.globals["log"] = get_template_logger() + self.env.globals["log"] = get_template_logger(self.name) self._headings: list[Element] = [] self._md: Markdown = None # type: ignore[assignment] # To be populated in `update_env`. diff --git a/src/mkdocstrings/loggers.py b/src/mkdocstrings/loggers.py index 9d7408cc..240e1808 100644 --- a/src/mkdocstrings/loggers.py +++ b/src/mkdocstrings/loggers.py @@ -177,10 +177,14 @@ def get_logger(name: str) -> LoggerAdapter: return LoggerAdapter(name.split(".", 1)[0], logger) -def get_template_logger() -> TemplateLogger: +def get_template_logger(handler_name: str | None = None) -> TemplateLogger: """Return a logger usable in templates. + Parameters: + handler_name: The name of the handler. + Returns: A template logger. """ - return TemplateLogger(get_logger("mkdocstrings.templates")) + handler_name = handler_name or "base" + return TemplateLogger(get_logger(f"mkdocstrings_handlers.{handler_name}.templates")) From 87d82299773a0203329d9d45ce3e1210c3320375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 27 Apr 2024 18:12:23 +0200 Subject: [PATCH 6/6] chore: Prepare release 0.25.0 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 723eb24c..7066ff44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.25.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.25.0) - 2024-04-27 + +[Compare with 0.24.3](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.3...0.25.0) + +### Features + +- Support `once` parameter in logging methods, allowing to log a message only once with a given logger ([1532b59](https://github.com/mkdocstrings/mkdocstrings/commit/1532b59a6efd99fed846cf7edfd0b26525700d3f) by Timothée Mazzucotelli). +- Support blank line between `::: path` and YAML options ([d799d2f](https://github.com/mkdocstrings/mkdocstrings/commit/d799d2f3903bce44fb751f8cf3fb8078d25549da) by Timothée Mazzucotelli). [Issue-450](https://github.com/mkdocstrings/mkdocstrings/issues/450) + +### Code Refactoring + +- Allow specifying name of template loggers ([c5b5f69](https://github.com/mkdocstrings/mkdocstrings/commit/c5b5f697c83271d961c7ac795412d6b4964ba2b7) by Timothée Mazzucotelli). + ## [0.24.3](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.24.3) - 2024-04-05 [Compare with 0.24.2](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.2...0.24.3)