diff --git a/docs/usage/extensions.md b/docs/usage/extensions.md new file mode 100644 index 00000000..4f6b96b3 --- /dev/null +++ b/docs/usage/extensions.md @@ -0,0 +1,17 @@ +# Extensions + +## :warning: Work in Progress! + +The Python handler supports extensions through +[*mkdocstrings*' handler extensions](https://mkdocstrings.github.io/usage/handlers/#handler-extensions). + +Specifically, additional templates can be added to the handler, +and Griffe extensions can instruct the handler to use a particular template +for a particular object by setting a value in the Griffe object's `extra` dictionary: + +```python title="griffe_extension.py" +obj = ... # get a reference to a Griffe object +if "mkdocstrings" not in obj.extra: + obj.extra["mkdocstrings"] = {} +obj.extra["mkdocstrings"]["template"] = "template_name.html" +``` diff --git a/mkdocs.yml b/mkdocs.yml index 50ba6925..85fd7ef7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ nav: - Sphinx: usage/docstrings/sphinx.md - Advanced: - Customization: usage/customization.md + - Extensions: usage/extensions.md # defer to gen-files + literate-nav - Code Reference: reference/ - Development: diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 9bfb02f4..c5a06d0c 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -297,7 +297,8 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa mutabled_config = dict(copy.deepcopy(config)) final_config = ChainMap(mutabled_config, self.default_config) - template = self.env.get_template(f"{data.kind.value}.html") + template_name = rendering.do_get_template(data) + template = self.env.get_template(template_name) # Heading level is a "state" variable, that will change at each step # of the rendering recursion. Therefore, it's easier to use it as a plain value @@ -335,6 +336,7 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore self.env.filters["format_signature"] = rendering.do_format_signature self.env.filters["filter_objects"] = rendering.do_filter_objects self.env.filters["stash_crossref"] = lambda ref, length: ref + self.env.filters["get_template"] = rendering.do_get_template def get_anchors(self, data: CollectorItem) -> set[str]: # noqa: D102 (ignore missing docstring) try: diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index d1f0eb75..5c8b0f19 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -248,3 +248,16 @@ def formatter(code: str, line_length: int) -> str: return format_str(code, mode=mode) return formatter + + +def do_get_template(obj: Object) -> str: + """Get the template name used to render an object. + + Parameters: + obj: A Griffe object. + + Returns: + A template name. + """ + extra_data = getattr(obj, "extra", {}).get("mkdocstrings", {}) + return extra_data.get("template", "") or f"{obj.kind.value}.html" diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/children.html b/src/mkdocstrings_handlers/python/templates/material/_base/children.html index 9e27ed0f..dda1c5ff 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/children.html +++ b/src/mkdocstrings_handlers/python/templates/material/_base/children.html @@ -27,7 +27,7 @@ {% with heading_level = heading_level + extra_level %} {% for attribute in attributes|order_members(config.members_order, members_list) %} {% if not attribute.is_alias or attribute.is_explicitely_exported %} - {% include "attribute.html" with context %} + {% include attribute|get_template with context %} {% endif %} {% endfor %} {% endwith %} @@ -42,7 +42,7 @@ {% with heading_level = heading_level + extra_level %} {% for class in classes|order_members(config.members_order, members_list) %} {% if not class.is_alias or class.is_explicitely_exported %} - {% include "class.html" with context %} + {% include class|get_template with context %} {% endif %} {% endfor %} {% endwith %} @@ -58,7 +58,7 @@ {% for function in functions|order_members(config.members_order, members_list) %} {% if not (obj.kind.value == "class" and function.name == "__init__" and config.merge_init_into_class) %} {% if not function.is_alias or function.is_explicitely_exported %} - {% include "function.html" with context %} + {% include function|get_template with context %} {% endif %} {% endif %} {% endfor %} @@ -75,7 +75,7 @@ {% with heading_level = heading_level + extra_level %} {% for module in modules|order_members(config.members_order, members_list) %} {% if not module.is_alias or module.is_explicitely_exported %} - {% include "module.html" with context %} + {% include module|get_template with context %} {% endif %} {% endfor %} {% endwith %} @@ -91,26 +91,26 @@ filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring)| order_members(config.members_order, members_list) %} - {% if not (obj.kind.value == "class" and child.name == "__init__" and config.merge_init_into_class) %} + {% if not (obj.is_class and child.name == "__init__" and config.merge_init_into_class) %} - {% if child.kind.value == "attribute" %} + {% if child.is_attribute %} {% with attribute = child %} - {% include "attribute.html" with context %} + {% include attribute|get_template with context %} {% endwith %} - {% elif child.kind.value == "class" %} + {% elif child.is_class %} {% with class = child %} - {% include "class.html" with context %} + {% include class|get_template with context %} {% endwith %} - {% elif child.kind.value == "function" %} + {% elif child.is_function %} {% with function = child %} - {% include "function.html" with context %} + {% include function|get_template with context %} {% endwith %} - {% elif child.kind.value == "module" and config.show_submodules %} + {% elif child.is_module and config.show_submodules %} {% with module = child %} - {% include "module.html" with context %} + {% include module|get_template with context %} {% endwith %} {% endif %}