diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66bb2047..1cd4ce23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,22 @@ 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.16.3](https://github.com/mkdocstrings/python/releases/tag/1.16.3) - 2025-03-08
+
+[Compare with 1.16.2](https://github.com/mkdocstrings/python/compare/1.16.2...1.16.3)
+
+### Build
+
+- Depend on mkdocstrings 0.28.3 ([9fa4f16](https://github.com/mkdocstrings/python/commit/9fa4f1636af240bb695661b7172f052cb11e0ec9) by Timothée Mazzucotelli).
+
+### Bug Fixes
+
+- De-duplicate summary sections ([a657d07](https://github.com/mkdocstrings/python/commit/a657d07499eb82d22337c169aa86b1cdd85543fa) by Timothée Mazzucotelli). [Issue-134](https://github.com/mkdocstrings/python/issues/134)
+
+### Code Refactoring
+
+- Import from top-level `mkdocstrings` module ([da2ba13](https://github.com/mkdocstrings/python/commit/da2ba13b1367ce107416d08f382fb9f2384c015c) by Timothée Mazzucotelli).
+
## [1.16.2](https://github.com/mkdocstrings/python/releases/tag/1.16.2) - 2025-02-24
[Compare with 1.16.1](https://github.com/mkdocstrings/python/compare/1.16.1...1.16.2)
diff --git a/pyproject.toml b/pyproject.toml
index e69145f9..cb429367 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,7 +30,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
- "mkdocstrings>=0.28.2",
+ "mkdocstrings>=0.28.3",
"mkdocs-autorefs>=1.4",
"griffe>=0.49",
"typing-extensions>=4.0; python_version < '3.11'",
diff --git a/scripts/make.py b/scripts/make.py
index 3d427296..a3b9e751 100755
--- a/scripts/make.py
+++ b/scripts/make.py
@@ -16,7 +16,7 @@
from collections.abc import Iterator
-PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split()
+PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13").split()
def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None:
diff --git a/src/mkdocstrings_handlers/python/config.py b/src/mkdocstrings_handlers/python/config.py
index 6607d01c..a1ecdcd8 100644
--- a/src/mkdocstrings_handlers/python/config.py
+++ b/src/mkdocstrings_handlers/python/config.py
@@ -7,7 +7,7 @@
from dataclasses import field, fields
from typing import TYPE_CHECKING, Annotated, Any, Literal
-from mkdocstrings.loggers import get_logger
+from mkdocstrings import get_logger
# YORE: EOL 3.10: Replace block with line 2.
if sys.version_info >= (3, 11):
diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py
index 0051be0f..d9db6669 100644
--- a/src/mkdocstrings_handlers/python/handler.py
+++ b/src/mkdocstrings_handlers/python/handler.py
@@ -22,9 +22,7 @@
patch_loggers,
)
from mkdocs.exceptions import PluginError
-from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, HandlerOptions
-from mkdocstrings.inventory import Inventory
-from mkdocstrings.loggers import get_logger
+from mkdocstrings import BaseHandler, CollectionError, CollectorItem, HandlerOptions, Inventory, get_logger
from mkdocstrings_handlers.python import rendering
from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions
diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py
index d284567a..4b8a8ee4 100644
--- a/src/mkdocstrings_handlers/python/rendering.py
+++ b/src/mkdocstrings_handlers/python/rendering.py
@@ -29,7 +29,7 @@
from jinja2 import TemplateNotFound, pass_context, pass_environment
from markupsafe import Markup
from mkdocs_autorefs import AutorefsHookInterface
-from mkdocstrings.loggers import get_logger
+from mkdocstrings import get_logger
if TYPE_CHECKING:
from collections.abc import Iterator, Sequence
@@ -37,7 +37,7 @@
from griffe import Attribute, Class, Function, Module
from jinja2 import Environment, Template
from jinja2.runtime import Context
- from mkdocstrings.handlers.base import CollectorItem
+ from mkdocstrings import CollectorItem
logger = get_logger(__name__)
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja
index 8bc8cb60..3675eda7 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja
@@ -7,15 +7,17 @@
-#}
{% endblock logs %}
-{% with section = obj.attributes
- |filter_objects(
- filters=config.filters,
- members_list=members_list,
- inherited_members=config.inherited_members,
- keep_no_docstrings=config.show_if_no_docstring,
- )
- |order_members(config.members_order, members_list)
- |as_attributes_section(check_public=not members_list)
- %}
- {% if section %}{% include "docstring/attributes"|get_template with context %}{% endif %}
-{% endwith %}
+{% if not obj.docstring.parsed | selectattr("kind.value", "eq", "attributes") %}
+ {% with section = obj.attributes
+ |filter_objects(
+ filters=config.filters,
+ members_list=members_list,
+ inherited_members=config.inherited_members,
+ keep_no_docstrings=config.show_if_no_docstring,
+ )
+ |order_members(config.members_order, members_list)
+ |as_attributes_section(check_public=not members_list)
+ %}
+ {% if section %}{% include "docstring/attributes"|get_template with context %}{% endif %}
+ {% endwith %}
+{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja
index 1b1ef8f3..3db63f3b 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja
@@ -7,15 +7,17 @@
-#}
{% endblock logs %}
-{% with section = obj.classes
- |filter_objects(
- filters=config.filters,
- members_list=members_list,
- inherited_members=config.inherited_members,
- keep_no_docstrings=config.show_if_no_docstring,
- )
- |order_members(config.members_order, members_list)
- |as_classes_section(check_public=not members_list)
- %}
- {% if section %}{% include "docstring/classes"|get_template with context %}{% endif %}
-{% endwith %}
+{% if not obj.docstring.parsed | selectattr("kind.value", "eq", "classes") %}
+ {% with section = obj.classes
+ |filter_objects(
+ filters=config.filters,
+ members_list=members_list,
+ inherited_members=config.inherited_members,
+ keep_no_docstrings=config.show_if_no_docstring,
+ )
+ |order_members(config.members_order, members_list)
+ |as_classes_section(check_public=not members_list)
+ %}
+ {% if section %}{% include "docstring/classes"|get_template with context %}{% endif %}
+ {% endwith %}
+{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja
index f03dfba2..60369401 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja
@@ -7,15 +7,17 @@
-#}
{% endblock logs %}
-{% with section = obj.functions
- |filter_objects(
- filters=config.filters,
- members_list=members_list,
- inherited_members=config.inherited_members,
- keep_no_docstrings=config.show_if_no_docstring,
- )
- |order_members(config.members_order, members_list)
- |as_functions_section(check_public=not members_list)
- %}
- {% if section %}{% include "docstring/functions"|get_template with context %}{% endif %}
-{% endwith %}
+{% if not obj.docstring.parsed | selectattr("kind.value", "eq", "functions") %}
+ {% with section = obj.functions
+ |filter_objects(
+ filters=config.filters,
+ members_list=members_list,
+ inherited_members=config.inherited_members,
+ keep_no_docstrings=config.show_if_no_docstring,
+ )
+ |order_members(config.members_order, members_list)
+ |as_functions_section(check_public=not members_list)
+ %}
+ {% if section %}{% include "docstring/functions"|get_template with context %}{% endif %}
+ {% endwith %}
+{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja
index 606711c5..dd14ee9f 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja
@@ -7,15 +7,17 @@
-#}
{% endblock logs %}
-{% with section = obj.modules
- |filter_objects(
- filters=config.filters,
- members_list=members_list,
- inherited_members=config.inherited_members,
- keep_no_docstrings=config.show_if_no_docstring,
- )
- |order_members("alphabetical", members_list)
- |as_modules_section(check_public=not members_list)
- %}
- {% if section %}{% include "docstring/modules"|get_template with context %}{% endif %}
-{% endwith %}
+{% if not obj.docstring.parsed | selectattr("kind.value", "eq", "modules") %}
+ {% with section = obj.modules
+ |filter_objects(
+ filters=config.filters,
+ members_list=members_list,
+ inherited_members=config.inherited_members,
+ keep_no_docstrings=config.show_if_no_docstring,
+ )
+ |order_members("alphabetical", members_list)
+ |as_modules_section(check_public=not members_list)
+ %}
+ {% if section %}{% include "docstring/modules"|get_template with context %}{% endif %}
+ {% endwith %}
+{% endif %}
diff --git a/tests/conftest.py b/tests/conftest.py
index 1c53cba4..5b2dd33f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,7 +15,7 @@
from markdown.core import Markdown
from mkdocs.config.defaults import MkDocsConfig
- from mkdocstrings.plugin import MkdocstringsPlugin
+ from mkdocstrings import MkdocstringsPlugin
from mkdocstrings_handlers.python.handler import PythonHandler
diff --git a/tests/helpers.py b/tests/helpers.py
index 37c127e4..b0dc6ad7 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -14,7 +14,7 @@
from pathlib import Path
import pytest
- from mkdocstrings.plugin import MkdocstringsPlugin
+ from mkdocstrings import MkdocstringsPlugin
from mkdocstrings_handlers.python.handler import PythonHandler
diff --git a/tests/test_handler.py b/tests/test_handler.py
index 7cf8dc54..4d8b4f3d 100644
--- a/tests/test_handler.py
+++ b/tests/test_handler.py
@@ -10,13 +10,13 @@
from typing import TYPE_CHECKING
import pytest
-from griffe import DocstringSectionExamples, DocstringSectionKind, temporary_visited_module
+from griffe import Docstring, DocstringSectionExamples, DocstringSectionKind, Module, temporary_visited_module
from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions
from mkdocstrings_handlers.python.handler import CollectionError, PythonHandler
if TYPE_CHECKING:
- from mkdocstrings.plugin import MkdocstringsPlugin
+ from mkdocstrings import MkdocstringsPlugin
def test_collect_missing_module(handler: PythonHandler) -> None:
@@ -179,3 +179,102 @@ def test_give_precedence_to_user_paths() -> None:
mdx_config={},
)
assert handler._paths[0] == last_sys_path
+
+
+@pytest.mark.parametrize(
+ ("section", "code"),
+ [
+ (
+ "Attributes",
+ """
+ class A:
+ '''Summary.
+
+ Attributes:
+ x: X.
+ y: Y.
+ '''
+ x: int = 0
+ '''X.'''
+ y: int = 0
+ '''Y.'''
+ """,
+ ),
+ (
+ "Methods",
+ """
+ class A:
+ '''Summary.
+
+ Methods:
+ x: X.
+ y: Y.
+ '''
+ def x(self): ...
+ '''X.'''
+ def y(self): ...
+ '''Y.'''
+ """,
+ ),
+ (
+ "Functions",
+ """
+ '''Summary.
+
+ Functions:
+ x: X.
+ y: Y.
+ '''
+ def x(): ...
+ '''X.'''
+ def y(): ...
+ '''Y.'''
+ """,
+ ),
+ (
+ "Classes",
+ """
+ '''Summary.
+
+ Classes:
+ A: A.
+ B: B.
+ '''
+ class A: ...
+ '''A.'''
+ class B: ...
+ '''B.'''
+ """,
+ ),
+ (
+ "Modules",
+ """
+ '''Summary.
+
+ Modules:
+ a: A.
+ b: B.
+ '''
+ """,
+ ),
+ ],
+)
+def test_deduplicate_summary_sections(handler: PythonHandler, section: str, code: str) -> None:
+ """Assert summary sections are deduplicated."""
+ summary_section = section.lower()
+ summary_section = "functions" if summary_section == "methods" else summary_section
+ with temporary_visited_module(code, docstring_parser="google") as module: # type: ignore[arg-type]
+ if summary_section == "modules":
+ module.set_member("a", Module("A", docstring=Docstring("A.")))
+ module.set_member("b", Module("B", docstring=Docstring("B.")))
+ html = handler.render(
+ module,
+ handler.get_options(
+ {
+ "summary": {summary_section: True},
+ "show_source": False,
+ "show_submodules": True,
+ },
+ ),
+ )
+ assert html.count(f"{section}:") == 1
diff --git a/tests/test_themes.py b/tests/test_themes.py
index a7b44795..ca7125d3 100644
--- a/tests/test_themes.py
+++ b/tests/test_themes.py
@@ -7,7 +7,7 @@
import pytest
if TYPE_CHECKING:
- from mkdocstrings.handlers.python import PythonHandler
+ from mkdocstrings_handlers.python.handler import PythonHandler
@pytest.mark.parametrize(