Skip to content

Commit f686f4e

Browse files
committed
fix: Bring compatibility with insiders signature crossrefs feature
Breaking change: the signature of the `format_signature` filter has changed. You must update the following templates if you override them: class.html, expression.html, function.html and signature.html.
1 parent 5204726 commit f686f4e

File tree

7 files changed

+80
-37
lines changed

7 files changed

+80
-37
lines changed

src/mkdocstrings_handlers/python/handler.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class PythonHandler(BaseHandler):
8484
"show_if_no_docstring": False,
8585
"show_signature": True,
8686
"show_signature_annotations": False,
87+
"signature_crossrefs": False,
8788
"separate_signature": False,
8889
"line_length": 60,
8990
"merge_init_into_class": False,
@@ -169,6 +170,7 @@ class PythonHandler(BaseHandler):
169170
line_length (int): Maximum line length when formatting code/signatures. Default: `60`.
170171
show_signature (bool): Show methods and functions signatures. Default: `True`.
171172
show_signature_annotations (bool): Show the type annotations in methods and functions signatures. Default: `False`.
173+
signature_crossrefs (bool): Whether to render cross-references for type annotations in signatures. Default: `False`.
172174
separate_signature (bool): Whether to put the whole signature in a code block below the heading.
173175
If Black is installed, the signature is also formatted using it. Default: `False`.
174176
"""
@@ -314,6 +316,9 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa
314316
(re.compile(filtr.lstrip("!")), filtr.startswith("!")) for filtr in final_config["filters"]
315317
]
316318

319+
# TODO: goal reached: remove once `signature_crossrefs` feature becomes public
320+
final_config["signature_crossrefs"] = False
321+
317322
return template.render(
318323
**{"config": final_config, data.kind.value: data, "heading_level": heading_level, "root": True},
319324
)
@@ -329,6 +334,7 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore
329334
self.env.filters["format_code"] = rendering.do_format_code
330335
self.env.filters["format_signature"] = rendering.do_format_signature
331336
self.env.filters["filter_objects"] = rendering.do_filter_objects
337+
self.env.filters["stash_crossref"] = lambda ref, length: ref
332338

333339
def get_anchors(self, data: CollectorItem) -> set[str]: # noqa: D102 (ignore missing docstring)
334340
try:

src/mkdocstrings_handlers/python/rendering.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
from functools import lru_cache
99
from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence
1010

11+
from jinja2 import pass_context
1112
from markupsafe import Markup
1213
from mkdocstrings.loggers import get_logger
1314

1415
if TYPE_CHECKING:
15-
from griffe.dataclasses import Alias, Object
16+
from griffe.dataclasses import Alias, Function, Object
17+
from jinja2.runtime import Context
1618
from mkdocstrings.handlers.base import CollectorItem
1719

1820
logger = get_logger(__name__)
@@ -60,33 +62,51 @@ def do_format_code(code: str, line_length: int) -> str:
6062
return formatter(code, line_length)
6163

6264

63-
def do_format_signature(signature: str, line_length: int) -> str:
64-
"""Format a signature using Black.
65-
66-
Parameters:
67-
signature: The signature to format.
68-
line_length: The line length to give to Black.
69-
70-
Returns:
71-
The same code, formatted.
72-
"""
73-
code = signature.strip()
74-
if len(code) < line_length:
75-
return code
65+
def _format_signature(name: Markup, signature: str, line_length: int) -> str:
66+
name = str(name).strip() # type: ignore[assignment]
67+
signature = signature.strip()
68+
if len(name + signature) < line_length:
69+
return name + signature
7670

7771
# Black cannot format names with dots, so we replace
7872
# the whole name with a string of equal length
79-
name_length = code.index("(")
80-
name = code[:name_length]
73+
name_length = len(name)
8174
formatter = _get_black_formatter()
82-
formatable = f"def {'x' * name_length}{code[name_length:]}: pass"
75+
formatable = f"def {'x' * name_length}{signature}: pass"
8376
formatted = formatter(formatable, line_length)
8477

8578
# We put back the original name
8679
# and remove starting `def ` and trailing `: pass`
8780
return name + formatted[4:-5].strip()[name_length:-1]
8881

8982

83+
@pass_context
84+
def do_format_signature(
85+
context: Context,
86+
callable_path: Markup,
87+
function: Function,
88+
line_length: int,
89+
*,
90+
crossrefs: bool = False, # noqa: ARG001
91+
) -> str:
92+
"""Format a signature using Black.
93+
94+
Parameters:
95+
callable_path: The path of the callable we render the signature of.
96+
line_length: The line length to give to Black.
97+
crossrefs: Whether to cross-reference types in the signature.
98+
99+
Returns:
100+
The same code, formatted.
101+
"""
102+
env = context.environment
103+
template = env.get_template("signature.html")
104+
signature = template.render(context.parent, function=function)
105+
signature = _format_signature(callable_path, signature, line_length)
106+
signature = str(env.filters["highlight"](signature, language="python", inline=False))
107+
return signature
108+
109+
90110
def do_order_members(
91111
members: Sequence[Object | Alias],
92112
order: Order,

src/mkdocstrings_handlers/python/templates/material/_base/class.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,8 @@
4343
{% if config.separate_signature and config.merge_init_into_class %}
4444
{% if "__init__" in class.members %}
4545
{% with function = class.members["__init__"] %}
46-
{% filter highlight(language="python", inline=False) %}
47-
{% filter format_signature(config.line_length) %}
48-
{% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %}
49-
{% include "signature.html" with context %}
50-
{% endfilter %}
46+
{% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
47+
{% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %}
5148
{% endfilter %}
5249
{% endwith %}
5350
{% endif %}

src/mkdocstrings_handlers/python/templates/material/_base/expression.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
{{ original_expression }}
88
{%- else -%}
99
{%- with annotation = original_expression|attr(config.annotations_path) -%}
10-
<span data-autorefs-optional{% if annotation != original_expression.full %}-hover{% endif %}="{{ original_expression.full }}">{{ annotation }}</span>
10+
{%- filter stash_crossref(length=annotation|length) -%}
11+
<span data-autorefs-optional{% if annotation != original_expression.full %}-hover{% endif %}="{{ original_expression.full }}">{{ annotation }}</span>
12+
{%- endfilter -%}
1113
{%- endwith -%}
1214
{%- endif -%}

src/mkdocstrings_handlers/python/templates/material/_base/function.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,8 @@
3737
{% endfilter %}
3838

3939
{% if config.separate_signature %}
40-
{% filter highlight(language="python", inline=False) %}
41-
{% filter format_signature(config.line_length) %}
42-
{% if show_full_path %}{{ function.path }}{% else %}{{ function.name }}{% endif %}
43-
{% include "signature.html" with context %}
44-
{% endfilter %}
40+
{% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
41+
{% if show_full_path %}{{ function.path }}{% else %}{{ function.name }}{% endif %}
4542
{% endfilter %}
4643
{% endif %}
4744

src/mkdocstrings_handlers/python/templates/material/_base/signature.html

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
{{ log.debug("Rendering signature") }}
33
{%- with -%}
44

5-
{%- set ns = namespace(has_pos_only=False, render_pos_only_separator=True, render_kw_only_separator=True, equal="=") -%}
5+
{%- set ns = namespace(
6+
has_pos_only=False,
7+
render_pos_only_separator=True,
8+
render_kw_only_separator=True,
9+
annotation="",
10+
equal="=",
11+
)
12+
-%}
613

714
{%- if config.show_signature_annotations -%}
815
{%- set ns.equal = " = " -%}
@@ -24,7 +31,13 @@
2431
{%- endif -%}
2532

2633
{%- if config.show_signature_annotations and parameter.annotation is not none -%}
27-
{%- set annotation = ": " + parameter.annotation|safe -%}
34+
{%- if config.separate_signature and config.signature_crossrefs -%}
35+
{%- with expression = parameter.annotation -%}
36+
{%- set ns.annotation -%}: {% include "expression.html" with context %}{%- endset -%}
37+
{%- endwith -%}
38+
{%- else -%}
39+
{%- set ns.annotation = ": " + parameter.annotation|safe -%}
40+
{%- endif -%}
2841
{%- endif -%}
2942

3043
{%- if parameter.default is not none and parameter.kind.value != "variadic positional" and parameter.kind.value != "variadic keyword" -%}
@@ -36,13 +49,18 @@
3649
{%- endif -%}
3750

3851
{% if parameter.kind.value == "variadic positional" %}*{% elif parameter.kind.value == "variadic keyword" %}**{% endif -%}
39-
{{ parameter.name }}{{ annotation }}{{ default }}
52+
{{ parameter.name }}{{ ns.annotation }}{{ default }}
4053
{%- if not loop.last %}, {% endif -%}
4154

4255
{%- endif -%}
4356
{%- endfor -%}
4457
)
45-
{%- if config.show_signature_annotations and function.annotation %} -> {{ function.annotation|safe }}{%- endif -%}
58+
{%- if config.show_signature_annotations and function.annotation %} -> {% if config.separate_signature and config.signature_crossrefs -%}
59+
{%- with expression = function.annotation %}{% include "expression.html" with context %}{%- endwith -%}
60+
{%- else -%}
61+
{{ function.annotation|safe }}
62+
{%- endif -%}
63+
{%- endif -%}
4664

4765
{%- endwith -%}
4866
{%- endif -%}

tests/test_rendering.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55
import re
66
from dataclasses import dataclass
7-
from typing import Any
7+
from typing import TYPE_CHECKING, Any
88

99
import pytest
1010

1111
from mkdocstrings_handlers.python import rendering
1212

13+
if TYPE_CHECKING:
14+
from markupsafe import Markup
15+
1316

1417
@pytest.mark.parametrize(
1518
"code",
@@ -29,17 +32,17 @@ def test_format_code(code: str) -> None:
2932

3033

3134
@pytest.mark.parametrize(
32-
"signature",
33-
["Class.method(param: str = 'hello') -> 'OtherClass'"],
35+
("name", "signature"),
36+
[("Class.method", "(param: str = 'hello') -> 'OtherClass'")],
3437
)
35-
def test_format_signature(signature: str) -> None:
38+
def test_format_signature(name: Markup, signature: str) -> None:
3639
"""Assert signatures can be Black-formatted.
3740
3841
Parameters:
3942
signature: Signature to format.
4043
"""
4144
for length in (5, 100):
42-
assert rendering.do_format_signature(signature, length)
45+
assert rendering._format_signature(name, signature, length)
4346

4447

4548
@dataclass

0 commit comments

Comments
 (0)