sphinx-examples-as-code 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/PKG-INFO +1 -1
  2. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code/__init__.py +29 -4
  3. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code/_version.py +3 -3
  4. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/PKG-INFO +1 -1
  5. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/SOURCES.txt +4 -0
  6. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/scm_file_list.json +4 -0
  7. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/scm_version.json +2 -2
  8. sphinx_examples_as_code-0.2.1/tests/single_function_fixture/conf.py +84 -0
  9. sphinx_examples_as_code-0.2.1/tests/single_function_fixture/index.rst +4 -0
  10. sphinx_examples_as_code-0.2.1/tests/single_function_fixture/mymodule.py +13 -0
  11. sphinx_examples_as_code-0.2.1/tests/test_single_function_page.py +40 -0
  12. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/test_sphinx_examples_as_code.py +40 -0
  13. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/.github/dependabot.yml +0 -0
  14. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/.github/release.yml +0 -0
  15. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/.github/workflows/ci.yml +0 -0
  16. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/.gitignore +0 -0
  17. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/.pre-commit-config.yaml +0 -0
  18. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/README.md +0 -0
  19. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/pyproject.toml +0 -0
  20. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/setup.cfg +0 -0
  21. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/dependency_links.txt +0 -0
  22. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/requires.txt +0 -0
  23. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/sphinx_examples_as_code.egg-info/top_level.txt +0 -0
  24. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/gallery_fixture/conf.py +0 -0
  25. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/gallery_fixture/examples/GALLERY_HEADER.rst +0 -0
  26. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/gallery_fixture/examples/plot_minimal.py +0 -0
  27. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/gallery_fixture/index.rst +0 -0
  28. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/test_gallery_downloads.py +0 -0
  29. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/test_tinypages.py +0 -0
  30. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/tinypages/conf.py +0 -0
  31. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/tinypages/docstring_cases.py +0 -0
  32. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/tinypages/docstring_cases.rst +0 -0
  33. {sphinx_examples_as_code-0.2.0 → sphinx_examples_as_code-0.2.1}/tests/tinypages/index.rst +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sphinx-examples-as-code
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Sphinx extension for converting docstring examples into downloadable code.
5
5
  Author-email: The PyVista Developers <info@pyvista.org>
6
6
  License-Expression: MIT
