sphinx-examples-as-code 0.8.2__tar.gz → 0.9.0__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.2 → sphinx_examples_as_code-0.9.0}/.github/workflows/ci.yml +1 -1
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/PKG-INFO +15 -8
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/README.md +13 -5
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/pyproject.toml +1 -2
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code/__init__.py +73 -6
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code/_version.py +3 -3
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code.egg-info/PKG-INFO +15 -8
- sphinx_examples_as_code-0.9.0/sphinx_examples_as_code.egg-info/scm_version.json +8 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_gallery_downloads.py +49 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_sphinx_examples_as_code.py +69 -0
- sphinx_examples_as_code-0.8.2/sphinx_examples_as_code.egg-info/scm_version.json +0 -8
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/.github/dependabot.yml +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/.github/release.yml +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/.gitignore +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/.pre-commit-config.yaml +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/LICENSE +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/setup.cfg +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code.egg-info/SOURCES.txt +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code.egg-info/dependency_links.txt +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code.egg-info/requires.txt +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code.egg-info/scm_file_list.json +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code.egg-info/top_level.txt +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/gallery_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/gallery_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/gallery_fixture/examples/plot_minimal.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/gallery_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/heading_level_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/heading_level_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/heading_level_fixture/examples/plot_reused_level.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/heading_level_fixture/examples/plot_three_levels.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/heading_level_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/single_function_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/single_function_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/single_function_fixture/mymodule.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_heading_level_reuse.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_single_function_page.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_tinypages.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/tinypages/conf.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/tinypages/docstring_cases.py +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/tinypages/docstring_cases.rst +0 -0
- {sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/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.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -151,8 +150,7 @@ imports, plus any `dependencies` set for its format, and says how to run it:
|
|
|
151
150
|
needed, so the command works in bash, zsh, PowerShell and cmd. An argument containing
|
|
152
151
|
`"`, `$`, a backtick, `\`, `%` or a `!` not followed by `=` is single-quoted instead,
|
|
153
152
|
which cmd does not understand.
|
|
154
|
-
- marimo notebooks start with the same `# /// script` block, which also lists `marimo
|
|
155
|
-
unless another dependency installs it,
|
|
153
|
+
- marimo notebooks start with the same `# /// script` block, which also lists `marimo`,
|
|
156
154
|
followed by `# Open this notebook with: uvx marimo edit --sandbox <name>_marimo.py`.
|
|
157
155
|
- Each run command is followed by a line linking to
|
|
158
156
|
[uv's installation guide](https://docs.astral.sh/uv/getting-started/installation/).
|
|
@@ -161,9 +159,18 @@ Imports are read from the code, not run. Each imported top-level module maps to
|
|
|
161
159
|
project that provides it in the environment building the docs; standard-library
|
|
162
160
|
modules, relative imports and modules no single installed project provides are left
|
|
163
161
|
out. Projects the code needs but never imports, such as an optional backend, go in
|
|
164
|
-
`dependencies
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
`dependencies`, or for a single page in an `examples-as-code-dependencies` directive anywhere on it.
|
|
163
|
+
It takes PEP 508 requirements separated by spaces or commas, adds them to every download from
|
|
164
|
+
that page, and renders nothing:
|
|
165
|
+
|
|
166
|
+
```rst
|
|
167
|
+
.. examples-as-code-dependencies:: sympy imageio-ffmpeg>=0.4
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
In a Sphinx-Gallery example, put it in the example's docstring. In Python scripts and Jupyter notebooks, a dependency with no version or extras of
|
|
171
|
+
its own is left out when another listed one already installs it on every platform, e.g. `numpy`
|
|
172
|
+
next to `pyvista`. marimo notebooks list every dependency, since marimo's sandbox adds back any
|
|
173
|
+
it does not find by name.
|
|
167
174
|
|
|
168
175
|
The project named by Sphinx's own `project` setting (e.g. `project = 'PyVista'`) gets
|
|
169
176
|
extra treatment when an example imports it and it is installed: its dependency becomes
|
|
@@ -119,8 +119,7 @@ imports, plus any `dependencies` set for its format, and says how to run it:
|
|
|
119
119
|
needed, so the command works in bash, zsh, PowerShell and cmd. An argument containing
|
|
120
120
|
`"`, `$`, a backtick, `\`, `%` or a `!` not followed by `=` is single-quoted instead,
|
|
121
121
|
which cmd does not understand.
|
|
122
|
-
- marimo notebooks start with the same `# /// script` block, which also lists `marimo
|
|
123
|
-
unless another dependency installs it,
|
|
122
|
+
- marimo notebooks start with the same `# /// script` block, which also lists `marimo`,
|
|
124
123
|
followed by `# Open this notebook with: uvx marimo edit --sandbox <name>_marimo.py`.
|
|
125
124
|
- Each run command is followed by a line linking to
|
|
126
125
|
[uv's installation guide](https://docs.astral.sh/uv/getting-started/installation/).
|
|
@@ -129,9 +128,18 @@ Imports are read from the code, not run. Each imported top-level module maps to
|
|
|
129
128
|
project that provides it in the environment building the docs; standard-library
|
|
130
129
|
modules, relative imports and modules no single installed project provides are left
|
|
131
130
|
out. Projects the code needs but never imports, such as an optional backend, go in
|
|
132
|
-
`dependencies
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
`dependencies`, or for a single page in an `examples-as-code-dependencies` directive anywhere on it.
|
|
132
|
+
It takes PEP 508 requirements separated by spaces or commas, adds them to every download from
|
|
133
|
+
that page, and renders nothing:
|
|
134
|
+
|
|
135
|
+
```rst
|
|
136
|
+
.. examples-as-code-dependencies:: sympy imageio-ffmpeg>=0.4
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
In a Sphinx-Gallery example, put it in the example's docstring. In Python scripts and Jupyter notebooks, a dependency with no version or extras of
|
|
140
|
+
its own is left out when another listed one already installs it on every platform, e.g. `numpy`
|
|
141
|
+
next to `pyvista`. marimo notebooks list every dependency, since marimo's sandbox adds back any
|
|
142
|
+
it does not find by name.
|
|
135
143
|
|
|
136
144
|
The project named by Sphinx's own `project` setting (e.g. `project = 'PyVista'`) gets
|
|
137
145
|
extra treatment when an example imports it and it is installed: its dependency becomes
|
|
@@ -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.2 → sphinx_examples_as_code-0.9.0}/sphinx_examples_as_code/__init__.py
RENAMED
|
@@ -23,6 +23,7 @@ from urllib.parse import urlsplit
|
|
|
23
23
|
|
|
24
24
|
from docutils import nodes
|
|
25
25
|
from docutils.core import publish_doctree
|
|
26
|
+
from packaging.requirements import InvalidRequirement
|
|
26
27
|
from packaging.requirements import Requirement
|
|
27
28
|
from packaging.specifiers import InvalidSpecifier
|
|
28
29
|
from packaging.specifiers import SpecifierSet
|
|
@@ -32,6 +33,7 @@ from packaging.version import Version
|
|
|
32
33
|
from sphinx import addnodes
|
|
33
34
|
from sphinx.errors import ConfigError
|
|
34
35
|
from sphinx.util import logging
|
|
36
|
+
from sphinx.util.docutils import SphinxDirective
|
|
35
37
|
|
|
36
38
|
from ._version import __version__
|
|
37
39
|
|
|
@@ -42,6 +44,7 @@ if TYPE_CHECKING:
|
|
|
42
44
|
|
|
43
45
|
from sphinx.application import Sphinx
|
|
44
46
|
from sphinx.config import Config
|
|
47
|
+
from sphinx.environment import BuildEnvironment
|
|
45
48
|
|
|
46
49
|
# Node types marking the start of another section - bound the end of an
|
|
47
50
|
# Examples span. Includes desc/index -- numpydoc renders a class's members
|
|
@@ -336,14 +339,19 @@ def _join_segments(segments: list[Segment]) -> list[str]:
|
|
|
336
339
|
|
|
337
340
|
|
|
338
341
|
def _convert_doctest_block(node: nodes.doctest_block) -> list[Segment]:
|
|
339
|
-
"""Convert a doctest block
|
|
342
|
+
"""Convert a doctest block; see ``_convert_doctest_source``."""
|
|
343
|
+
return _convert_doctest_source(node.astext())
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _convert_doctest_source(source: str) -> list[Segment]:
|
|
347
|
+
"""Convert doctest text, stripping ``>>> ``/``... `` prompts.
|
|
340
348
|
|
|
341
349
|
Non-prompted, non-blank lines are doctest *output* -- dropped entirely,
|
|
342
350
|
not kept as comments.
|
|
343
351
|
"""
|
|
344
352
|
lines: list[str] = []
|
|
345
353
|
has_code = False
|
|
346
|
-
for line in
|
|
354
|
+
for line in source.splitlines():
|
|
347
355
|
if line.startswith('>>> ') or line == '>>>':
|
|
348
356
|
lines.append(_clean_code_comment(line[4:]))
|
|
349
357
|
has_code = True
|
|
@@ -382,6 +390,8 @@ def _convert_literal_block(node: nodes.Element) -> list[Segment]:
|
|
|
382
390
|
language, source = node.get('language', ''), node.astext()
|
|
383
391
|
# Case-insensitive: sphinx-gallery emits ``.. code-block:: Python`` (capitalized).
|
|
384
392
|
if language.lower() in _PYTHON_LANGUAGES:
|
|
393
|
+
if source.lstrip().startswith('>>>'):
|
|
394
|
+
return _convert_doctest_source(source)
|
|
385
395
|
lines = [_clean_code_comment(line) for line in source.splitlines()]
|
|
386
396
|
while lines and not lines[-1].strip():
|
|
387
397
|
lines.pop()
|
|
@@ -1006,20 +1016,29 @@ def _drop_future_imports(source: list[str]) -> list[str]:
|
|
|
1006
1016
|
return [line for i, line in enumerate(source, start=1) if i not in dropped]
|
|
1007
1017
|
|
|
1008
1018
|
|
|
1019
|
+
# Stands in for a line's leading % so marimo does not read it as an IPython magic.
|
|
1020
|
+
_PERCENT_MARK = '\ue000'
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def _mark_percent_lines(source: list[str]) -> list[str]:
|
|
1024
|
+
"""Replace the ``%`` that starts a line with ``_PERCENT_MARK``."""
|
|
1025
|
+
return [_PERCENT_MARK + line[1:] if line.startswith('%') else line for line in source]
|
|
1026
|
+
|
|
1027
|
+
|
|
1009
1028
|
def _write_marimo_notebook(app: Sphinx, name: str, notebook: dict, header: str = '') -> str:
|
|
1010
1029
|
"""Convert a generated Jupyter notebook with marimo's own converter and write it."""
|
|
1011
1030
|
from marimo._convert.converters import MarimoConvert
|
|
1012
1031
|
|
|
1013
1032
|
# A __future__ import inside a marimo cell is a SyntaxError when run as a script.
|
|
1014
1033
|
cells = [
|
|
1015
|
-
{**cell, 'source': _drop_future_imports(cell['source'])}
|
|
1034
|
+
{**cell, 'source': _mark_percent_lines(_drop_future_imports(cell['source']))}
|
|
1016
1035
|
if cell['cell_type'] == 'code'
|
|
1017
1036
|
else cell
|
|
1018
1037
|
for cell in notebook['cells']
|
|
1019
1038
|
]
|
|
1020
1039
|
ir = MarimoConvert.from_ipynb(json.dumps({**notebook, 'cells': cells})).to_ir()
|
|
1021
1040
|
source = MarimoConvert.from_ir(replace(ir, cells=_title_first(ir.cells))).to_py()
|
|
1022
|
-
return _write_download_file(app, name, _FMT_MARIMO, header + source)
|
|
1041
|
+
return _write_download_file(app, name, _FMT_MARIMO, header + source.replace(_PERCENT_MARK, '%'))
|
|
1023
1042
|
|
|
1024
1043
|
|
|
1025
1044
|
@functools.cache
|
|
@@ -1351,10 +1370,13 @@ def _build_download_entries(
|
|
|
1351
1370
|
if script_metadata is not None:
|
|
1352
1371
|
implied = {_FMT_MARIMO: ['marimo']}
|
|
1353
1372
|
extra = script_metadata['dependencies']
|
|
1373
|
+
page = script_metadata.get('page_dependencies', [])
|
|
1354
1374
|
project = _documented_project(script_metadata['project'])
|
|
1355
1375
|
pins = {project.name: project.pin} if project and project.pin else {}
|
|
1356
1376
|
deps = {
|
|
1357
|
-
fmt: _script_dependencies(
|
|
1377
|
+
fmt: _script_dependencies(
|
|
1378
|
+
source, [*implied.get(fmt, []), *extra.get(fmt, []), *page], pins
|
|
1379
|
+
)
|
|
1358
1380
|
for fmt in formats
|
|
1359
1381
|
}
|
|
1360
1382
|
|
|
@@ -1378,7 +1400,11 @@ def _build_download_entries(
|
|
|
1378
1400
|
cap = _python_upper_bound(deps[fmt], project.name if project else None)
|
|
1379
1401
|
return _python_range(floor, cap)
|
|
1380
1402
|
|
|
1381
|
-
|
|
1403
|
+
# marimo's sandbox adds back any import or marimo itself that its header does not name.
|
|
1404
|
+
shown = {
|
|
1405
|
+
fmt: fmt_deps if fmt == _FMT_MARIMO else _drop_covered(fmt_deps)
|
|
1406
|
+
for fmt, fmt_deps in deps.items()
|
|
1407
|
+
}
|
|
1382
1408
|
|
|
1383
1409
|
def header_for(fmt: str, run_line: str) -> str:
|
|
1384
1410
|
if not deps:
|
|
@@ -1591,6 +1617,7 @@ def _process_doctree(app: Sphinx, doctree: nodes.document, docname: str) -> None
|
|
|
1591
1617
|
'dependencies': conf['dependencies'],
|
|
1592
1618
|
'requires_python': conf['requires_python'],
|
|
1593
1619
|
'project': app.config.project,
|
|
1620
|
+
'page_dependencies': _page_dependencies(app.env).get(docname, []),
|
|
1594
1621
|
}
|
|
1595
1622
|
if conf['script_metadata']
|
|
1596
1623
|
else None
|
|
@@ -1833,8 +1860,48 @@ def _disable_gallery_notebooks(_app: Sphinx, config: Config) -> None:
|
|
|
1833
1860
|
config.sphinx_gallery_conf = {**gallery_conf, **overrides}
|
|
1834
1861
|
|
|
1835
1862
|
|
|
1863
|
+
def _page_dependencies(env: BuildEnvironment) -> dict[str, list[str]]:
|
|
1864
|
+
"""Return the requirements each page added with ``examples-as-code-dependencies``."""
|
|
1865
|
+
return vars(env).setdefault('_examples_as_code_dependencies', {})
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
class _DependenciesDirective(SphinxDirective):
|
|
1869
|
+
"""Add requirements to every download from this page; renders nothing."""
|
|
1870
|
+
|
|
1871
|
+
required_arguments = 1
|
|
1872
|
+
final_argument_whitespace = True
|
|
1873
|
+
|
|
1874
|
+
def run(self) -> list[nodes.Node]:
|
|
1875
|
+
"""Record the requirements for the current page."""
|
|
1876
|
+
requirements = self.arguments[0].replace(',', ' ').split()
|
|
1877
|
+
for requirement in requirements:
|
|
1878
|
+
try:
|
|
1879
|
+
Requirement(requirement)
|
|
1880
|
+
except InvalidRequirement as exc:
|
|
1881
|
+
msg = f'Invalid requirement {requirement!r}: {exc}'
|
|
1882
|
+
raise self.error(msg) from None
|
|
1883
|
+
_page_dependencies(self.env).setdefault(self.env.docname, []).extend(requirements)
|
|
1884
|
+
return []
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
def _purge_page_dependencies(_app: Sphinx, env: BuildEnvironment, docname: str) -> None:
|
|
1888
|
+
"""Forget a page's requirements before it is read again."""
|
|
1889
|
+
_page_dependencies(env).pop(docname, None)
|
|
1890
|
+
|
|
1891
|
+
|
|
1892
|
+
def _merge_page_dependencies(
|
|
1893
|
+
_app: Sphinx, env: BuildEnvironment, docnames: set[str], other: BuildEnvironment
|
|
1894
|
+
) -> None:
|
|
1895
|
+
"""Copy the requirements a parallel reader recorded into the main environment."""
|
|
1896
|
+
theirs = _page_dependencies(other)
|
|
1897
|
+
_page_dependencies(env).update((name, theirs[name]) for name in docnames if name in theirs)
|
|
1898
|
+
|
|
1899
|
+
|
|
1836
1900
|
def setup(app: Sphinx) -> dict: # numpydoc ignore=RT01
|
|
1837
1901
|
"""Register the extension."""
|
|
1902
|
+
app.add_directive('examples-as-code-dependencies', _DependenciesDirective)
|
|
1903
|
+
app.connect('env-purge-doc', _purge_page_dependencies)
|
|
1904
|
+
app.connect('env-merge-info', _merge_page_dependencies)
|
|
1838
1905
|
app.connect('doctree-resolved', _process_doctree)
|
|
1839
1906
|
app.connect('config-inited', _finalize_conf)
|
|
1840
1907
|
# sphinx-gallery fills its own defaults at priority 10.
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.9.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 9, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g71df6bcfb'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-examples-as-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -151,8 +150,7 @@ imports, plus any `dependencies` set for its format, and says how to run it:
|
|
|
151
150
|
needed, so the command works in bash, zsh, PowerShell and cmd. An argument containing
|
|
152
151
|
`"`, `$`, a backtick, `\`, `%` or a `!` not followed by `=` is single-quoted instead,
|
|
153
152
|
which cmd does not understand.
|
|
154
|
-
- marimo notebooks start with the same `# /// script` block, which also lists `marimo
|
|
155
|
-
unless another dependency installs it,
|
|
153
|
+
- marimo notebooks start with the same `# /// script` block, which also lists `marimo`,
|
|
156
154
|
followed by `# Open this notebook with: uvx marimo edit --sandbox <name>_marimo.py`.
|
|
157
155
|
- Each run command is followed by a line linking to
|
|
158
156
|
[uv's installation guide](https://docs.astral.sh/uv/getting-started/installation/).
|
|
@@ -161,9 +159,18 @@ Imports are read from the code, not run. Each imported top-level module maps to
|
|
|
161
159
|
project that provides it in the environment building the docs; standard-library
|
|
162
160
|
modules, relative imports and modules no single installed project provides are left
|
|
163
161
|
out. Projects the code needs but never imports, such as an optional backend, go in
|
|
164
|
-
`dependencies
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
`dependencies`, or for a single page in an `examples-as-code-dependencies` directive anywhere on it.
|
|
163
|
+
It takes PEP 508 requirements separated by spaces or commas, adds them to every download from
|
|
164
|
+
that page, and renders nothing:
|
|
165
|
+
|
|
166
|
+
```rst
|
|
167
|
+
.. examples-as-code-dependencies:: sympy imageio-ffmpeg>=0.4
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
In a Sphinx-Gallery example, put it in the example's docstring. In Python scripts and Jupyter notebooks, a dependency with no version or extras of
|
|
171
|
+
its own is left out when another listed one already installs it on every platform, e.g. `numpy`
|
|
172
|
+
next to `pyvista`. marimo notebooks list every dependency, since marimo's sandbox adds back any
|
|
173
|
+
it does not find by name.
|
|
167
174
|
|
|
168
175
|
The project named by Sphinx's own `project` setting (e.g. `project = 'PyVista'`) gets
|
|
169
176
|
extra treatment when an example imports it and it is installed: its dependency becomes
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_gallery_downloads.py
RENAMED
|
@@ -334,3 +334,52 @@ def test_gallery_downloads_enabled_warns_about_notebook_settings(tmp_path: Path)
|
|
|
334
334
|
assert "sphinx_gallery_conf key(s) 'pypandoc', 'first_notebook_cell' have no effect" in err
|
|
335
335
|
assert "'pypandoc' not available" not in err
|
|
336
336
|
assert not list((src_dir / 'auto_examples').rglob('*.ipynb'))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _build_with_dependencies(tmp_path: Path, requirements: str) -> tuple[int, str, str, Path]:
|
|
340
|
+
"""Build the fixture with an ``examples-as-code-dependencies`` directive in its example."""
|
|
341
|
+
src_dir = tmp_path / 'src'
|
|
342
|
+
shutil.copytree(FIXTURE_DIR, src_dir)
|
|
343
|
+
example = src_dir / 'examples' / 'plot_minimal.py'
|
|
344
|
+
source = example.read_text(encoding='utf-8')
|
|
345
|
+
marker = '==========================\n'
|
|
346
|
+
directive = f'{marker}\n.. examples-as-code-dependencies:: {requirements}\n'
|
|
347
|
+
example.write_text(source.replace(marker, directive, 1), encoding='utf-8')
|
|
348
|
+
html_dir = tmp_path / 'html'
|
|
349
|
+
sphinx_args = (
|
|
350
|
+
'-D',
|
|
351
|
+
'sphinx_examples_as_code_conf.gallery_downloads=1',
|
|
352
|
+
'-D',
|
|
353
|
+
'sphinx_examples_as_code_conf.script_metadata=1',
|
|
354
|
+
'-D',
|
|
355
|
+
'sphinx_examples_as_code_conf.formats=python,jupyter,marimo',
|
|
356
|
+
)
|
|
357
|
+
returncode, out, err = _run_sphinx_build(
|
|
358
|
+
_sphinx_build_cmd(src_dir, html_dir, tmp_path / 'doctrees', sphinx_args)
|
|
359
|
+
)
|
|
360
|
+
return returncode, out, err, html_dir
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_dependencies_directive_adds_to_every_download(tmp_path: Path):
|
|
364
|
+
"""``examples-as-code-dependencies`` lists its requirements in each download's header."""
|
|
365
|
+
returncode, out, err, html_dir = _build_with_dependencies(tmp_path, 'sympy tqdm>=4')
|
|
366
|
+
assert returncode == 0, f'sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n'
|
|
367
|
+
downloads = [
|
|
368
|
+
path
|
|
369
|
+
for path in (html_dir / '_downloads').rglob('plot_minimal*')
|
|
370
|
+
if path.suffix == '.ipynb'
|
|
371
|
+
or (path.suffix == '.py' and path.read_text(encoding='utf-8').startswith('# /// script'))
|
|
372
|
+
]
|
|
373
|
+
assert sorted(path.suffix for path in downloads) == ['.ipynb', '.py', '.py']
|
|
374
|
+
for path in downloads:
|
|
375
|
+
text = path.read_text(encoding='utf-8')
|
|
376
|
+
assert 'sympy' in text
|
|
377
|
+
assert 'tqdm>=4' in text
|
|
378
|
+
assert 'examples-as-code-dependencies' not in text
|
|
379
|
+
html = (html_dir / 'auto_examples' / 'plot_minimal.html').read_text(encoding='utf-8')
|
|
380
|
+
assert 'examples-as-code-dependencies' not in html
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def test_dependencies_directive_rejects_invalid_requirement(tmp_path: Path):
|
|
384
|
+
_returncode, _out, err, _html_dir = _build_with_dependencies(tmp_path, 'not!valid')
|
|
385
|
+
assert "Invalid requirement 'not!valid'" in err
|
|
@@ -15,6 +15,7 @@ from unittest.mock import Mock
|
|
|
15
15
|
|
|
16
16
|
from docutils import nodes
|
|
17
17
|
from docutils.core import publish_doctree
|
|
18
|
+
from docutils.parsers.rst import DirectiveError
|
|
18
19
|
import pytest
|
|
19
20
|
from sphinx import addnodes
|
|
20
21
|
from sphinx.errors import ConfigError
|
|
@@ -352,6 +353,13 @@ def test_convert_doctest_block_trailing_blank_trimmed():
|
|
|
352
353
|
# ---------------------------------------------------------------------------
|
|
353
354
|
|
|
354
355
|
|
|
356
|
+
@pytest.mark.parametrize('language', ['python', 'py'])
|
|
357
|
+
def test_convert_literal_block_python_doctest_style(language: str):
|
|
358
|
+
node = nodes.literal_block('', '>>> import math\n>>> math.floor(8.5)\n8')
|
|
359
|
+
node['language'] = language
|
|
360
|
+
assert seac._convert_literal_block(node) == [('code', ['import math', 'math.floor(8.5)'])]
|
|
361
|
+
|
|
362
|
+
|
|
355
363
|
def test_convert_literal_block_python():
|
|
356
364
|
# Sphinx's own code-block directive sets a ``language`` attribute
|
|
357
365
|
# (unlike plain docutils', which uses CSS classes instead), so this
|
|
@@ -3086,6 +3094,31 @@ def test_script_metadata_marimo(tmp_path: Path):
|
|
|
3086
3094
|
assert source.index('mo.md(') < source.index('import marimo as mo')
|
|
3087
3095
|
|
|
3088
3096
|
|
|
3097
|
+
@pytest.mark.parametrize(('fmt', 'listed'), [('jupyter', False), ('marimo', True)])
|
|
3098
|
+
def test_script_metadata_drops_covered_except_marimo(
|
|
3099
|
+
tmp_path: Path, fake_requires, fmt: str, listed: bool
|
|
3100
|
+
):
|
|
3101
|
+
fake_requires({'ipywidgets': ['numpy'], 'marimo': ['numpy']})
|
|
3102
|
+
_name, source = _write_with_metadata(tmp_path, [fmt])
|
|
3103
|
+
header = source.split('///', 2)[1] if fmt == 'marimo' else json.loads(source)['cells'][0]
|
|
3104
|
+
assert ('numpy' in str(header)) is listed
|
|
3105
|
+
|
|
3106
|
+
|
|
3107
|
+
def test_write_marimo_notebook_keeps_percent_lines(tmp_path: Path):
|
|
3108
|
+
code = ['art = """\n', '% %\n', '%% %\n', '"""\n', 'x = 5 % 3']
|
|
3109
|
+
notebook = {
|
|
3110
|
+
'cells': [{'cell_type': 'code', 'metadata': {}, 'source': code}],
|
|
3111
|
+
'metadata': {},
|
|
3112
|
+
'nbformat': 4,
|
|
3113
|
+
'nbformat_minor': 5,
|
|
3114
|
+
}
|
|
3115
|
+
rel_path = seac._write_marimo_notebook(Mock(outdir=str(tmp_path)), 'art', notebook)
|
|
3116
|
+
source = (tmp_path / '_downloads' / rel_path).read_text(encoding='utf-8')
|
|
3117
|
+
assert 'magic command' not in source
|
|
3118
|
+
assert seac._PERCENT_MARK not in source
|
|
3119
|
+
assert ' art = """\n % %\n %% %\n """\n x = 5 % 3\n' in source
|
|
3120
|
+
|
|
3121
|
+
|
|
3089
3122
|
@pytest.mark.parametrize(
|
|
3090
3123
|
('codes', 'expected'),
|
|
3091
3124
|
[
|
|
@@ -3294,3 +3327,39 @@ def test_drop_covered_keeps_pinned_platform_specific_and_cycles(fake_requires):
|
|
|
3294
3327
|
def test_drop_covered_without_metadata_keeps_everything(fake_requires):
|
|
3295
3328
|
fake_requires({})
|
|
3296
3329
|
assert seac._drop_covered(['numpy', 'pyvista']) == ['numpy', 'pyvista']
|
|
3330
|
+
|
|
3331
|
+
|
|
3332
|
+
def test_page_dependencies_purge_and_merge():
|
|
3333
|
+
env, other = SimpleNamespace(), SimpleNamespace()
|
|
3334
|
+
seac._page_dependencies(env)['a'] = ['sympy']
|
|
3335
|
+
seac._page_dependencies(other).update({'b': ['tqdm'], 'c': ['rich']})
|
|
3336
|
+
seac._merge_page_dependencies(None, env, {'b'}, other)
|
|
3337
|
+
assert seac._page_dependencies(env) == {'a': ['sympy'], 'b': ['tqdm']}
|
|
3338
|
+
seac._purge_page_dependencies(None, env, 'a')
|
|
3339
|
+
seac._purge_page_dependencies(None, env, 'missing')
|
|
3340
|
+
assert seac._page_dependencies(env) == {'b': ['tqdm']}
|
|
3341
|
+
|
|
3342
|
+
|
|
3343
|
+
def _run_dependencies_directive(argument: str, env: SimpleNamespace) -> list[nodes.Node]:
|
|
3344
|
+
"""Run ``examples-as-code-dependencies`` with ``argument`` on page ``page``."""
|
|
3345
|
+
state = Mock()
|
|
3346
|
+
state.document.settings.env = env
|
|
3347
|
+
directive = seac._DependenciesDirective(
|
|
3348
|
+
'examples-as-code-dependencies', [argument], {}, [], 1, 0, '', state, Mock()
|
|
3349
|
+
)
|
|
3350
|
+
return directive.run()
|
|
3351
|
+
|
|
3352
|
+
|
|
3353
|
+
def test_dependencies_directive_records_requirements():
|
|
3354
|
+
env = SimpleNamespace(docname='page')
|
|
3355
|
+
assert _run_dependencies_directive('sympy, tqdm>=4', env) == []
|
|
3356
|
+
_run_dependencies_directive('rich', env)
|
|
3357
|
+
assert seac._page_dependencies(env) == {'page': ['sympy', 'tqdm>=4', 'rich']}
|
|
3358
|
+
|
|
3359
|
+
|
|
3360
|
+
def test_dependencies_directive_invalid_requirement():
|
|
3361
|
+
env = SimpleNamespace(docname='page')
|
|
3362
|
+
with pytest.raises(DirectiveError) as excinfo:
|
|
3363
|
+
_run_dependencies_directive('sympy not!valid', env)
|
|
3364
|
+
assert excinfo.value.msg.startswith("Invalid requirement 'not!valid'")
|
|
3365
|
+
assert seac._page_dependencies(env) == {}
|
|
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.2 → sphinx_examples_as_code-0.9.0}/tests/gallery_fixture/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/gallery_fixture/index.rst
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/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.2 → sphinx_examples_as_code-0.9.0}/tests/test_heading_level_reuse.py
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/test_single_function_page.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/tinypages/docstring_cases.py
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.8.2 → sphinx_examples_as_code-0.9.0}/tests/tinypages/docstring_cases.rst
RENAMED
|
File without changes
|
|
File without changes
|