From 94e786828369d8b04ab2e7439fd644ee8fad191c Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 23 May 2026 00:32:17 +0000 Subject: [PATCH 1/3] Require Sphinx 8; test Sphinx 8 and 9 in CI - Bump minimum Sphinx dependency from 6 to 8 - Replace test-oldsphinx job (sphinx<7/8/9) with test-sphinx testing sphinx>=8,<9 and sphinx>=9,<10 - Simplify EMPTY sentinel import: remove innermost fallback that guarded against Sphinx <7 not having EMPTY at all --- .github/workflows/test.yml | 9 ++++----- numpydoc/docscrape.py | 5 +---- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a37f1645..16d6632a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,13 +11,12 @@ on: branches: [main] jobs: - test-oldsphinx: - runs-on: ${{ matrix.os }}-latest + test-sphinx: + runs-on: ubuntu-latest strategy: matrix: - os: [Ubuntu] - python-version: ["3.10", "3.11"] - sphinx-version: ["sphinx<7", "sphinx<8", "sphinx<9"] + python-version: ["3.11"] + sphinx-version: ["sphinx>=8,<9", "sphinx>=9,<10"] defaults: run: shell: bash -eo pipefail {0} diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 0e0af470..6f0a5981 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -632,10 +632,7 @@ def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=No try: from sphinx.ext.autodoc._sentinels import EMPTY except ImportError: - try: - from sphinx.ext.autodoc import EMPTY - except ImportError: - EMPTY = object() + from sphinx.ext.autodoc import EMPTY else: ALL = object() EMPTY = object() diff --git a/pyproject.toml b/pyproject.toml index 6cae975e..9d3f900f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ 'Topic :: Documentation', ] dependencies = [ - 'sphinx>=6', + 'sphinx>=8', "tomli>=1.1.0;python_version<'3.11'", ] From c45a959a3b905630ff3d88a5e578a7853817835b Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Fri, 22 May 2026 17:39:56 -0700 Subject: [PATCH 2/3] Tune versions --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 16d6632a..1b65cbb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,8 +15,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11"] - sphinx-version: ["sphinx>=8,<9", "sphinx>=9,<10"] + python-version: ["3.12"] + sphinx-version: ["sphinx==8", "sphinx>=9"] defaults: run: shell: bash -eo pipefail {0} @@ -65,7 +65,7 @@ jobs: strategy: matrix: os: ["ubuntu", "macos", "windows"] - python-version: ["3.12", "3.13", "3.14"] + python-version: ["3.13", "3.14"] steps: - uses: actions/checkout@v6 From 1d423380d21877559012169413347ef91dfbc815 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 23 May 2026 01:09:53 +0000 Subject: [PATCH 3/3] MAINT: replace _traverse_or_findall shim with node.findall() Sphinx 8 requires docutils>=0.18.1, which introduced node.findall(). The compatibility shim is no longer needed. --- numpydoc/numpydoc.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 8a0cefe7..6d3e7531 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -43,19 +43,6 @@ HASH_LEN = 12 -def _traverse_or_findall(node, condition, **kwargs): - """Triage node.traverse (docutils <0.18.1) vs node.findall. - - TODO: This check can be removed when the minimum supported docutils version - for numpydoc is docutils>=0.18.1 - """ - return ( - node.findall(condition, **kwargs) - if hasattr(node, "findall") - else node.traverse(condition, **kwargs) - ) - - def rename_references(app: SphinxApp, what, name, obj, options, lines): # decorate reference numbers so that there are no duplicates # these are later undecorated in the doctree, in relabel_references @@ -84,7 +71,6 @@ def rename_references(app: SphinxApp, what, name, obj, options, lines): def _is_cite_in_numpydoc_docstring(citation_node): # Find DEDUPLICATION_TAG in comment as last node of sibling section - # XXX: I failed to use citation_node.traverse to do this: section_node = citation_node.parent def is_docstring_section(node): @@ -96,8 +82,7 @@ def is_docstring_section(node): return False sibling_sections = itertools.chain( - _traverse_or_findall( - section_node, + section_node.findall( is_docstring_section, include_self=True, descend=False, @@ -120,7 +105,7 @@ def is_docstring_section(node): def relabel_references(app: SphinxApp, doc): # Change 'hash-ref' to 'ref' in label text - for citation_node in _traverse_or_findall(doc, citation): + for citation_node in doc.findall(citation): if not _is_cite_in_numpydoc_docstring(citation_node): continue label_node = citation_node[0] @@ -140,7 +125,7 @@ def matching_pending_xref(node): and node[0].astext() == f"[{ref_text}]" ) - for xref_node in _traverse_or_findall(ref.parent, matching_pending_xref): + for xref_node in ref.parent.findall(matching_pending_xref): xref_node.replace(xref_node[0], Text(f"[{new_text}]")) ref.replace(ref_text, new_text.copy()) @@ -148,14 +133,14 @@ def matching_pending_xref(node): def clean_backrefs(app: SphinxApp, doc, docname): # only::latex directive has resulted in citation backrefs without reference known_ref_ids = set() - for ref in _traverse_or_findall(doc, reference, descend=True): + for ref in doc.findall(reference, descend=True): for id_ in ref["ids"]: known_ref_ids.add(id_) # some extensions produce backrefs to inline elements - for ref in _traverse_or_findall(doc, inline, descend=True): + for ref in doc.findall(inline, descend=True): for id_ in ref["ids"]: known_ref_ids.add(id_) - for citation_node in _traverse_or_findall(doc, citation, descend=True): + for citation_node in doc.findall(citation, descend=True): # remove backrefs to non-existent refs citation_node["backrefs"] = [ id_ for id_ in citation_node["backrefs"] if id_ in known_ref_ids