Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Deprecate *.html templates in favor of *.html.jinja ones #155

Merged
merged 8 commits into from
Apr 28, 2024
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"mkdocstrings>=0.24.2",
"mkdocstrings>=0.25",
"griffe>=0.44",
]

Expand Down
14 changes: 13 additions & 1 deletion src/mkdocstrings_handlers/python/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
from collections import ChainMap
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping, Sequence

from griffe.collections import LinesCollection, ModulesCollection
Expand Down Expand Up @@ -208,6 +209,17 @@ def __init__(
**kwargs: Same thing, but with keyword arguments.
"""
super().__init__(*args, **kwargs)

# Warn if user overrides base templates.
if custom_templates := kwargs.get("custom_templates", ()):
config_dir = Path(config_file_path or "./mkdocs.yml").parent
for theme_dir in config_dir.joinpath(custom_templates, "python").iterdir():
if theme_dir.joinpath("_base").is_dir():
logger.warning(
f"Overriding base template '{theme_dir.name}/_base/<template>.html.jinja' is not supported, "
f"override '{theme_dir.name}/<template>.html.jinja' instead",
)

self._config_file_path = config_file_path
self._load_external_modules = load_external_modules
paths = paths or []
Expand Down Expand Up @@ -321,7 +333,7 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem:
def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa: D102 (ignore missing docstring)
final_config = ChainMap(config, self.default_config) # type: ignore[arg-type]

template_name = rendering.do_get_template(data)
template_name = rendering.do_get_template(self.env, data)
template = self.env.get_template(template_name)

# Heading level is a "state" variable, that will change at each step
Expand Down
50 changes: 42 additions & 8 deletions src/mkdocstrings_handlers/python/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@
import sys
import warnings
from functools import lru_cache, partial
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence

from griffe.dataclasses import Alias, Object
from griffe.docstrings.dataclasses import (
DocstringSectionAttributes,
DocstringSectionClasses,
DocstringSectionFunctions,
DocstringSectionModules,
)
from jinja2 import pass_context
from jinja2 import TemplateNotFound, pass_context, pass_environment
from markupsafe import Markup
from mkdocstrings.loggers import get_logger

if TYPE_CHECKING:
from griffe.dataclasses import Alias, Attribute, Class, Function, Module, Object
from griffe.dataclasses import Attribute, Class, Function, Module
from jinja2 import Environment, Template
from jinja2.runtime import Context
from mkdocstrings.handlers.base import CollectorItem

Expand Down Expand Up @@ -137,7 +140,8 @@ def do_format_signature(
The same code, formatted.
"""
env = context.environment
template = env.get_template("signature.html")
# TODO: Stop using `do_get_template` when `*.html` templates are removed.
template = env.get_template(do_get_template(env, "signature"))
config_annotations = context.parent["config"]["show_signature_annotations"]
old_stash_ref_filter = env.filters["stash_crossref"]

Expand Down Expand Up @@ -204,7 +208,8 @@ def do_format_attribute(
The same code, formatted.
"""
env = context.environment
template = env.get_template("expression.html")
# TODO: Stop using `do_get_template` when `*.html` templates are removed.
template = env.get_template(do_get_template(env, "expression"))
annotations = context.parent["config"]["show_signature_annotations"]
separate_signature = context.parent["config"]["separate_signature"]
old_stash_ref_filter = env.filters["stash_crossref"]
Expand Down Expand Up @@ -448,17 +453,46 @@ def formatter(code: str, line_length: int) -> str:
return formatter


def do_get_template(obj: Object) -> str:
@pass_environment
def do_get_template(env: Environment, obj: str | Object) -> str | Template:
"""Get the template name used to render an object.

Parameters:
obj: A Griffe object.
env: The Jinja environment, passed automatically.
obj: A Griffe object, or a template name.

Returns:
A template name.
"""
extra_data = getattr(obj, "extra", {}).get("mkdocstrings", {})
return extra_data.get("template", "") or f"{obj.kind.value}.html"
name = obj
if isinstance(obj, (Alias, Object)):
extra_data = getattr(obj, "extra", {}).get("mkdocstrings", {})
if name := extra_data.get("template", ""):
return name
name = obj.kind.value
try:
template = env.get_template(f"{name}.html")
except TemplateNotFound:
return f"{name}.html.jinja"
else:
# TODO: Remove once support for Python 3.8 is dropped.
if sys.version_info < (3, 9):
try:
Path(template.filename).relative_to(Path(__file__).parent) # type: ignore[arg-type]
except ValueError:
our_template = False
else:
our_template = True
else:
our_template = Path(template.filename).is_relative_to(Path(__file__).parent) # type: ignore[arg-type]
if not our_template:
# TODO: Switch to a warning log after some time.
logger.info(
f"DeprecationWarning: Overriding '{name}.html' is deprecated, override '{name}.html.jinja' instead. "
"After some time, this message will be logged as a warning, causing strict builds to fail.",
once=True,
)
return f"{name}.html"


@pass_context
Expand Down
Original file line number Diff line number Diff line change
@@ -1,80 +1,11 @@
{{ log.debug("Rendering " + attribute.path) }}

<div class="doc doc-object doc-attribute">
{% with obj = attribute, html_id = attribute.path %}

{% if root %}
{% set show_full_path = config.show_root_full_path %}
{% set root_members = True %}
{% elif root_members %}
{% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %}
{% set root_members = False %}
{% else %}
{% set show_full_path = config.show_object_full_path %}
{% endif %}

{% set attribute_name = attribute.path if show_full_path else attribute.name %}

{% if not root or config.show_root_heading %}
{% filter heading(
heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
class="doc doc-heading",
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-attribute"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + attribute.name,
) %}

{% block heading scoped %}
{% if config.show_symbol_type_heading %}<code class="doc-symbol doc-symbol-heading doc-symbol-attribute"></code>{% endif %}
{% if config.separate_signature %}
<span class="doc doc-object-name doc-attribute-name">{{ attribute_name }}</span>
{% else %}
{%+ filter highlight(language="python", inline=True) %}
{{ attribute_name }}{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
{% if attribute.value %} = {{ attribute.value }}{% endif %}
{% endfilter %}
{% endif %}
{% endblock heading %}

{% block labels scoped %}
{% with labels = attribute.labels %}
{% include "labels.html" with context %}
{% endwith %}
{% endblock labels %}

{% endfilter %}

{% block signature scoped %}
{% if config.separate_signature %}
{% filter format_attribute(attribute, config.line_length, crossrefs=config.signature_crossrefs) %}
{{ attribute.name }}
{% endfilter %}
{% endif %}
{% endblock signature %}

{% else %}

{% if config.show_root_toc_entry %}
{% filter heading(heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-attribute"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + attribute.name,
hidden=True,
) %}
{% endfilter %}
{% endif %}
{% set heading_level = heading_level - 1 %}
{% endif %}

<div class="doc doc-contents {% if root %}first{% endif %}">
{% block contents scoped %}
{% block docstring scoped %}
{% with docstring_sections = attribute.docstring.parsed %}
{% include "docstring.html" with context %}
{% endwith %}
{% endblock docstring %}
{% endblock contents %}
</div>

{% endwith %}
</div>
{% extends "_base/attribute.html.jinja" %}

{% block logs scoped %}
{{ super() }}
{# TODO: Switch to a warning after some time. #}
{{ log.info(
"DeprecationWarning: Extending '_base/attribute.html' is deprecated, extend '_base/attribute.html.jinja' instead. " ~
"After some time, this message will be logged as a warning, causing strict builds to fail.",
once=True,
) }}
{% endblock logs %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{% block logs scoped %}
{{ log.debug("Rendering " + attribute.path) }}
{% endblock logs %}

<div class="doc doc-object doc-attribute">
{% with obj = attribute, html_id = attribute.path %}

{% if root %}
{% set show_full_path = config.show_root_full_path %}
{% set root_members = True %}
{% elif root_members %}
{% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %}
{% set root_members = False %}
{% else %}
{% set show_full_path = config.show_object_full_path %}
{% endif %}

{% set attribute_name = attribute.path if show_full_path else attribute.name %}

{% if not root or config.show_root_heading %}
{% filter heading(
heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
class="doc doc-heading",
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-attribute"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + attribute.name,
) %}

{% block heading scoped %}
{% if config.show_symbol_type_heading %}<code class="doc-symbol doc-symbol-heading doc-symbol-attribute"></code>{% endif %}
{% if config.separate_signature %}
<span class="doc doc-object-name doc-attribute-name">{{ attribute_name }}</span>
{% else %}
{%+ filter highlight(language="python", inline=True) %}
{{ attribute_name }}{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
{% if attribute.value %} = {{ attribute.value }}{% endif %}
{% endfilter %}
{% endif %}
{% endblock heading %}

{% block labels scoped %}
{% with labels = attribute.labels %}
{% include "labels"|get_template with context %}
{% endwith %}
{% endblock labels %}

{% endfilter %}

{% block signature scoped %}
{% if config.separate_signature %}
{% filter format_attribute(attribute, config.line_length, crossrefs=config.signature_crossrefs) %}
{{ attribute.name }}
{% endfilter %}
{% endif %}
{% endblock signature %}

{% else %}

{% if config.show_root_toc_entry %}
{% filter heading(heading_level,
role="data" if attribute.parent.kind.value == "module" else "attr",
id=html_id,
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-attribute"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + attribute.name,
hidden=True,
) %}
{% endfilter %}
{% endif %}
{% set heading_level = heading_level - 1 %}
{% endif %}

<div class="doc doc-contents {% if root %}first{% endif %}">
{% block contents scoped %}
{% block docstring scoped %}
{% with docstring_sections = attribute.docstring.parsed %}
{% include "docstring"|get_template with context %}
{% endwith %}
{% endblock docstring %}
{% endblock contents %}
</div>

{% endwith %}
</div>
Loading
Loading