sphinx-examples-as-code 0.8.1__tar.gz → 0.8.3__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.
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/.github/workflows/ci.yml +1 -1
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/PKG-INFO +11 -7
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/README.md +9 -4
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/pyproject.toml +1 -2
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code/__init__.py +92 -5
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code/_version.py +3 -3
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code.egg-info/PKG-INFO +11 -7
- sphinx_examples_as_code-0.8.3/sphinx_examples_as_code.egg-info/scm_version.json +8 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_single_function_page.py +1 -1
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_sphinx_examples_as_code.py +101 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_tinypages.py +9 -21
- sphinx_examples_as_code-0.8.1/sphinx_examples_as_code.egg-info/scm_version.json +0 -8
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/.github/dependabot.yml +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/.github/release.yml +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/.gitignore +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/.pre-commit-config.yaml +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/LICENSE +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/setup.cfg +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code.egg-info/SOURCES.txt +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code.egg-info/dependency_links.txt +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code.egg-info/requires.txt +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code.egg-info/scm_file_list.json +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code.egg-info/top_level.txt +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/gallery_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/gallery_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/gallery_fixture/examples/plot_minimal.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/gallery_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/heading_level_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/heading_level_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/heading_level_fixture/examples/plot_reused_level.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/heading_level_fixture/examples/plot_three_levels.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/heading_level_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/single_function_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/single_function_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/single_function_fixture/mymodule.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_gallery_downloads.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_heading_level_reuse.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/tinypages/conf.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/tinypages/docstring_cases.py +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/tinypages/docstring_cases.rst +0 -0
- {sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/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.8.
|
|
3
|
+
Version: 0.8.3
|
|
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
|
|
@@ -13,13 +13,12 @@ Classifier: Operating System :: MacOS
|
|
|
13
13
|
Classifier: Operating System :: Microsoft :: Windows
|
|
14
14
|
Classifier: Operating System :: POSIX :: Linux
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
20
|
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
22
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.11
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE
|
|
25
24
|
Requires-Dist: packaging>=22
|
|
@@ -100,7 +99,9 @@ sphinx_examples_as_code_conf = {
|
|
|
100
99
|
offered in that order regardless of how the list is written. Without marimo 0.14 or
|
|
101
100
|
newer installed, the default skips the marimo download, and a `formats` that names
|
|
102
101
|
`'marimo'` (in `conf.py` or through `-D`) stops the build with a configuration error. An unknown format raises a configuration error too;
|
|
103
|
-
the old `'py'` and `'ipynb'` names say what replaced them.
|
|
102
|
+
the old `'py'` and `'ipynb'` names say what replaced them. `<name>` is the documented
|
|
103
|
+
object's full name without its top-level package, e.g. `Plotter_show` for
|
|
104
|
+
`pyvista.Plotter.show`, or the gallery example's file name.
|
|
104
105
|
- `link_labels`: the text of the download link(s) themselves, per format. Set only the
|
|
105
106
|
format(s) you want to change; any left unset keep reading their own default shown above.
|
|
106
107
|
- `gallery_downloads`: opt-in takeover of
|
|
@@ -158,7 +159,10 @@ Imports are read from the code, not run. Each imported top-level module maps to
|
|
|
158
159
|
project that provides it in the environment building the docs; standard-library
|
|
159
160
|
modules, relative imports and modules no single installed project provides are left
|
|
160
161
|
out. Projects the code needs but never imports, such as an optional backend, go in
|
|
161
|
-
`dependencies`.
|
|
162
|
+
`dependencies`. In Python scripts and Jupyter notebooks, a dependency with no version or extras of
|
|
163
|
+
its own is left out when another listed one already installs it on every platform, e.g. `numpy`
|
|
164
|
+
next to `pyvista`. marimo notebooks list every dependency, since marimo's sandbox adds back any
|
|
165
|
+
it does not find by name.
|
|
162
166
|
|
|
163
167
|
The project named by Sphinx's own `project` setting (e.g. `project = 'PyVista'`) gets
|
|
164
168
|
extra treatment when an example imports it and it is installed: its dependency becomes
|
|
@@ -169,8 +173,8 @@ Unless `requires_python` is set, the block's `requires-python` combines:
|
|
|
169
173
|
|
|
170
174
|
- that project's own `Requires-Python`, e.g. `>=3.10`, when it is listed;
|
|
171
175
|
- `<3.N`, one minor version above the newest Python named in the `Programming Language ::
|
|
172
|
-
Python :: 3.X` classifiers of that project and of every listed
|
|
173
|
-
from a platform-specific wheel, as installed in the environment building the docs.
|
|
176
|
+
Python :: 3.X` classifiers of that project and of every dependency, listed or left out,
|
|
177
|
+
installed from a platform-specific wheel, as installed in the environment building the docs.
|
|
174
178
|
Pure-Python dependencies and dependencies without such classifiers don't count, and
|
|
175
179
|
the bound is dropped when it would exclude every Python the project's own
|
|
176
180
|
`Requires-Python` allows.
|
|
@@ -68,7 +68,9 @@ sphinx_examples_as_code_conf = {
|
|
|
68
68
|
offered in that order regardless of how the list is written. Without marimo 0.14 or
|
|
69
69
|
newer installed, the default skips the marimo download, and a `formats` that names
|
|
70
70
|
`'marimo'` (in `conf.py` or through `-D`) stops the build with a configuration error. An unknown format raises a configuration error too;
|
|
71
|
-
the old `'py'` and `'ipynb'` names say what replaced them.
|
|
71
|
+
the old `'py'` and `'ipynb'` names say what replaced them. `<name>` is the documented
|
|
72
|
+
object's full name without its top-level package, e.g. `Plotter_show` for
|
|
73
|
+
`pyvista.Plotter.show`, or the gallery example's file name.
|
|
72
74
|
- `link_labels`: the text of the download link(s) themselves, per format. Set only the
|
|
73
75
|
format(s) you want to change; any left unset keep reading their own default shown above.
|
|
74
76
|
- `gallery_downloads`: opt-in takeover of
|
|
@@ -126,7 +128,10 @@ Imports are read from the code, not run. Each imported top-level module maps to
|
|
|
126
128
|
project that provides it in the environment building the docs; standard-library
|
|
127
129
|
modules, relative imports and modules no single installed project provides are left
|
|
128
130
|
out. Projects the code needs but never imports, such as an optional backend, go in
|
|
129
|
-
`dependencies`.
|
|
131
|
+
`dependencies`. In Python scripts and Jupyter notebooks, a dependency with no version or extras of
|
|
132
|
+
its own is left out when another listed one already installs it on every platform, e.g. `numpy`
|
|
133
|
+
next to `pyvista`. marimo notebooks list every dependency, since marimo's sandbox adds back any
|
|
134
|
+
it does not find by name.
|
|
130
135
|
|
|
131
136
|
The project named by Sphinx's own `project` setting (e.g. `project = 'PyVista'`) gets
|
|
132
137
|
extra treatment when an example imports it and it is installed: its dependency becomes
|
|
@@ -137,8 +142,8 @@ Unless `requires_python` is set, the block's `requires-python` combines:
|
|
|
137
142
|
|
|
138
143
|
- that project's own `Requires-Python`, e.g. `>=3.10`, when it is listed;
|
|
139
144
|
- `<3.N`, one minor version above the newest Python named in the `Programming Language ::
|
|
140
|
-
Python :: 3.X` classifiers of that project and of every listed
|
|
141
|
-
from a platform-specific wheel, as installed in the environment building the docs.
|
|
145
|
+
Python :: 3.X` classifiers of that project and of every dependency, listed or left out,
|
|
146
|
+
installed from a platform-specific wheel, as installed in the environment building the docs.
|
|
142
147
|
Pure-Python dependencies and dependencies without such classifiers don't count, and
|
|
143
148
|
the bound is dropped when it would exclude every Python the project's own
|
|
144
149
|
`Requires-Python` allows.
|
|
@@ -25,7 +25,6 @@ classifiers = [
|
|
|
25
25
|
'Operating System :: Microsoft :: Windows',
|
|
26
26
|
'Operating System :: POSIX :: Linux',
|
|
27
27
|
'Programming Language :: Python :: 3',
|
|
28
|
-
'Programming Language :: Python :: 3.10',
|
|
29
28
|
'Programming Language :: Python :: 3.11',
|
|
30
29
|
'Programming Language :: Python :: 3.12',
|
|
31
30
|
'Programming Language :: Python :: 3.13',
|
|
@@ -39,7 +38,7 @@ keywords = ['download', 'examples', 'sphinx']
|
|
|
39
38
|
license = 'MIT'
|
|
40
39
|
name = 'sphinx-examples-as-code'
|
|
41
40
|
readme = 'README.md'
|
|
42
|
-
requires-python = '>=3.
|
|
41
|
+
requires-python = '>=3.11'
|
|
43
42
|
|
|
44
43
|
[project.optional-dependencies]
|
|
45
44
|
gallery = ['sphinx-gallery>=0.22,<0.23']
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code/__init__.py
RENAMED
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import ast
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
+
from dataclasses import replace
|
|
10
11
|
import functools
|
|
11
12
|
import hashlib
|
|
12
13
|
import importlib.metadata
|
|
@@ -808,6 +809,14 @@ def _qualified_name_for(node: nodes.Node, docname: str, counter: int) -> str:
|
|
|
808
809
|
return f'{base}-example-{counter}'
|
|
809
810
|
|
|
810
811
|
|
|
812
|
+
def _download_stem(node: nodes.Node, docname: str, counter: int) -> str:
|
|
813
|
+
"""Name a span's downloads, e.g. ``Plotter.show`` for ``pyvista.Plotter.show``."""
|
|
814
|
+
name = _qualified_name_for(node, docname, counter)
|
|
815
|
+
if any(_desc_id(desc) is not None for desc in _enclosing_descs(node)):
|
|
816
|
+
return name.partition('.')[2] or name
|
|
817
|
+
return name
|
|
818
|
+
|
|
819
|
+
|
|
811
820
|
def _header_title(qualified_name: str) -> str:
|
|
812
821
|
"""Build the file's own header title text, e.g. ``Examples from pyvista.read``."""
|
|
813
822
|
return f'Examples from {qualified_name}'
|
|
@@ -935,7 +944,7 @@ def _build_notebook(cells: list[tuple[str, list[str]]]) -> dict:
|
|
|
935
944
|
|
|
936
945
|
|
|
937
946
|
def _download_filename(name: str, fmt: str) -> str:
|
|
938
|
-
"""Return the file name a download is written under, e.g. ``
|
|
947
|
+
"""Return the file name a download is written under, e.g. ``Plotter_show.py``."""
|
|
939
948
|
safe_name = name.replace('.', '_') if name else 'example'
|
|
940
949
|
return f'{safe_name}{_SUFFIXES[fmt]}'
|
|
941
950
|
|
|
@@ -970,6 +979,17 @@ def _write_jupyter_notebook(app: Sphinx, name: str, notebook: dict) -> str:
|
|
|
970
979
|
return _write_download_file(app, name, _FMT_JUPYTER, json.dumps(notebook, indent=1))
|
|
971
980
|
|
|
972
981
|
|
|
982
|
+
def _title_first(cells: list) -> list:
|
|
983
|
+
"""Move marimo's leading ``import marimo as mo`` cell below the title cell after it."""
|
|
984
|
+
if (
|
|
985
|
+
len(cells) > 1
|
|
986
|
+
and cells[0].code == 'import marimo as mo'
|
|
987
|
+
and cells[1].code.startswith('mo.md(')
|
|
988
|
+
):
|
|
989
|
+
return [cells[1], cells[0], *cells[2:]]
|
|
990
|
+
return cells
|
|
991
|
+
|
|
992
|
+
|
|
973
993
|
def _drop_future_imports(source: list[str]) -> list[str]:
|
|
974
994
|
"""Remove the lines of every ``from __future__`` import that has its lines to itself."""
|
|
975
995
|
try:
|
|
@@ -997,7 +1017,8 @@ def _write_marimo_notebook(app: Sphinx, name: str, notebook: dict, header: str =
|
|
|
997
1017
|
else cell
|
|
998
1018
|
for cell in notebook['cells']
|
|
999
1019
|
]
|
|
1000
|
-
|
|
1020
|
+
ir = MarimoConvert.from_ipynb(json.dumps({**notebook, 'cells': cells})).to_ir()
|
|
1021
|
+
source = MarimoConvert.from_ir(replace(ir, cells=_title_first(ir.cells))).to_py()
|
|
1001
1022
|
return _write_download_file(app, name, _FMT_MARIMO, header + source)
|
|
1002
1023
|
|
|
1003
1024
|
|
|
@@ -1070,6 +1091,66 @@ def _keep_pin(requirement: str, pin: str | None) -> str:
|
|
|
1070
1091
|
return str(parsed)
|
|
1071
1092
|
|
|
1072
1093
|
|
|
1094
|
+
_QUOTED = re.compile(r'"[^"]*"|\'[^\']*\'')
|
|
1095
|
+
_MARKER_WORDS = {'and', 'or', 'not', 'in'}
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _depends_only_on_extras(requirement: Requirement, extras: frozenset[str]) -> bool:
|
|
1099
|
+
"""Check whether ``requirement`` applies with ``extras`` on every platform and Python."""
|
|
1100
|
+
if requirement.marker is None:
|
|
1101
|
+
return True
|
|
1102
|
+
names = set(re.findall(r'[A-Za-z_.]+', _QUOTED.sub('', str(requirement.marker))))
|
|
1103
|
+
if names - _MARKER_WORDS != {'extra'}:
|
|
1104
|
+
return False
|
|
1105
|
+
return any(requirement.marker.evaluate({'extra': extra}) for extra in extras | {''})
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
@functools.cache
|
|
1109
|
+
def _installed_requirements(name: str, extras: frozenset[str]) -> frozenset[Requirement]:
|
|
1110
|
+
"""Return the unconditional requirements of an installed distribution with ``extras``."""
|
|
1111
|
+
try:
|
|
1112
|
+
requires = importlib.metadata.requires(name) or []
|
|
1113
|
+
except importlib.metadata.PackageNotFoundError:
|
|
1114
|
+
return frozenset()
|
|
1115
|
+
parsed = (Requirement(req) for req in requires)
|
|
1116
|
+
return frozenset(req for req in parsed if _depends_only_on_extras(req, extras))
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def _transitive_names(requirement: str) -> set[str]:
|
|
1120
|
+
"""Return the normalized names every install of ``requirement`` pulls in, itself excluded."""
|
|
1121
|
+
root = Requirement(requirement)
|
|
1122
|
+
seen: set[tuple[str, frozenset[str]]] = set()
|
|
1123
|
+
todo = [(canonicalize_name(root.name), frozenset(root.extras))]
|
|
1124
|
+
names: set[str] = set()
|
|
1125
|
+
while todo:
|
|
1126
|
+
key = todo.pop()
|
|
1127
|
+
if key in seen:
|
|
1128
|
+
continue
|
|
1129
|
+
seen.add(key)
|
|
1130
|
+
for req in _installed_requirements(*key):
|
|
1131
|
+
names.add(canonicalize_name(req.name))
|
|
1132
|
+
todo.append((canonicalize_name(req.name), frozenset(req.extras)))
|
|
1133
|
+
names.discard(canonicalize_name(root.name))
|
|
1134
|
+
return names
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def _drop_covered(deps: list[str]) -> list[str]:
|
|
1138
|
+
"""Drop each bare-name dependency that another listed dependency already installs."""
|
|
1139
|
+
pulled = {dep: _transitive_names(dep) for dep in deps}
|
|
1140
|
+
names = {dep: _canonical_name(dep) for dep in deps}
|
|
1141
|
+
|
|
1142
|
+
def covered(dep: str) -> bool:
|
|
1143
|
+
if dep != Requirement(dep).name:
|
|
1144
|
+
return False
|
|
1145
|
+
return any(
|
|
1146
|
+
names[dep] in pulled[other] and names[other] not in pulled[dep]
|
|
1147
|
+
for other in deps
|
|
1148
|
+
if other != dep
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
return [dep for dep in deps if not covered(dep)]
|
|
1152
|
+
|
|
1153
|
+
|
|
1073
1154
|
@dataclass(frozen=True)
|
|
1074
1155
|
class _DocumentedProject:
|
|
1075
1156
|
"""The installed distribution named by Sphinx's ``project``."""
|
|
@@ -1297,10 +1378,16 @@ def _build_download_entries(
|
|
|
1297
1378
|
cap = _python_upper_bound(deps[fmt], project.name if project else None)
|
|
1298
1379
|
return _python_range(floor, cap)
|
|
1299
1380
|
|
|
1381
|
+
# marimo's sandbox adds back any import or marimo itself that its header does not name.
|
|
1382
|
+
shown = {
|
|
1383
|
+
fmt: fmt_deps if fmt == _FMT_MARIMO else _drop_covered(fmt_deps)
|
|
1384
|
+
for fmt, fmt_deps in deps.items()
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1300
1387
|
def header_for(fmt: str, run_line: str) -> str:
|
|
1301
1388
|
if not deps:
|
|
1302
1389
|
return ''
|
|
1303
|
-
return _script_metadata_header(
|
|
1390
|
+
return _script_metadata_header(shown[fmt], requires_python_for(fmt), run_line)
|
|
1304
1391
|
|
|
1305
1392
|
entries = []
|
|
1306
1393
|
for fmt in _FORMAT_ORDER:
|
|
@@ -1313,7 +1400,7 @@ def _build_download_entries(
|
|
|
1313
1400
|
rel_path = _write_python_script(app, name, header + source)
|
|
1314
1401
|
elif fmt == _FMT_JUPYTER:
|
|
1315
1402
|
run_line = (
|
|
1316
|
-
_jupyter_run_line(
|
|
1403
|
+
_jupyter_run_line(shown[fmt], requires_python_for(fmt), filename) if deps else None
|
|
1317
1404
|
)
|
|
1318
1405
|
rel_path = _write_jupyter_notebook(app, name, notebook_for(run_line))
|
|
1319
1406
|
else:
|
|
@@ -1347,7 +1434,7 @@ def _process_span(
|
|
|
1347
1434
|
entries = _build_download_entries(
|
|
1348
1435
|
app,
|
|
1349
1436
|
docname,
|
|
1350
|
-
|
|
1437
|
+
_download_stem(heading, docname, counter),
|
|
1351
1438
|
_header_title(name),
|
|
1352
1439
|
nodes_in_span,
|
|
1353
1440
|
formats,
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/sphinx_examples_as_code/_version.py
RENAMED
|
@@ -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.8.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 8,
|
|
21
|
+
__version__ = version = '0.8.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 8, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g1289062e0'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-examples-as-code
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
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
|
|
@@ -13,13 +13,12 @@ Classifier: Operating System :: MacOS
|
|
|
13
13
|
Classifier: Operating System :: Microsoft :: Windows
|
|
14
14
|
Classifier: Operating System :: POSIX :: Linux
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
20
|
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
22
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.11
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE
|
|
25
24
|
Requires-Dist: packaging>=22
|
|
@@ -100,7 +99,9 @@ sphinx_examples_as_code_conf = {
|
|
|
100
99
|
offered in that order regardless of how the list is written. Without marimo 0.14 or
|
|
101
100
|
newer installed, the default skips the marimo download, and a `formats` that names
|
|
102
101
|
`'marimo'` (in `conf.py` or through `-D`) stops the build with a configuration error. An unknown format raises a configuration error too;
|
|
103
|
-
the old `'py'` and `'ipynb'` names say what replaced them.
|
|
102
|
+
the old `'py'` and `'ipynb'` names say what replaced them. `<name>` is the documented
|
|
103
|
+
object's full name without its top-level package, e.g. `Plotter_show` for
|
|
104
|
+
`pyvista.Plotter.show`, or the gallery example's file name.
|
|
104
105
|
- `link_labels`: the text of the download link(s) themselves, per format. Set only the
|
|
105
106
|
format(s) you want to change; any left unset keep reading their own default shown above.
|
|
106
107
|
- `gallery_downloads`: opt-in takeover of
|
|
@@ -158,7 +159,10 @@ Imports are read from the code, not run. Each imported top-level module maps to
|
|
|
158
159
|
project that provides it in the environment building the docs; standard-library
|
|
159
160
|
modules, relative imports and modules no single installed project provides are left
|
|
160
161
|
out. Projects the code needs but never imports, such as an optional backend, go in
|
|
161
|
-
`dependencies`.
|
|
162
|
+
`dependencies`. In Python scripts and Jupyter notebooks, a dependency with no version or extras of
|
|
163
|
+
its own is left out when another listed one already installs it on every platform, e.g. `numpy`
|
|
164
|
+
next to `pyvista`. marimo notebooks list every dependency, since marimo's sandbox adds back any
|
|
165
|
+
it does not find by name.
|
|
162
166
|
|
|
163
167
|
The project named by Sphinx's own `project` setting (e.g. `project = 'PyVista'`) gets
|
|
164
168
|
extra treatment when an example imports it and it is installed: its dependency becomes
|
|
@@ -169,8 +173,8 @@ Unless `requires_python` is set, the block's `requires-python` combines:
|
|
|
169
173
|
|
|
170
174
|
- that project's own `Requires-Python`, e.g. `>=3.10`, when it is listed;
|
|
171
175
|
- `<3.N`, one minor version above the newest Python named in the `Programming Language ::
|
|
172
|
-
Python :: 3.X` classifiers of that project and of every listed
|
|
173
|
-
from a platform-specific wheel, as installed in the environment building the docs.
|
|
176
|
+
Python :: 3.X` classifiers of that project and of every dependency, listed or left out,
|
|
177
|
+
installed from a platform-specific wheel, as installed in the environment building the docs.
|
|
174
178
|
Pure-Python dependencies and dependencies without such classifiers don't count, and
|
|
175
179
|
the bound is dropped when it would exclude every Python the project's own
|
|
176
180
|
`Requires-Python` allows.
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_single_function_page.py
RENAMED
|
@@ -34,7 +34,7 @@ def test_download_named_after_the_function_not_the_docname(tmp_path: Path):
|
|
|
34
34
|
python_path = python_files[0]
|
|
35
35
|
|
|
36
36
|
# not "index-example-1" (the docname-derived fallback name)
|
|
37
|
-
assert python_path.stem == '
|
|
37
|
+
assert python_path.stem == 'download_bunny'
|
|
38
38
|
|
|
39
39
|
lines = python_path.read_text(encoding='utf-8').splitlines()
|
|
40
40
|
assert lines[0] == '# Examples from mymodule.download_bunny'
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import copy
|
|
11
11
|
import json
|
|
12
|
+
from types import SimpleNamespace
|
|
12
13
|
from typing import TYPE_CHECKING
|
|
13
14
|
from unittest.mock import Mock
|
|
14
15
|
|
|
@@ -1140,6 +1141,24 @@ def test_qualified_name_for_desc_ancestor():
|
|
|
1140
1141
|
assert seac._qualified_name_for(heading, 'page', 1) == 'pkg.mod.func'
|
|
1141
1142
|
|
|
1142
1143
|
|
|
1144
|
+
@pytest.mark.parametrize(('desc_id', 'expected'), [('pkg.mod.func', 'mod.func'), ('func', 'func')])
|
|
1145
|
+
def test_download_stem_drops_top_level_package(desc_id: str, expected: str):
|
|
1146
|
+
desc = addnodes.desc()
|
|
1147
|
+
desc += addnodes.desc_signature(ids=[desc_id])
|
|
1148
|
+
content = addnodes.desc_content()
|
|
1149
|
+
heading = nodes.rubric()
|
|
1150
|
+
content += heading
|
|
1151
|
+
desc += content
|
|
1152
|
+
assert seac._download_stem(heading, 'page', 1) == expected
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def test_download_stem_keeps_dotted_fallback_name():
|
|
1156
|
+
section = nodes.section()
|
|
1157
|
+
heading = nodes.rubric()
|
|
1158
|
+
section += heading
|
|
1159
|
+
assert seac._download_stem(heading, 'release.0.44', 1) == 'release.0.44-example-1'
|
|
1160
|
+
|
|
1161
|
+
|
|
1143
1162
|
def test_qualified_name_for_fallback_no_desc_ancestor():
|
|
1144
1163
|
section = nodes.section()
|
|
1145
1164
|
heading = nodes.rubric()
|
|
@@ -1613,6 +1632,18 @@ def test_build_notebook_valid_nbformat():
|
|
|
1613
1632
|
nbformat.validate(node) # raises if invalid
|
|
1614
1633
|
|
|
1615
1634
|
|
|
1635
|
+
@pytest.mark.parametrize(
|
|
1636
|
+
('name', 'fmt', 'expected'),
|
|
1637
|
+
[
|
|
1638
|
+
('ImageDataFilters.resample', 'marimo', 'ImageDataFilters_resample_marimo.py'),
|
|
1639
|
+
('plot_vtk9.4', 'python', 'plot_vtk9_4.py'),
|
|
1640
|
+
('', 'jupyter', 'example.ipynb'),
|
|
1641
|
+
],
|
|
1642
|
+
)
|
|
1643
|
+
def test_download_filename(name: str, fmt: str, expected: str):
|
|
1644
|
+
assert seac._download_filename(name, fmt) == expected
|
|
1645
|
+
|
|
1646
|
+
|
|
1616
1647
|
# ---------------------------------------------------------------------------
|
|
1617
1648
|
# _write_jupyter_notebook
|
|
1618
1649
|
# ---------------------------------------------------------------------------
|
|
@@ -3052,6 +3083,31 @@ def test_script_metadata_marimo(tmp_path: Path):
|
|
|
3052
3083
|
)
|
|
3053
3084
|
assert source.startswith(header)
|
|
3054
3085
|
assert 'Open this notebook' not in source.removeprefix(header)
|
|
3086
|
+
assert source.index('mo.md(') < source.index('import marimo as mo')
|
|
3087
|
+
|
|
3088
|
+
|
|
3089
|
+
@pytest.mark.parametrize(('fmt', 'listed'), [('jupyter', False), ('marimo', True)])
|
|
3090
|
+
def test_script_metadata_drops_covered_except_marimo(
|
|
3091
|
+
tmp_path: Path, fake_requires, fmt: str, listed: bool
|
|
3092
|
+
):
|
|
3093
|
+
fake_requires({'ipywidgets': ['numpy'], 'marimo': ['numpy']})
|
|
3094
|
+
_name, source = _write_with_metadata(tmp_path, [fmt])
|
|
3095
|
+
header = source.split('///', 2)[1] if fmt == 'marimo' else json.loads(source)['cells'][0]
|
|
3096
|
+
assert ('numpy' in str(header)) is listed
|
|
3097
|
+
|
|
3098
|
+
|
|
3099
|
+
@pytest.mark.parametrize(
|
|
3100
|
+
('codes', 'expected'),
|
|
3101
|
+
[
|
|
3102
|
+
(['import marimo as mo', 'mo.md("# T")', 'x = 1'], [1, 0, 2]),
|
|
3103
|
+
(['import marimo as mo', 'x = 1'], [0, 1]),
|
|
3104
|
+
(['mo.md("# T")', 'import marimo as mo'], [0, 1]),
|
|
3105
|
+
(['import marimo as mo'], [0]),
|
|
3106
|
+
],
|
|
3107
|
+
)
|
|
3108
|
+
def test_title_first(codes: list[str], expected: list[int]):
|
|
3109
|
+
cells = [SimpleNamespace(code=code) for code in codes]
|
|
3110
|
+
assert seac._title_first(cells) == [cells[i] for i in expected]
|
|
3055
3111
|
|
|
3056
3112
|
|
|
3057
3113
|
def test_process_doctree_script_metadata_enabled(tmp_path: Path):
|
|
@@ -3203,3 +3259,48 @@ def test_script_metadata_documented_project_not_imported(
|
|
|
3203
3259
|
source = _python_source_for(tmp_path, 'NumPy', '.. rubric:: Examples\n\n>>> import docutils')
|
|
3204
3260
|
assert '# requires-python = "<3.15"\n' in source
|
|
3205
3261
|
assert 'numpy' not in source
|
|
3262
|
+
|
|
3263
|
+
|
|
3264
|
+
@pytest.fixture
|
|
3265
|
+
def fake_requires(monkeypatch: pytest.MonkeyPatch):
|
|
3266
|
+
"""Serve installed requirements from a dict."""
|
|
3267
|
+
|
|
3268
|
+
def install(requires: dict[str, list[str]]) -> None:
|
|
3269
|
+
def fake(name: str) -> list[str]:
|
|
3270
|
+
if name not in requires:
|
|
3271
|
+
raise seac.importlib.metadata.PackageNotFoundError(name)
|
|
3272
|
+
return requires[name]
|
|
3273
|
+
|
|
3274
|
+
monkeypatch.setattr(seac.importlib.metadata, 'requires', fake)
|
|
3275
|
+
|
|
3276
|
+
seac._installed_requirements.cache_clear()
|
|
3277
|
+
yield install
|
|
3278
|
+
seac._installed_requirements.cache_clear()
|
|
3279
|
+
|
|
3280
|
+
|
|
3281
|
+
def test_drop_covered_follows_extras_transitively(fake_requires):
|
|
3282
|
+
fake_requires(
|
|
3283
|
+
{
|
|
3284
|
+
'pyvista': ['numpy>=1.21', 'trame-pyvista[marimo]>=0.3; extra == "marimo"'],
|
|
3285
|
+
'trame-pyvista': ['marimo; extra == "marimo"'],
|
|
3286
|
+
}
|
|
3287
|
+
)
|
|
3288
|
+
deps = ['marimo', 'numpy', 'pyvista[marimo]', 'scipy']
|
|
3289
|
+
assert seac._drop_covered(deps) == ['pyvista[marimo]', 'scipy']
|
|
3290
|
+
assert seac._drop_covered(['marimo', 'numpy', 'pyvista']) == ['marimo', 'pyvista']
|
|
3291
|
+
|
|
3292
|
+
|
|
3293
|
+
def test_drop_covered_keeps_pinned_platform_specific_and_cycles(fake_requires):
|
|
3294
|
+
fake_requires(
|
|
3295
|
+
{
|
|
3296
|
+
'pyvista': ['numpy', 'pooch; sys_platform == "linux"', 'cycle'],
|
|
3297
|
+
'cycle': ['pyvista'],
|
|
3298
|
+
}
|
|
3299
|
+
)
|
|
3300
|
+
deps = ['cycle', 'numpy>=2', 'pooch', 'pyvista']
|
|
3301
|
+
assert seac._drop_covered(deps) == deps
|
|
3302
|
+
|
|
3303
|
+
|
|
3304
|
+
def test_drop_covered_without_metadata_keeps_everything(fake_requires):
|
|
3305
|
+
fake_requires({})
|
|
3306
|
+
assert seac._drop_covered(['numpy', 'pyvista']) == ['numpy', 'pyvista']
|
|
@@ -228,7 +228,7 @@ def test_table_becomes_an_rst_simple_table_in_python(built: tuple[Path, list[Pat
|
|
|
228
228
|
|
|
229
229
|
def test_table_becomes_a_markdown_table_in_jupyter(built_jupyter_notebooks: list[Path]):
|
|
230
230
|
"""The same table is a Markdown pipe table in a notebook's markdown cell."""
|
|
231
|
-
nb_path = next(p for p in built_jupyter_notebooks if p.stem == '
|
|
231
|
+
nb_path = next(p for p in built_jupyter_notebooks if p.stem == 'case_table')
|
|
232
232
|
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
233
233
|
lines = [
|
|
234
234
|
line.rstrip()
|
|
@@ -246,9 +246,7 @@ def test_table_becomes_a_markdown_table_in_jupyter(built_jupyter_notebooks: list
|
|
|
246
246
|
|
|
247
247
|
def test_header_less_table_gets_a_blank_markdown_header(built_jupyter_notebooks: list[Path]):
|
|
248
248
|
"""A Markdown pipe table needs a header row, so one is left blank."""
|
|
249
|
-
nb_path = next(
|
|
250
|
-
p for p in built_jupyter_notebooks if p.stem == 'docstring_cases_case_table_without_header'
|
|
251
|
-
)
|
|
249
|
+
nb_path = next(p for p in built_jupyter_notebooks if p.stem == 'case_table_without_header')
|
|
252
250
|
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
253
251
|
lines = [
|
|
254
252
|
line.rstrip()
|
|
@@ -301,7 +299,7 @@ def test_header_format(built: tuple[Path, list[Path]]):
|
|
|
301
299
|
|
|
302
300
|
def test_header_format_jupyter_uses_atx(built_jupyter_notebooks: list[Path]):
|
|
303
301
|
"""The same header, in .ipynb, is a single ATX level-1 line -- no underline."""
|
|
304
|
-
nb_path = next(p for p in built_jupyter_notebooks if p.stem == '
|
|
302
|
+
nb_path = next(p for p in built_jupyter_notebooks if p.stem == 'case_note')
|
|
305
303
|
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
306
304
|
header_cell = notebook['cells'][0]
|
|
307
305
|
lines = [line.rstrip() for line in header_cell['source']]
|
|
@@ -451,9 +449,7 @@ def test_jupyter_notebook_code_cells_match_python_code(
|
|
|
451
449
|
line for line in python_src.splitlines() if line and not line.startswith('#')
|
|
452
450
|
]
|
|
453
451
|
|
|
454
|
-
notebook_path = next(
|
|
455
|
-
p for p in built_jupyter_notebooks if p.stem == 'docstring_cases_case_combined'
|
|
456
|
-
)
|
|
452
|
+
notebook_path = next(p for p in built_jupyter_notebooks if p.stem == 'case_combined')
|
|
457
453
|
notebook = json.loads(notebook_path.read_text(encoding='utf-8'))
|
|
458
454
|
code_cells = [c for c in notebook['cells'] if c['cell_type'] == 'code']
|
|
459
455
|
notebook_code_lines = [
|
|
@@ -520,11 +516,7 @@ def test_base_url_unset_no_links_anywhere(
|
|
|
520
516
|
python_src = _read(built[1], 'case_regular_xref_for_base_url')
|
|
521
517
|
assert 'http' not in python_src
|
|
522
518
|
|
|
523
|
-
nb_path = next(
|
|
524
|
-
p
|
|
525
|
-
for p in built_jupyter_notebooks
|
|
526
|
-
if p.stem == 'docstring_cases_case_regular_xref_for_base_url'
|
|
527
|
-
)
|
|
519
|
+
nb_path = next(p for p in built_jupyter_notebooks if p.stem == 'case_regular_xref_for_base_url')
|
|
528
520
|
nb_text = nb_path.read_text(encoding='utf-8')
|
|
529
521
|
assert 'http' not in nb_text
|
|
530
522
|
assert '](' not in nb_text # no markdown links
|
|
@@ -562,7 +554,7 @@ def test_base_url_set_resolves_links(tmp_path: Path):
|
|
|
562
554
|
assert '`docstring_cases.Sample`' in regular_python
|
|
563
555
|
|
|
564
556
|
regular_nb = next(
|
|
565
|
-
p for p in jupyter_files if p.stem == '
|
|
557
|
+
p for p in jupyter_files if p.stem == 'case_regular_xref_for_base_url'
|
|
566
558
|
).read_text(encoding='utf-8')
|
|
567
559
|
assert '[`docstring_cases.Sample`](https://docs.example.com/' in regular_nb
|
|
568
560
|
|
|
@@ -601,7 +593,7 @@ def test_markdown_cells_use_hard_line_breaks(built_jupyter_notebooks: list[Path]
|
|
|
601
593
|
with no blank line between -- not the header cell, which is a single
|
|
602
594
|
ATX heading line with nothing else to connect to.
|
|
603
595
|
"""
|
|
604
|
-
nb_path = next(p for p in built_jupyter_notebooks if p.stem == '
|
|
596
|
+
nb_path = next(p for p in built_jupyter_notebooks if p.stem == 'case_note')
|
|
605
597
|
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
606
598
|
note_cell = next(
|
|
607
599
|
c
|
|
@@ -632,9 +624,7 @@ def test_html_baseurl_missing_scheme_disables_links_without_failing_build(tmp_pa
|
|
|
632
624
|
)
|
|
633
625
|
assert returncode == 0, f'sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n'
|
|
634
626
|
|
|
635
|
-
nb_path = next(
|
|
636
|
-
(html_dir / '_downloads').rglob('docstring_cases_case_regular_xref_for_base_url.ipynb')
|
|
637
|
-
)
|
|
627
|
+
nb_path = next((html_dir / '_downloads').rglob('case_regular_xref_for_base_url.ipynb'))
|
|
638
628
|
nb_text = nb_path.read_text(encoding='utf-8')
|
|
639
629
|
assert '](' not in nb_text # no markdown links -- same as html_baseurl unset entirely
|
|
640
630
|
|
|
@@ -653,9 +643,7 @@ def test_base_url_missing_trailing_slash_still_resolves_subpath(tmp_path: Path):
|
|
|
653
643
|
)
|
|
654
644
|
assert returncode == 0, f'sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n'
|
|
655
645
|
|
|
656
|
-
nb_path = next(
|
|
657
|
-
(html_dir / '_downloads').rglob('docstring_cases_case_regular_xref_for_base_url.ipynb')
|
|
658
|
-
)
|
|
646
|
+
nb_path = next((html_dir / '_downloads').rglob('case_regular_xref_for_base_url.ipynb'))
|
|
659
647
|
nb_text = nb_path.read_text(encoding='utf-8')
|
|
660
648
|
assert 'https://docs.example.com/en/stable/docstring_cases.html' in nb_text
|
|
661
649
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/gallery_fixture/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/gallery_fixture/index.rst
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/heading_level_fixture/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_gallery_downloads.py
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/test_heading_level_reuse.py
RENAMED
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/tinypages/docstring_cases.py
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.8.1 → sphinx_examples_as_code-0.8.3}/tests/tinypages/docstring_cases.rst
RENAMED
|
File without changes
|
|
File without changes
|