Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f3917e9

Browse filesBrowse files
committed
fix: Prevent infinite recursion by detecting parent-member cycles
Issue-griffe-368: mkdocstrings/griffe#368
1 parent bfb5b30 commit f3917e9
Copy full SHA for f3917e9

File tree

3 files changed

+58
-4
lines changed
Filter options

3 files changed

+58
-4
lines changed

‎src/mkdocstrings_handlers/python/_internal/rendering.py

Copy file name to clipboardExpand all lines: src/mkdocstrings_handlers/python/_internal/rendering.py
+32-3Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
from griffe import (
2020
Alias,
21+
AliasResolutionError,
22+
CyclicAliasError,
2123
DocstringAttribute,
2224
DocstringClass,
2325
DocstringFunction,
@@ -411,6 +413,29 @@ def _keep_object(name: str, filters: Sequence[tuple[Pattern, bool]]) -> bool:
411413
return keep
412414

413415

416+
def _parents(obj: Alias) -> set[str]:
417+
parent: Object | Alias = obj.parent # type: ignore[assignment]
418+
parents = {obj.path, parent.path}
419+
if parent.is_alias:
420+
parents.add(parent.final_target.path) # type: ignore[union-attr]
421+
while parent.parent:
422+
parent = parent.parent
423+
parents.add(parent.path)
424+
if parent.is_alias:
425+
parents.add(parent.final_target.path) # type: ignore[union-attr]
426+
return parents
427+
428+
429+
def _remove_cycles(objects: list[Object | Alias]) -> Iterator[Object | Alias]:
430+
suppress_errors = suppress(AliasResolutionError, CyclicAliasError)
431+
for obj in objects:
432+
if obj.is_alias:
433+
with suppress_errors:
434+
if obj.final_target.path in _parents(obj): # type: ignore[arg-type,union-attr]
435+
continue
436+
yield obj
437+
438+
414439
def do_filter_objects(
415440
objects_dictionary: dict[str, Object | Alias],
416441
*,
@@ -470,10 +495,14 @@ def do_filter_objects(
470495
objects = [
471496
obj for obj in objects if _keep_object(obj.name, filters) or (inherited_members_specified and obj.inherited)
472497
]
473-
if keep_no_docstrings:
474-
return objects
498+
if not keep_no_docstrings:
499+
objects = [obj for obj in objects if obj.has_docstrings or (inherited_members_specified and obj.inherited)]
500+
501+
# Prevent infinite recursion.
502+
if objects:
503+
objects = list(_remove_cycles(objects))
475504

476-
return [obj for obj in objects if obj.has_docstrings or (inherited_members_specified and obj.inherited)]
505+
return objects
477506

478507

479508
@lru_cache(maxsize=1)

‎tests/test_handler.py

Copy file name to clipboardExpand all lines: tests/test_handler.py
+24-1Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@
1010
from typing import TYPE_CHECKING
1111

1212
import pytest
13-
from griffe import Docstring, DocstringSectionExamples, DocstringSectionKind, Module, temporary_visited_module
13+
from griffe import (
14+
Docstring,
15+
DocstringSectionExamples,
16+
DocstringSectionKind,
17+
Module,
18+
temporary_inspected_module,
19+
temporary_visited_module,
20+
)
1421
from mkdocstrings import CollectionError
1522

1623
from mkdocstrings_handlers.python import PythonConfig, PythonHandler, PythonOptions
@@ -275,3 +282,19 @@ def test_deduplicate_summary_sections(handler: PythonHandler, section: str, code
275282
),
276283
)
277284
assert html.count(f"{section}:") == 1
285+
286+
287+
def test_inheriting_self_from_parent_class(handler: PythonHandler) -> None:
288+
"""Inspect self only once when inheriting it from parent class."""
289+
with temporary_inspected_module(
290+
"""
291+
class A: ...
292+
class B(A): ...
293+
A.B = B
294+
""",
295+
) as module:
296+
# Assert no recusrion error.
297+
handler.render(
298+
module,
299+
handler.get_options({"inherited_members": True}),
300+
)

‎tests/test_rendering.py

Copy file name to clipboardExpand all lines: tests/test_rendering.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ def test_format_signature(name: Markup, signature: str) -> None:
5858
class _FakeObject:
5959
name: str
6060
inherited: bool = False
61+
parent: None = None
62+
is_alias: bool = False
6163

6264

6365
@pytest.mark.parametrize(

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.