Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
*Note that versions roughly correspond to the version of mkdocstrings-python that they
are compatible with.*

## 1.16.4

* Fix handling of aliases (see bug #47)

## 1.16.3

* Added `check_crossrefs_exclude` config option
Expand Down
4 changes: 2 additions & 2 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ dynamic = ["version"]
requires-python = ">=3.9"
dependencies = [
"mkdocstrings-python >=1.16.6,<2.0",
"griffe >=1.0"
"griffe >=1.0",
]

[project.urls]
Expand All @@ -47,7 +47,7 @@ dev = [
"mike >=1.1",
"mkdocs >=1.5.3,<2.0",
"mkdocs-material >=9.5.4",
"linkchecker >=10.4"
"linkchecker >=10.4",
]

[tool.pixi.workspace]
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings_handlers/python_xref/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.16.3
1.16.4
17 changes: 14 additions & 3 deletions src/mkdocstrings_handlers/python_xref/crossref.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import sys
from typing import Any, Callable, List, Optional, cast

from griffe import Docstring, Object
from griffe import Alias, Docstring, GriffeError, Object
from mkdocstrings import get_logger

__all__ = [
Expand Down Expand Up @@ -318,21 +318,32 @@ def _error(self, msg: str, just_warn: bool = False) -> None:
self._ok = just_warn


def substitute_relative_crossrefs(obj: Object, checkref: Optional[Callable[[str], bool]] = None) -> None:
def substitute_relative_crossrefs(
obj: Alias|Object,
checkref: Optional[Callable[[str], bool]] = None,
) -> None:
"""Recursively expand relative cross-references in all docstrings in tree.

Arguments:
obj: a Griffe [Object][griffe.] whose docstrings should be modified
checkref: optional function to check whether computed cross-reference is valid.
Should return True if valid, False if not valid.
"""
if isinstance(obj, Alias):
try:
obj = obj.target
except GriffeError:
# If alias could not be resolved, it probably refers
# to an external package, not be documented.
return

doc = obj.docstring

if doc is not None:
doc.value = _RE_CROSSREF.sub(_RelativeCrossrefProcessor(doc, checkref=checkref), doc.value)

for member in obj.members.values():
if isinstance(member, Object): # pragma: no branch
if isinstance(member, (Alias,Object)): # pragma: no branch
substitute_relative_crossrefs(member, checkref=checkref)

def doc_value_offset_to_location(doc: Docstring, offset: int) -> tuple[int,int]:
Expand Down
5 changes: 5 additions & 0 deletions tests/project/src/myproj/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
class Bar(Foo):
"""See [bar][.] method."""

attribute: str = "attribute"
"""
See [`foo`][(c).]
"""

def bar(self) -> None:
"""This is in the [Bar][(c)] class.
Also see the [foo][^.] method and the [func][(m).] function.
Expand Down
7 changes: 7 additions & 0 deletions tests/project/src/myproj/pkg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
A module
"""

from .dataclass import Dataclass

__all__ = [
"Dataclass",
"func",
]

def func() -> None:
"""
A function
Expand Down
25 changes: 25 additions & 0 deletions tests/project/src/myproj/pkg/dataclass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Dataclass example
"""

from dataclasses import dataclass, field

@dataclass
class Dataclass:
"""
Test dataclasses

See [content][(c).] for an example attribute.

See [method][(c).]
"""
content: str = "hi"
"""some content"""

duration: float = field(default_factory=lambda: 0.0)
"""
example: [`content`][(c).]
"""

def method(self) -> None:
"""Example method."""
16 changes: 16 additions & 0 deletions tests/test_crossref.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from textwrap import dedent
from typing import Callable, Optional

import griffe
import pytest
from griffe import Class, Docstring, Function, Module, Object, LinesCollection

Expand Down Expand Up @@ -264,3 +265,18 @@ def test_doc_value_offset_to_location() -> None:

assert doc_value_offset_to_location(doc3, 0) == (2, 5)
assert doc_value_offset_to_location(doc3, 6) == (3, 3)

def test_griffe() -> None:
"""
Test substitution on griffe rep of local project
Returns:

"""
this_dir = Path(__file__).parent
test_src_dir = this_dir / "project" / "src"
myproj = griffe.load(
"myproj",
search_paths = [ test_src_dir ],
)
substitute_relative_crossrefs(myproj)
# TODO - grovel output
15 changes: 14 additions & 1 deletion tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def check_autorefs(autorefs: List[Any], cases: Dict[Tuple[str,str],str] ) -> Non
Arguments:
autorefs: list of autoref tags parsed from HTML
cases: mapping from (<location>,<title>) to generated reference tag
where <location? is the qualified name of the object whose doc string
where <location> is the qualified name of the object whose doc string
contains the cross-reference, and <title> is the text in the cross-reference.
"""
cases = cases.copy()
Expand Down Expand Up @@ -123,4 +123,17 @@ def test_integration(tmpdir: PathLike) -> None:
}
)

pkg_html = site_dir.joinpath('pkg', 'index.html').read_text()
pkg_bs = bs4.BeautifulSoup(pkg_html, 'html.parser')

autorefs = pkg_bs.find_all('a', attrs={'class':'autorefs'})
assert len(autorefs) >= 3

check_autorefs(
autorefs,
{
('myproj.pkg.Dataclass', 'content') : '#myproj.pkg.Dataclass.content',
('myproj.pkg.Dataclass', 'method') : '#myproj.pkg.Dataclass.method',
('myproj.pkg.Dataclass.duration', 'content') : '#myproj.pkg.Dataclass.content',
}
)
Loading