diff --git a/.copier-answers.yml b/.copier-answers.yml
index 70fc1c32..2076d959 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
-_commit: 0.15.2
+_commit: 0.15.6
_src_path: gh:pawamoy/copier-pdm
author_email: pawamoy@pm.me
author_fullname: Timothée Mazzucotelli
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 00d61acb..8c0977e0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,10 +31,10 @@ jobs:
python-version: "3.8"
- name: Resolving dependencies
- run: pdm lock -v
+ run: pdm lock -v --no-cross-platform -G ci-quality
- name: Install dependencies
- run: pdm install -G duty -G docs -G quality -G typing -G security
+ run: pdm install -G ci-quality
- name: Check if the documentation builds correctly
run: pdm run duty check-docs
@@ -54,6 +54,7 @@ jobs:
tests:
strategy:
+ max-parallel: 4
matrix:
os:
- ubuntu-latest
@@ -78,10 +79,10 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Resolving dependencies
- run: pdm lock -v
+ run: pdm lock -v --no-cross-platform -G ci-tests
- name: Install dependencies
- run: pdm install --no-editable -G duty -G tests -G docs
+ run: pdm install --no-editable -G ci-tests
- name: Run the test suite
run: pdm run duty test
diff --git a/.gitignore b/.gitignore
index 428b2409..ae47b28b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ pip-wheel-metadata/
site/
pdm.lock
pdm.toml
+.pdm-plugins/
.pdm-python
__pypackages__/
.venv/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9606a25..d4b4606c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,32 @@ 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).
+## [1.0.0](https://github.com/mkdocstrings/python/releases/tag/1.0.0) - 2023-05-11
+
+[Compare with 0.10.1](https://github.com/mkdocstrings/python/compare/0.10.1...1.0.0)
+
+### Breaking changes
+
+- The signature of the [`format_signature` filter](https://mkdocstrings.github.io/python/reference/mkdocstrings_handlers/python/rendering/#mkdocstrings_handlers.python.rendering.do_format_signature) has changed.
+ If you override templates in your project to customize the output,
+ make sure to update the following templates so that they use
+ the new filter signature:
+
+ - `class.html`
+ - `expression.html`
+ - `function.html`
+ - `signature.html`
+
+ You can see how to use the filter in this commit's changes:
+ [f686f4e4](https://github.com/mkdocstrings/python/commit/f686f4e4599cea64686d4ef4863b507dd096a513).
+
+**We take this as an opportunity to go out of beta and bump the version to 1.0.0.
+This will allow users to rely on semantic versioning.**
+
+### Bug Fixes
+
+- Bring compatibility with insiders signature crossrefs feature ([f686f4e](https://github.com/mkdocstrings/python/commit/f686f4e4599cea64686d4ef4863b507dd096a513) by Timothée Mazzucotelli).
+
## [0.10.1](https://github.com/mkdocstrings/python/releases/tag/0.10.1) - 2023-05-07
[Compare with 0.10.0](https://github.com/mkdocstrings/python/compare/0.10.0...0.10.1)
diff --git a/Makefile b/Makefile
index 68fea02f..1b33c56b 100644
--- a/Makefile
+++ b/Makefile
@@ -32,7 +32,7 @@ help:
.PHONY: lock
lock:
- @pdm lock
+ @pdm lock -G:all
.PHONY: setup
setup:
diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html
index cb5234e5..cf8adeb7 100644
--- a/docs/.overrides/main.html
+++ b/docs/.overrides/main.html
@@ -6,9 +6,9 @@
is now available!
{% include ".icons/octicons/heart-fill-16.svg" %}
-
+ —
- — For updates follow @pawamoy on
+ For updates follow @pawamoy on
{% include ".icons/fontawesome/brands/mastodon.svg" %}
diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css
index 87842c0c..559575fb 100644
--- a/docs/css/mkdocstrings.css
+++ b/docs/css/mkdocstrings.css
@@ -4,7 +4,7 @@ div.doc-contents:not(.first) {
border-left: .05rem solid var(--md-typeset-table-color);
}
-/* Mark external links as such */
+/* Mark external links as such. */
a.external::after,
a.autorefs-external::after {
/* https://primer.style/octicons/arrow-up-right-24 */
@@ -12,13 +12,14 @@ a.autorefs-external::after {
content: ' ';
display: inline-block;
+ vertical-align: middle;
position: relative;
- top: 0.1em;
+ bottom: 0.1em;
margin-left: 0.2em;
margin-right: 0.1em;
- height: 1em;
- width: 1em;
+ height: 0.7em;
+ width: 0.7em;
border-radius: 100%;
background-color: var(--md-typeset-a-color);
}
diff --git a/docs/usage/customization.md b/docs/usage/customization.md
index dd2bd56c..99b33bd2 100644
--- a/docs/usage/customization.md
+++ b/docs/usage/customization.md
@@ -70,20 +70,20 @@ a.autorefs-external::after {
content: ' ';
display: inline-block;
+ vertical-align: middle;
position: relative;
- top: 0.1em;
+ bottom: 0.1em;
margin-left: 0.2em;
margin-right: 0.1em;
- height: 1em;
- width: 1em;
+ height: 0.7em;
+ width: 0.7em;
border-radius: 100%;
background-color: var(--md-typeset-a-color);
}
a.autorefs-external:hover::after {
background-color: var(--md-accent-fg-color);
}
-
```
### Recommended style (ReadTheDocs)
diff --git a/duties.py b/duties.py
index 9b93ca81..a49b49cb 100644
--- a/duties.py
+++ b/duties.py
@@ -5,7 +5,7 @@
import os
import sys
from pathlib import Path
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
from duty import duty
from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety
@@ -35,7 +35,29 @@ def pyprefix(title: str) -> str: # noqa: D103
return title
+def merge(d1: Any, d2: Any) -> Any: # noqa: D103
+ basic_types = (int, float, str, bool, complex)
+ if isinstance(d1, dict) and isinstance(d2, dict):
+ for key, value in d2.items():
+ if key in d1:
+ if isinstance(d1[key], basic_types):
+ d1[key] = value
+ else:
+ d1[key] = merge(d1[key], value)
+ else:
+ d1[key] = value
+ return d1
+ if isinstance(d1, list) and isinstance(d2, list):
+ return d1 + d2
+ return d2
+
+
def mkdocs_config() -> str: # noqa: D103
+ from mkdocs import utils
+
+ # patch YAML loader to merge arrays
+ utils.merge = merge
+
if "+insiders" in pkgversion("mkdocs-material"):
return "mkdocs.insiders.yml"
return "mkdocs.yml"
@@ -83,6 +105,7 @@ def check_quality(ctx: Context) -> None:
Parameters:
ctx: The context instance (passed automatically).
"""
+ os.environ["MYPYPATH"] = "src"
ctx.run(
ruff.check(*PY_SRC_LIST, config="config/ruff.toml"),
title=pyprefix("Checking code quality"),
diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml
index 93e3a93b..9afba9aa 100644
--- a/mkdocs.insiders.yml
+++ b/mkdocs.insiders.yml
@@ -1,4 +1,4 @@
INHERIT: mkdocs.yml
plugins:
- typeset: {}
+- typeset
diff --git a/mkdocs.yml b/mkdocs.yml
index bdb863e9..50ba6925 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -119,16 +119,16 @@ markdown_extensions:
permalink: "¤"
plugins:
- autorefs: {}
- search: {}
- markdown-exec: {}
- gen-files:
+- autorefs
+- search
+- markdown-exec
+- gen-files:
scripts:
- scripts/gen_ref_nav.py
- literate-nav:
+- literate-nav:
nav_file: SUMMARY.txt
- coverage: {}
- mkdocstrings:
+- coverage
+- mkdocstrings:
handlers:
python:
paths: [src]
@@ -141,10 +141,10 @@ plugins:
merge_init_into_class: true
docstring_options:
ignore_init_summary: true
- git-committers:
+- git-committers:
enabled: !ENV [DEPLOY, false]
repository: mkdocstrings/python
- minify:
+- minify:
minify_html: !ENV [DEPLOY, false]
extra:
diff --git a/pyproject.toml b/pyproject.toml
index 22d865ae..84f0f2f1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -45,6 +45,9 @@ Funding = "https://github.com/sponsors/mkdocstrings"
[tool.pdm]
version = {source = "scm"}
+plugins = [
+ "pdm-multirun",
+]
[tool.pdm.build]
package-dir = "src"
@@ -53,6 +56,8 @@ editable-backend = "editables"
[tool.pdm.dev-dependencies]
duty = ["duty>=0.10"]
+ci-quality = ["mkdocstrings-python[duty,docs,quality,typing,security]"]
+ci-tests = ["mkdocstrings-python[duty,docs,tests]"]
docs = [
"black>=23.1",
"markdown-callouts>=0.2",
@@ -86,4 +91,6 @@ typing = [
"types-pyyaml>=6.0",
"types-toml>=0.10",
]
-security = ["safety>=2"]
+security = [
+ "safety>=2",
+]
diff --git a/scripts/setup.sh b/scripts/setup.sh
index 559ae8b1..a0f87210 100755
--- a/scripts/setup.sh
+++ b/scripts/setup.sh
@@ -10,7 +10,7 @@ if ! command -v pdm &>/dev/null; then
pipx install pdm
fi
if ! pdm self list 2>/dev/null | grep -q pdm-multirun; then
- pipx inject pdm pdm-multirun
+ pdm install --plugins
fi
if [ -n "${PYTHON_VERSIONS}" ]; then
diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py
index 775d7de0..9bfb02f4 100644
--- a/src/mkdocstrings_handlers/python/handler.py
+++ b/src/mkdocstrings_handlers/python/handler.py
@@ -84,6 +84,7 @@ class PythonHandler(BaseHandler):
"show_if_no_docstring": False,
"show_signature": True,
"show_signature_annotations": False,
+ "signature_crossrefs": False,
"separate_signature": False,
"line_length": 60,
"merge_init_into_class": False,
@@ -169,6 +170,7 @@ class PythonHandler(BaseHandler):
line_length (int): Maximum line length when formatting code/signatures. Default: `60`.
show_signature (bool): Show methods and functions signatures. Default: `True`.
show_signature_annotations (bool): Show the type annotations in methods and functions signatures. Default: `False`.
+ signature_crossrefs (bool): Whether to render cross-references for type annotations in signatures. Default: `False`.
separate_signature (bool): Whether to put the whole signature in a code block below the heading.
If Black is installed, the signature is also formatted using it. Default: `False`.
"""
@@ -314,6 +316,9 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa
(re.compile(filtr.lstrip("!")), filtr.startswith("!")) for filtr in final_config["filters"]
]
+ # TODO: goal reached: remove once `signature_crossrefs` feature becomes public
+ final_config["signature_crossrefs"] = False
+
return template.render(
**{"config": final_config, data.kind.value: data, "heading_level": heading_level, "root": True},
)
@@ -329,6 +334,7 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore
self.env.filters["format_code"] = rendering.do_format_code
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
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 7edfd7ac..d1f0eb75 100644
--- a/src/mkdocstrings_handlers/python/rendering.py
+++ b/src/mkdocstrings_handlers/python/rendering.py
@@ -8,11 +8,13 @@
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence
+from jinja2 import pass_context
from markupsafe import Markup
from mkdocstrings.loggers import get_logger
if TYPE_CHECKING:
- from griffe.dataclasses import Alias, Object
+ from griffe.dataclasses import Alias, Function, Object
+ from jinja2.runtime import Context
from mkdocstrings.handlers.base import CollectorItem
logger = get_logger(__name__)
@@ -60,26 +62,17 @@ def do_format_code(code: str, line_length: int) -> str:
return formatter(code, line_length)
-def do_format_signature(signature: str, line_length: int) -> str:
- """Format a signature using Black.
-
- Parameters:
- signature: The signature to format.
- line_length: The line length to give to Black.
-
- Returns:
- The same code, formatted.
- """
- code = signature.strip()
- if len(code) < line_length:
- return code
+def _format_signature(name: Markup, signature: str, line_length: int) -> str:
+ name = str(name).strip() # type: ignore[assignment]
+ signature = signature.strip()
+ if len(name + signature) < line_length:
+ return name + signature
# Black cannot format names with dots, so we replace
# the whole name with a string of equal length
- name_length = code.index("(")
- name = code[:name_length]
+ name_length = len(name)
formatter = _get_black_formatter()
- formatable = f"def {'x' * name_length}{code[name_length:]}: pass"
+ formatable = f"def {'x' * name_length}{signature}: pass"
formatted = formatter(formatable, line_length)
# We put back the original name
@@ -87,6 +80,33 @@ def do_format_signature(signature: str, line_length: int) -> str:
return name + formatted[4:-5].strip()[name_length:-1]
+@pass_context
+def do_format_signature(
+ context: Context,
+ callable_path: Markup,
+ function: Function,
+ line_length: int,
+ *,
+ crossrefs: bool = False, # noqa: ARG001
+) -> str:
+ """Format a signature using Black.
+
+ Parameters:
+ callable_path: The path of the callable we render the signature of.
+ line_length: The line length to give to Black.
+ crossrefs: Whether to cross-reference types in the signature.
+
+ Returns:
+ The same code, formatted.
+ """
+ env = context.environment
+ template = env.get_template("signature.html")
+ signature = template.render(context.parent, function=function)
+ signature = _format_signature(callable_path, signature, line_length)
+ signature = str(env.filters["highlight"](signature, language="python", inline=False))
+ return signature
+
+
def do_order_members(
members: Sequence[Object | Alias],
order: Order,
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/class.html b/src/mkdocstrings_handlers/python/templates/material/_base/class.html
index ff102c88..3f6248fb 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/class.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/class.html
@@ -43,11 +43,8 @@
{% if config.separate_signature and config.merge_init_into_class %}
{% if "__init__" in class.members %}
{% with function = class.members["__init__"] %}
- {% filter highlight(language="python", inline=False) %}
- {% filter format_signature(config.line_length) %}
- {% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %}
- {% include "signature.html" with context %}
- {% endfilter %}
+ {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
+ {% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %}
{% endfilter %}
{% endwith %}
{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html
index 3347e272..9bcfc867 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html
@@ -7,6 +7,8 @@
{{ original_expression }}
{%- else -%}
{%- with annotation = original_expression|attr(config.annotations_path) -%}
- {{ annotation }}
+ {%- filter stash_crossref(length=annotation|length) -%}
+ {{ annotation }}
+ {%- endfilter -%}
{%- endwith -%}
{%- endif -%}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/function.html b/src/mkdocstrings_handlers/python/templates/material/_base/function.html
index b9b1696c..70c26892 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/function.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/function.html
@@ -37,11 +37,8 @@
{% endfilter %}
{% if config.separate_signature %}
- {% filter highlight(language="python", inline=False) %}
- {% filter format_signature(config.line_length) %}
- {% if show_full_path %}{{ function.path }}{% else %}{{ function.name }}{% endif %}
- {% include "signature.html" with context %}
- {% endfilter %}
+ {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
+ {% if show_full_path %}{{ function.path }}{% else %}{{ function.name }}{% endif %}
{% endfilter %}
{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/signature.html b/src/mkdocstrings_handlers/python/templates/material/_base/signature.html
index bc24ea35..ea642f51 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/signature.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/signature.html
@@ -2,7 +2,14 @@
{{ log.debug("Rendering signature") }}
{%- with -%}
- {%- set ns = namespace(has_pos_only=False, render_pos_only_separator=True, render_kw_only_separator=True, equal="=") -%}
+ {%- set ns = namespace(
+ has_pos_only=False,
+ render_pos_only_separator=True,
+ render_kw_only_separator=True,
+ annotation="",
+ equal="=",
+ )
+ -%}
{%- if config.show_signature_annotations -%}
{%- set ns.equal = " = " -%}
@@ -24,7 +31,13 @@
{%- endif -%}
{%- if config.show_signature_annotations and parameter.annotation is not none -%}
- {%- set annotation = ": " + parameter.annotation|safe -%}
+ {%- if config.separate_signature and config.signature_crossrefs -%}
+ {%- with expression = parameter.annotation -%}
+ {%- set ns.annotation -%}: {% include "expression.html" with context %}{%- endset -%}
+ {%- endwith -%}
+ {%- else -%}
+ {%- set ns.annotation = ": " + parameter.annotation|safe -%}
+ {%- endif -%}
{%- endif -%}
{%- if parameter.default is not none and parameter.kind.value != "variadic positional" and parameter.kind.value != "variadic keyword" -%}
@@ -36,13 +49,18 @@
{%- endif -%}
{% if parameter.kind.value == "variadic positional" %}*{% elif parameter.kind.value == "variadic keyword" %}**{% endif -%}
- {{ parameter.name }}{{ annotation }}{{ default }}
+ {{ parameter.name }}{{ ns.annotation }}{{ default }}
{%- if not loop.last %}, {% endif -%}
{%- endif -%}
{%- endfor -%}
)
- {%- if config.show_signature_annotations and function.annotation %} -> {{ function.annotation|safe }}{%- endif -%}
+ {%- if config.show_signature_annotations and function.annotation %} -> {% if config.separate_signature and config.signature_crossrefs -%}
+ {%- with expression = function.annotation %}{% include "expression.html" with context %}{%- endwith -%}
+ {%- else -%}
+ {{ function.annotation|safe }}
+ {%- endif -%}
+ {%- endif -%}
{%- endwith -%}
{%- endif -%}
\ No newline at end of file
diff --git a/tests/test_rendering.py b/tests/test_rendering.py
index fc970c57..c504d4d0 100644
--- a/tests/test_rendering.py
+++ b/tests/test_rendering.py
@@ -4,12 +4,15 @@
import re
from dataclasses import dataclass
-from typing import Any
+from typing import TYPE_CHECKING, Any
import pytest
from mkdocstrings_handlers.python import rendering
+if TYPE_CHECKING:
+ from markupsafe import Markup
+
@pytest.mark.parametrize(
"code",
@@ -29,17 +32,17 @@ def test_format_code(code: str) -> None:
@pytest.mark.parametrize(
- "signature",
- ["Class.method(param: str = 'hello') -> 'OtherClass'"],
+ ("name", "signature"),
+ [("Class.method", "(param: str = 'hello') -> 'OtherClass'")],
)
-def test_format_signature(signature: str) -> None:
+def test_format_signature(name: Markup, signature: str) -> None:
"""Assert signatures can be Black-formatted.
Parameters:
signature: Signature to format.
"""
for length in (5, 100):
- assert rendering.do_format_signature(signature, length)
+ assert rendering._format_signature(name, signature, length)
@dataclass