diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 26a08259..0e0af470 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -628,8 +628,17 @@ def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=No if "sphinx" in sys.modules: from sphinx.ext.autodoc import ALL + + try: + from sphinx.ext.autodoc._sentinels import EMPTY + except ImportError: + try: + from sphinx.ext.autodoc import EMPTY + except ImportError: + EMPTY = object() else: ALL = object() + EMPTY = object() if config is None: config = {} @@ -649,7 +658,11 @@ def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=No _members = config.get("members", []) if _members is ALL: _members = None - _exclude = config.get("exclude-members", []) + _exclude = config.get("exclude_members") + if _exclude is None: + _exclude = config.get("exclude-members", []) + if _exclude is EMPTY: + _exclude = ALL if config.get("show_class_members", True) and _exclude is not ALL: diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 2b9757d7..8a0cefe7 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -187,10 +187,17 @@ def mangle_docstrings(app: SphinxApp, what, name, obj, options, lines): } # TODO: Find a cleaner way to take care of this change away from dict # https://github.com/sphinx-doc/sphinx/issues/13942 - try: - cfg.update(options or {}) - except TypeError: - cfg.update(options.__dict__ or {}) + if options is not None: + if isinstance(options, dict): + cfg.update(options) + elif hasattr(options, "from_directive_options"): + # Sphinx 9.x _AutoDocumenterOptions + cfg.update(vars(options)) + else: + try: + cfg.update(options or {}) + except TypeError: + cfg.update(options.__dict__ or {}) u_NL = "\n" if what == "module": # Strip top title diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py index f879d7fb..b923802f 100644 --- a/numpydoc/tests/test_numpydoc.py +++ b/numpydoc/tests/test_numpydoc.py @@ -4,10 +4,18 @@ from pathlib import PosixPath import pytest +import sphinx from docutils import nodes from sphinx.ext.autodoc import ALL from sphinx.util import logging +try: + from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions + from sphinx.ext.autodoc._sentinels import EMPTY +except ImportError: + _AutoDocumenterOptions = None # Sphinx < 9 + EMPTY = None + from numpydoc.numpydoc import ( _clean_text_signature, clean_backrefs, @@ -82,6 +90,35 @@ def test_mangle_docstrings_basic(): assert "upper" not in [x.strip() for x in lines] +@pytest.mark.skipif( + sphinx.version_info < (9, 0), + reason="_AutoDocumenterOptions not available", +) +@pytest.mark.parametrize( + ("kwargs", "has_rpartition", "has_upper"), + [ + ({"exclude_members": {"upper"}}, True, False), + ({"exclude_members": EMPTY}, False, False), + ({}, True, True), + ], +) +def test_mangle_docstrings_exclude_members_sphinx9(kwargs, has_rpartition, has_upper): + """Non-regression test for #671: Sphinx 9.x _AutoDocumenterOptions with + underscored exclude_members key and EMPTY sentinel.""" + s = """ +A top section before + +.. autoclass:: str + """ + + lines = s.split("\n") + options = _AutoDocumenterOptions(**kwargs) + mangle_docstrings(MockApp(), "class", "str", str, options, lines) + lines = [x.strip() for x in lines] + assert ("rpartition" in lines) is has_rpartition + assert ("upper" in lines) is has_upper + + def test_mangle_docstrings_inherited_class_members(): # if subclass docs are rendered, this PosixPath should have Path.samefile p = """