@@ -494,15 +494,40 @@ def _examples_spans(doctree: nodes.document) -> list[tuple[nodes.Element, int, i
494
494
  return spans
495
495
 
496
496
 
497
+ def _desc_id(desc: addnodes.desc) -> str | None:
498
+ """Pull the documented object's id (e.g. ``pkg.mod.func``) off a ``desc`` node."""
499
+ signature = desc.next_node(addnodes.desc_signature)
500
+ if signature is not None and signature.get('ids'):
501
+ return signature['ids'][0]
502
+ return None
503
+
504
+
497
505
  def _qualified_name_for(node: nodes.Node, docname: str, counter: int) -> str:
498
506
  """Best-effort identifier used to name the generated file and its title header."""
499
507
  ancestor: nodes.Node | None = node.parent
500
508
  while ancestor is not None:
501
509
  if isinstance(ancestor, addnodes.desc):
502
- signature = ancestor.next_node(addnodes.desc_signature)
503
- if signature is not None and signature.get('ids'):
504
- return signature['ids'][0]
505
- ancestor = ancestor.parent
510
+ desc_id = _desc_id(ancestor)
511
+ if desc_id is not None:
512
+ return desc_id
513
+ parent = ancestor.parent
514
+ if parent is not None:
515
+ # An "Examples" section written as a real RST heading (not a
516
+ # bare ``.. rubric::``) starts a new docutils section -- one
517
+ # can't nest inside the ``desc_content`` a numpydoc-rendered
518
+ # docstring builds it in, so it gets promoted to a sibling of
519
+ # the object's own ``desc`` node instead (same reason
520
+ # sphinx-gallery's own "# %%"-headed cells become siblings; see
521
+ # gallery mode's sibling-section handling). Look for the
522
+ # nearest ``desc`` immediately preceding it under that shared
523
+ # parent, closest first.
524
+ index = parent.index(ancestor)
525
+ for sibling in reversed(parent.children[:index]):
526
+ if isinstance(sibling, addnodes.desc):
527
+ desc_id = _desc_id(sibling)
528
+ if desc_id is not None:
529
+ return desc_id
530
+ ancestor = parent
506
531
  base = Path(docname).name or docname
507
532
  return f'{base}-example-{counter}'
508
533
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.0'
22
- __version_tuple__ = version_tuple = (0, 2, 0)
21
+ __version__ = version = '0.2.1'
22
+ __version_tuple__ = version_tuple = (0, 2, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g5ead8d7ac'
24
+ __commit_id__ = commit_id = 'g5224fa967'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sphinx-examples-as-code
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Sphinx extension for converting docstring examples into downloadable code.
5
5
  Author-email: The PyVista Developers <info@pyvista.org>
6
6
  License-Expression: MIT
@@ -15,12 +15,16 @@ sphinx_examples_as_code.egg-info/scm_file_list.json
15
15
  sphinx_examples_as_code.egg-info/scm_version.json
16
16
  sphinx_examples_as_code.egg-info/top_level.txt
17
17
  tests/test_gallery_downloads.py
18
+ tests/test_single_function_page.py
18
19
  tests/test_sphinx_examples_as_code.py
19
20
  tests/test_tinypages.py
20
21
  tests/gallery_fixture/conf.py
21
22
  tests/gallery_fixture/index.rst
22
23
  tests/gallery_fixture/examples/GALLERY_HEADER.rst
23
24
  tests/gallery_fixture/examples/plot_minimal.py
25
+ tests/single_function_fixture/conf.py
26
+ tests/single_function_fixture/index.rst
27
+ tests/single_function_fixture/mymodule.py
24
28
  tests/tinypages/conf.py
25
29
  tests/tinypages/docstring_cases.py
26
30
  tests/tinypages/docstring_cases.rst
@@ -8,6 +8,7 @@
8
8
  ".github/release.yml",
9
9
  ".github/workflows/ci.yml",
10
10
  "tests/test_tinypages.py",
11
+ "tests/test_single_function_page.py",
11
12
  "tests/test_gallery_downloads.py",
12
13
  "tests/test_sphinx_examples_as_code.py",
13
14
  "tests/tinypages/index.rst",
@@ -18,6 +19,9 @@
18
19
  "tests/gallery_fixture/conf.py",
19
20
  "tests/gallery_fixture/examples/plot_minimal.py",
20
21
  "tests/gallery_fixture/examples/GALLERY_HEADER.rst",
22
+ "tests/single_function_fixture/index.rst",
23
+ "tests/single_function_fixture/conf.py",
24
+ "tests/single_function_fixture/mymodule.py",
21
25
  "sphinx_examples_as_code/__init__.py"
22
26
  ]
23
27
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.2.0",
2
+ "tag": "0.2.1",
3
3
  "distance": 0,
4
- "node": "g5ead8d7ac522b8f558b03171296568d1bb3b8722",
4
+ "node": "g5224fa967500cbd885faa83213fd2a70aa717a21",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-05"
@@ -0,0 +1,84 @@
1
+ """Sphinx config reproducing a real-world numpydoc section-hoisting setup.
2
+
3
+ numpydoc renders docstring sections (Notes, Examples, ...) as
4
+ ``.. rubric::`` by default, which never becomes a real docutils section --
5
+ so ``_qualified_name_for``'s plain ancestor walk (desc_content -> desc)
6
+ always finds the object's own ``desc`` node directly, no special handling
7
+ needed. Some projects (pyvista's real docs included -- see its
8
+ ``doc/source/conf.py``) override this to get real headings for the page's
9
+ "on this page" navbar, then hoist those sections out of the autodoc
10
+ ``desc`` node to page level so Sphinx's TocTreeCollector can see them. That
11
+ lands "Examples" as a *sibling* of the object's own ``desc``, not an
12
+ ancestor of its own heading -- the exact structure that produced a
13
+ nonsensical ``<docname>-example-1`` header in production for a page that
14
+ unambiguously documents one function.
15
+
16
+ Reproduced here (rather than in ``tinypages/conf.py``) since it changes
17
+ docstring-section rendering globally for the build it's applied to, and
18
+ ``tinypages`` is shared by many other tests that already pin exact output
19
+ for the un-hoisted (rubric-based) case.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from pathlib import Path
25
+ import sys
26
+
27
+ from docutils import nodes
28
+ from numpydoc.docscrape_sphinx import SphinxDocString
29
+ from sphinx import addnodes
30
+
31
+ sys.path.insert(0, str(Path(__file__).parent))
32
+
33
+ extensions = [
34
+ 'numpydoc',
35
+ 'sphinx.ext.autodoc',
36
+ 'sphinx_examples_as_code',
37
+ ]
38
+
39
+ root_doc = 'index'
40
+ project = 'single_function_fixture'
41
+ exclude_patterns = ['_build']
42
+ numpydoc_show_class_members = False
43
+
44
+
45
+ def _str_header(self, name):
46
+ return [name, '-' * len(name), '']
47
+
48
+
49
+ SphinxDocString._str_header = _str_header
50
+
51
+
52
+ def _is_nested_desc(node: nodes.Node) -> bool:
53
+ parent = node.parent
54
+ while parent is not None:
55
+ if isinstance(parent, addnodes.desc):
56
+ return True
57
+ parent = parent.parent
58
+ return False
59
+
60
+
61
+ def _hoist_docstring_sections(app, doctree) -> None:
62
+ """Move docstring sections out of their ``desc`` node to page level."""
63
+ for desc in list(doctree.findall(addnodes.desc)):
64
+ if _is_nested_desc(desc):
65
+ continue
66
+ parent = desc.parent
67
+ if parent is None:
68
+ continue
69
+ # Only hoist when this object owns the page -- otherwise sections
70
+ # from several objects would collide at page level.
71
+ if len([node for node in parent if isinstance(node, addnodes.desc)]) != 1:
72
+ continue
73
+ content = next((node for node in desc if isinstance(node, addnodes.desc_content)), None)
74
+ if content is None:
75
+ continue
76
+ sections = [node for node in content if isinstance(node, nodes.section)]
77
+ index = parent.index(desc)
78
+ for offset, section in enumerate(sections):
79
+ content.remove(section)
80
+ parent.insert(index + 1 + offset, section)
81
+
82
+
83
+ def setup(app):
84
+ app.connect('doctree-read', _hoist_docstring_sections)
@@ -0,0 +1,4 @@
1
+ download_bunny
2
+ ==============
3
+
4
+ .. autofunction:: mymodule.download_bunny
@@ -0,0 +1,13 @@
1
+ """Dummy module documented as a single-function page, like a real autosummary build."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def download_bunny():
7
+ """Download bunny dataset.
8
+
9
+ Examples
10
+ --------
11
+ >>> import sys
12
+
13
+ """
@@ -0,0 +1,40 @@
1
+ """Regression test for a real numpydoc "hoisted section" page structure.
2
+
3
+ Builds a real sphinx site (not a hand-built doctree) using the same
4
+ rubric-to-heading + section-hoisting setup pyvista's actual docs build uses
5
+ (see ``single_function_fixture/conf.py``), confirming
6
+ ``_qualified_name_for`` names the download after the documented function
7
+ rather than falling back to a nonsensical ``<docname>-example-1`` -- the
8
+ exact bug seen in a real pyvista build for
9
+ ``pyvista.examples.downloads.download_bunny``.
10
+
11
+ Complements the hand-built-doctree unit tests for ``_qualified_name_for``
12
+ in ``test_sphinx_examples_as_code.py``.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pathlib import Path
18
+
19
+ from test_tinypages import _run_sphinx_build
20
+ from test_tinypages import _sphinx_build_cmd
21
+
22
+ FIXTURE_DIR = Path(__file__).parent / 'single_function_fixture'
23
+
24
+
25
+ def test_download_named_after_the_function_not_the_docname(tmp_path: Path):
26
+ html_dir = tmp_path / 'html'
27
+ doctree_dir = tmp_path / 'doctrees'
28
+ returncode, out, err = _run_sphinx_build(_sphinx_build_cmd(FIXTURE_DIR, html_dir, doctree_dir))
29
+ assert returncode == 0, f'sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n'
30
+
31
+ downloads_dir = html_dir / '_downloads'
32
+ py_files = list(downloads_dir.rglob('*.py'))
33
+ assert len(py_files) == 1, f'expected exactly one generated .py, got {py_files}'
34
+ py_path = py_files[0]
35
+
36
+ # not "index-example-1" (the docname-derived fallback name)
37
+ assert py_path.stem == 'mymodule_download_bunny'
38
+
39
+ lines = py_path.read_text(encoding='utf-8').splitlines()
40
+ assert lines[0] == '# Examples from mymodule.download_bunny'
@@ -700,6 +700,46 @@ def test_qualified_name_for_desc_signature_without_ids_falls_back():
700
700
  assert seac._qualified_name_for(heading, 'mypage', 2) == 'mypage-example-2'
701
701
 
702
702
 
703
+ def test_qualified_name_for_examples_section_sibling_of_desc():
704
+ r"""A real ``Examples\n--------`` heading, not a bare rubric.
705
+
706
+ Matches an actual numpydoc-rendered autosummary page: docutils can't
707
+ nest a new section inside the non-sectioning ``desc_content`` a
708
+ docstring's other fields render into, so it promotes "Examples" to a
709
+ section of its own, sitting as a *sibling* of the object's ``desc`` --
710
+ not an ancestor of the heading the way the plain-rubric case above is.
711
+ Confirmed against a real pyvista build, where this exact structure
712
+ produced a nonsensical ``<docname>-example-1`` header for a page that
713
+ unambiguously documents one function.
714
+ """
715
+ page_section = nodes.section()
716
+ page_section += nodes.title('', 'download_bunny')
717
+ desc = addnodes.desc()
718
+ desc += addnodes.desc_signature(ids=['pyvista.examples.downloads.download_bunny'])
719
+ page_section += desc
720
+ examples_section = nodes.section()
721
+ heading = nodes.title('', 'Examples')
722
+ examples_section += heading
723
+ page_section += examples_section
724
+
725
+ assert (
726
+ seac._qualified_name_for(heading, 'page', 1) == 'pyvista.examples.downloads.download_bunny'
727
+ )
728
+
729
+
730
+ def test_qualified_name_for_examples_section_sibling_no_desc_falls_back():
731
+ # a promoted sibling section with nothing resembling a desc anywhere
732
+ # nearby -- e.g. a plain prose page's own "Examples\n--------" heading
733
+ page_section = nodes.section()
734
+ page_section += nodes.title('', 'Some Guide Page')
735
+ examples_section = nodes.section()
736
+ heading = nodes.title('', 'Examples')
737
+ examples_section += heading
738
+ page_section += examples_section
739
+
740
+ assert seac._qualified_name_for(heading, 'mypage', 4) == 'mypage-example-4'
741
+
742
+
703
743
  # ---------------------------------------------------------------------------
704
744
  # _header_segment
705
745
  # ---------------------------------------------------------------------------