scipy-doctest 1.6__tar.gz → 1.7__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.
- {scipy_doctest-1.6 → scipy_doctest-1.7}/PKG-INFO +59 -45
- {scipy_doctest-1.6 → scipy_doctest-1.7}/README.md +50 -42
- {scipy_doctest-1.6 → scipy_doctest-1.7}/pyproject.toml +32 -1
- scipy_doctest-1.7/scipy_doctest/.ruff_cache/.gitignore +2 -0
- scipy_doctest-1.7/scipy_doctest/.ruff_cache/0.9.4/12495833369255784944 +0 -0
- scipy_doctest-1.7/scipy_doctest/.ruff_cache/CACHEDIR.TAG +1 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/__init__.py +1 -2
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/conftest.py +0 -1
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/frontend.py +6 -7
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/impl.py +94 -35
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/plugin.py +9 -9
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/failure_cases_2.py +0 -1
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/finder_cases.py +0 -2
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/local_file_cases.py +0 -2
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/module_cases.py +0 -2
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst +2 -2
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_finder.py +6 -9
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_parser.py +0 -3
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_pytest_configuration.py +1 -2
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_runner.py +0 -1
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_skipmarkers.py +12 -7
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_testmod.py +1 -1
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/util.py +2 -3
- {scipy_doctest-1.6 → scipy_doctest-1.7}/LICENSE +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/__main__.py +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/__init__.py +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/failure_cases.py +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/finder_cases_2.py +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/local_file.txt +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/octave_a.mat +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/stopwords_cases.py +0 -0
- {scipy_doctest-1.6 → scipy_doctest-1.7}/scipy_doctest/tests/test_testfile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: scipy_doctest
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7
|
|
4
4
|
Summary: Configurable, whitespace-insensitive, floating-point-aware doctest helpers.
|
|
5
5
|
Maintainer-email: SciPy developers <scipy-dev@python.org>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -10,20 +10,36 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Framework :: Pytest
|
|
13
|
+
License-File: LICENSE
|
|
13
14
|
Requires-Dist: numpy>=1.19.5
|
|
14
15
|
Requires-Dist: pytest
|
|
15
|
-
Requires-Dist:
|
|
16
|
+
Requires-Dist: furo==2024.8.6 ; extra == "doc"
|
|
17
|
+
Requires-Dist: myst-parser==4.0.0 ; extra == "doc"
|
|
18
|
+
Requires-Dist: sphinx==8.1.3 ; extra == "doc"
|
|
19
|
+
Requires-Dist: sphinx-copybutton==0.5.2 ; extra == "doc"
|
|
20
|
+
Requires-Dist: scipy <= 1.14.1 ; extra == "test"
|
|
16
21
|
Requires-Dist: matplotlib ; extra == "test"
|
|
17
22
|
Project-URL: Home, https://github.com/scipy/scipy_doctest
|
|
23
|
+
Provides-Extra: doc
|
|
18
24
|
Provides-Extra: test
|
|
19
25
|
|
|
20
26
|
# Floating-point aware, human readable, numpy-compatible doctesting.
|
|
21
27
|
|
|
28
|
+
[![PyPI version][pypi-version]][pypi-link]
|
|
29
|
+
[![Conda-Forge][conda-badge]][conda-link]
|
|
30
|
+
|
|
31
|
+
<!-- prettier-ignore-start -->
|
|
32
|
+
[conda-badge]: https://img.shields.io/conda/vn/conda-forge/scipy-doctest
|
|
33
|
+
[conda-link]: https://anaconda.org/conda-forge/scipy-doctest
|
|
34
|
+
[pypi-link]: https://pypi.org/project/scipy-doctest/
|
|
35
|
+
[pypi-version]: https://img.shields.io/pypi/v/scipy-doctest
|
|
36
|
+
<!-- prettier-ignore-end -->
|
|
37
|
+
|
|
22
38
|
## TL;DR
|
|
23
39
|
|
|
24
40
|
This project extends the standard library `doctest` module to allow flexibility
|
|
25
41
|
and easy customization of finding, parsing and checking code examples in
|
|
26
|
-
documentation.
|
|
42
|
+
documentation.
|
|
27
43
|
|
|
28
44
|
Can be used either as drop-in `doctest` replacement or through the `pytest`
|
|
29
45
|
integration. Uses a floating-point aware doctest checker by default.
|
|
@@ -31,7 +47,7 @@ integration. Uses a floating-point aware doctest checker by default.
|
|
|
31
47
|
## Motivation and scope
|
|
32
48
|
|
|
33
49
|
Having examples in the documentation is great. Having wrong examples in the
|
|
34
|
-
documentation is not that great however.
|
|
50
|
+
documentation is not that great however.
|
|
35
51
|
|
|
36
52
|
The standard library `doctest` module is great for making sure that docstring
|
|
37
53
|
examples are correct. However, the `doctest` module is limited in several
|
|
@@ -46,7 +62,7 @@ This looks reasonably clear but does not work, in three different ways.
|
|
|
46
62
|
_First_, `1/3` is not equal to 0.333 because floating-point arithmetic.
|
|
47
63
|
_Second_, `numpy` adds whitespace to its output, this whitespace confuses the
|
|
48
64
|
`doctest`, which is whitespace-sensitive. Therefore, we added a magic directive,
|
|
49
|
-
|
|
65
|
+
`+SKIP` to avoid a doctest error. _Third_, the example is actually
|
|
50
66
|
wrong---notice `0.669` which is not equal to `2/3` to three sig figs. The error
|
|
51
67
|
went unnoticed by the doctester also because of the `+SKIP` directive.
|
|
52
68
|
|
|
@@ -56,44 +72,44 @@ a human reader, and should not be present in the documentation.
|
|
|
56
72
|
This package defines modified doctesting routines which fix these deficiencies.
|
|
57
73
|
Its main features are
|
|
58
74
|
|
|
59
|
-
-
|
|
75
|
+
- _Doctesting is floating-point aware._ In a nutshell, the core check is
|
|
60
76
|
`np.allclose(want, got, atol=..., rtol=...)`, with user-controllable abs
|
|
61
77
|
and relative tolerances. In the example above (_sans_ `# doctest: +SKIP`),
|
|
62
78
|
`want` is the desired output, `array([0.333, 0.669, 1])` and `got` is the
|
|
63
79
|
actual output from numpy: `array([0.33333333, 0.66666667, 1. ])`.
|
|
64
80
|
|
|
65
|
-
-
|
|
81
|
+
- _Human-readable skip markers._ Consider
|
|
66
82
|
```
|
|
67
83
|
>>> np.random.randint(100)
|
|
68
84
|
42 # may vary
|
|
69
85
|
```
|
|
70
|
-
Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
|
|
71
|
-
to either an example's output, or its source.
|
|
86
|
+
Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
|
|
87
|
+
to either an example's output, or its source.
|
|
72
88
|
|
|
73
89
|
Also note a difference with respect to the standard `# doctest: +SKIP`: the latter
|
|
74
90
|
skips the example entirely, while these additional markers only skip checking
|
|
75
91
|
the output. Thus the example source needs to be valid python code still.
|
|
76
92
|
|
|
77
|
-
- A user-configurable list of
|
|
93
|
+
- A user-configurable list of _stopwords_. If an example contains a stopword,
|
|
78
94
|
it is checked to be valid python, but the output is not checked. This can
|
|
79
95
|
be useful e.g. for not littering the documentation with the output of
|
|
80
96
|
`import matplotlib.pyplot as plt; plt.xlim([2.3, 4.5])`.
|
|
81
97
|
|
|
82
|
-
- A user-configurable list of
|
|
98
|
+
- A user-configurable list of _pseudocode_ markers. If an example contains one
|
|
83
99
|
of these markers, it is considered pseudocode and is not checked.
|
|
84
100
|
This is useful for `from example import some_functions` and similar stanzas.
|
|
85
101
|
|
|
86
102
|
- A `# doctest: +SKIPBLOCK` option flag to skip whole blocks of pseudocode. Here
|
|
87
103
|
a 'block' is a sequence of doctest examples without any intervening text.
|
|
88
104
|
|
|
89
|
-
-
|
|
105
|
+
- _Doctest discovery_ is somewhat more flexible then the standard library
|
|
90
106
|
`doctest` module. Specifically, one can use `testmod(module, strategy='api')`
|
|
91
107
|
to only examine public objects of a module. This is helpful for complex
|
|
92
108
|
packages, with non-trivial internal file structure. Alternatively, the default
|
|
93
109
|
value of `strategy=None` is equivalent to the standard `doctest` module
|
|
94
110
|
behavior.
|
|
95
111
|
|
|
96
|
-
-
|
|
112
|
+
- _User configuration_. Essentially all aspects of the behavior are user
|
|
97
113
|
configurable via a `DTConfig` instance attributes. See the `DTConfig`
|
|
98
114
|
docstring for details.
|
|
99
115
|
|
|
@@ -124,7 +140,7 @@ pip install scipy-doctest
|
|
|
124
140
|
|
|
125
141
|
2. **Register or load the plugin**
|
|
126
142
|
|
|
127
|
-
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
|
|
143
|
+
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
|
|
128
144
|
|
|
129
145
|
To do this, add the following line of code:
|
|
130
146
|
|
|
@@ -136,14 +152,16 @@ pytest_plugins = "scipy_doctest"
|
|
|
136
152
|
|
|
137
153
|
Check out the [pytest documentation](https://docs.pytest.org/en/stable/how-to/writing_plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file) for more information on requiring/loading plugins in a test module or `conftest.py` file.
|
|
138
154
|
|
|
139
|
-
3. **Run doctests**
|
|
155
|
+
3. **Run doctests**
|
|
140
156
|
|
|
141
157
|
Once the plugin is registered, run the doctests by executing the following command:
|
|
142
158
|
|
|
143
159
|
```bash
|
|
144
160
|
$ python -m pytest --doctest-modules
|
|
145
161
|
```
|
|
162
|
+
|
|
146
163
|
or
|
|
164
|
+
|
|
147
165
|
```bash
|
|
148
166
|
$ pytest --pyargs <your-package> --doctest-modules
|
|
149
167
|
```
|
|
@@ -155,10 +173,9 @@ use the command flag
|
|
|
155
173
|
$ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
|
|
156
174
|
```
|
|
157
175
|
|
|
158
|
-
See [More fine-grained control](
|
|
176
|
+
See [More fine-grained control](#more-fine-grained-control) section
|
|
159
177
|
for details on how to customize the behavior.
|
|
160
178
|
|
|
161
|
-
|
|
162
179
|
### Basic usage
|
|
163
180
|
|
|
164
181
|
The use of `pytest` is optional, and you can use the `doctest` layer API.
|
|
@@ -171,6 +188,7 @@ For example,
|
|
|
171
188
|
>>> res
|
|
172
189
|
TestResults(failed=0, attempted=764)
|
|
173
190
|
```
|
|
191
|
+
|
|
174
192
|
The second return value, `hist` is a dict which maps the names of the objects
|
|
175
193
|
to the numbers of failures and attempts for individual examples.
|
|
176
194
|
|
|
@@ -178,10 +196,10 @@ For more details, see the `testmod` docstring. Other useful functions are
|
|
|
178
196
|
`find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
|
|
179
197
|
the behavior of the eponymous functions of the `doctest` module).
|
|
180
198
|
|
|
181
|
-
|
|
182
199
|
### Command-line interface
|
|
183
200
|
|
|
184
201
|
There is a basic CLI, which also mimics that of the `doctest` module:
|
|
202
|
+
|
|
185
203
|
```
|
|
186
204
|
$ python -m scipy_doctest foo.py
|
|
187
205
|
```
|
|
@@ -190,28 +208,30 @@ Note that, just like `$ python -m doctest foo.py`, this may
|
|
|
190
208
|
fail if `foo.py` is a part of a package due to package imports.
|
|
191
209
|
|
|
192
210
|
Text files can also be CLI-checked:
|
|
211
|
+
|
|
193
212
|
```
|
|
194
213
|
$ python -m scipy_doctest bar.rst
|
|
195
214
|
```
|
|
196
215
|
|
|
197
216
|
Notice that the command-line usage only uses the default `DTConfig` settings.
|
|
198
217
|
|
|
218
|
+
(more-fine-grained-control)=
|
|
199
219
|
|
|
200
220
|
## More fine-grained control
|
|
201
221
|
|
|
202
222
|
More fine-grained control of the functionality is available via the following
|
|
203
223
|
classes
|
|
204
224
|
|
|
205
|
-
|
|
|
206
|
-
|
|
207
|
-
| `DTChecker` | `DocTestChecker`
|
|
208
|
-
| `DTParser` | `DocTestParser`
|
|
209
|
-
| `DTRunner` | `DocTestRunner`
|
|
210
|
-
| `DTFinder` | `DocTestFinder`
|
|
211
|
-
| `DTContext` |
|
|
225
|
+
| Class | `doctest` analog |
|
|
226
|
+
| ----------- | ---------------- |
|
|
227
|
+
| `DTChecker` | `DocTestChecker` |
|
|
228
|
+
| `DTParser` | `DocTestParser` |
|
|
229
|
+
| `DTRunner` | `DocTestRunner` |
|
|
230
|
+
| `DTFinder` | `DocTestFinder` |
|
|
231
|
+
| `DTContext` | -- |
|
|
212
232
|
|
|
213
233
|
The `DTContext` class is just a bag class which holds various configuration
|
|
214
|
-
settings as attributes.
|
|
234
|
+
settings as attributes. An instance of this class is passed around, so user
|
|
215
235
|
configuration is simply creating an instance, overriding an attribute and
|
|
216
236
|
passing the instance to `testmod` or constructors of `DT*` objects. Defaults
|
|
217
237
|
are provided, based on a long-term usage in SciPy.
|
|
@@ -220,7 +240,7 @@ See the [DTConfig docstring](https://github.com/scipy/scipy_doctest/blob/main/sc
|
|
|
220
240
|
for the full set of attributes that allow you to fine-tune your doctesting experience.
|
|
221
241
|
|
|
222
242
|
To set any of these attributes, create an instance of `DTConfig` and assign the attributes
|
|
223
|
-
in a usual way.
|
|
243
|
+
in a usual way.
|
|
224
244
|
|
|
225
245
|
If using the pytest plugin, it is convenient to use the default instance, which
|
|
226
246
|
is predefined in `scipy_doctest/conftest.py`. This instance will be automatically
|
|
@@ -260,14 +280,12 @@ dt_config.skiplist = {
|
|
|
260
280
|
|
|
261
281
|
If you don't set these attributes, the [default settings](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/impl.py#L94) of the attributes are used.
|
|
262
282
|
|
|
263
|
-
|
|
264
283
|
#### Alternative Checkers
|
|
265
284
|
|
|
266
285
|
By default, we use the floating-point aware `DTChecker`. If you want to use an
|
|
267
286
|
alternative checker, all you need to do is to define the corresponding class,
|
|
268
287
|
and add an attribute to the `DTConfig` instance. For example,
|
|
269
288
|
|
|
270
|
-
|
|
271
289
|
```
|
|
272
290
|
class VanillaOutputChecker(doctest.OutputChecker):
|
|
273
291
|
"""doctest.OutputChecker to drop in for DTChecker.
|
|
@@ -290,10 +308,8 @@ See [a pytest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_do
|
|
|
290
308
|
and [a doctest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_runner.py#L94)
|
|
291
309
|
for more details.
|
|
292
310
|
|
|
293
|
-
|
|
294
311
|
### NumPy and SciPy wrappers
|
|
295
312
|
|
|
296
|
-
|
|
297
313
|
NumPy wraps `scipy-doctest` with the `spin` command
|
|
298
314
|
|
|
299
315
|
```
|
|
@@ -307,13 +323,11 @@ $ python dev.py smoke-docs # check docstrings
|
|
|
307
323
|
$ python dev.py smoke-tutorials # ReST user guide tutorials
|
|
308
324
|
```
|
|
309
325
|
|
|
310
|
-
|
|
311
|
-
|
|
312
326
|
## Rough edges and sharp bits
|
|
313
327
|
|
|
314
328
|
Here is a (non-exhaustive) list of possible gotchas:
|
|
315
329
|
|
|
316
|
-
-
|
|
330
|
+
- _In-place development builds_.
|
|
317
331
|
|
|
318
332
|
Some tools (looking at you `meson-python`) simulate in-place builds with a
|
|
319
333
|
`build-install` directory. If this directory is located under the project root,
|
|
@@ -329,12 +343,15 @@ $ pytest build-install/lib/python3.10/site-packages/scipy/ --doctest-modules
|
|
|
329
343
|
|
|
330
344
|
instead of `$ pytest --pyargs scipy`.
|
|
331
345
|
|
|
332
|
-
If
|
|
346
|
+
If you use actual editable installs, of the `pip install --no-build-isolation -e .` variety, you may
|
|
347
|
+
need to add `--import-mode=importlib` to the `pytest` invocation.
|
|
348
|
+
|
|
349
|
+
If push really comes to shove, you may try using the magic env variable:
|
|
333
350
|
` PY_IGNORE_IMPORTMISMATCH=1 pytest ...`,
|
|
334
351
|
however the need usually indicates an issue with the package itself.
|
|
335
352
|
(see [gh-107](https://github.com/scipy/scipy_doctest/pull/107) for an example).
|
|
336
353
|
|
|
337
|
-
-
|
|
354
|
+
- _Optional dependencies are not that optional_
|
|
338
355
|
|
|
339
356
|
If your package contains optional dependencies, doctests do not know about them
|
|
340
357
|
being optional. So you either guard the imports in doctests (yikes!), or
|
|
@@ -353,11 +370,11 @@ Note that installed packages are no different:
|
|
|
353
370
|
$ pytest --pyargs scipy --doctest-modules --ignore=/path/to/installed/scipy/_lib
|
|
354
371
|
```
|
|
355
372
|
|
|
356
|
-
-
|
|
373
|
+
- _Doctest collection strategies_
|
|
357
374
|
|
|
358
375
|
The default collection strategy follows `doctest` module and `pytest`. This leads
|
|
359
376
|
to duplicates if your package has the split between public and \_private modules,
|
|
360
|
-
where
|
|
377
|
+
where public modules re-export things from private ones. The solution is to
|
|
361
378
|
use `$ pytest --doctest-collect=api` CLI switch: with this, only public
|
|
362
379
|
objects will be collected.
|
|
363
380
|
|
|
@@ -375,8 +392,7 @@ leads to
|
|
|
375
392
|
- `scipy.linalg._basic.det`, collected from `scipy/linalg/_basic.py`, is private.
|
|
376
393
|
- `scipy.linalg.det`, collected from `scipy/linalg/__init__.py`, is public.
|
|
377
394
|
|
|
378
|
-
|
|
379
|
-
- *`pytest`'s assertion rewriting*
|
|
395
|
+
- _`pytest`'s assertion rewriting_
|
|
380
396
|
|
|
381
397
|
In some rare cases you may need to either explicitly register the `scipy_doctest`
|
|
382
398
|
package with the `pytest` assertion rewriting machinery, or ask it to avoid rewriting
|
|
@@ -388,7 +404,6 @@ In general, rewriting assertions is not very useful for doctests, as the
|
|
|
388
404
|
output on error is fixed by the doctest machinery anyway. Therefore, we believe
|
|
389
405
|
adding `--assert=plain` is reasonable.
|
|
390
406
|
|
|
391
|
-
|
|
392
407
|
## Prior art and related work
|
|
393
408
|
|
|
394
409
|
- `pytest` provides some limited floating-point aware `NumericLiteralChecker`.
|
|
@@ -409,15 +424,14 @@ adding `--assert=plain` is reasonable.
|
|
|
409
424
|
to be not easy to reason about, work with, and extend to other projects.
|
|
410
425
|
|
|
411
426
|
This project is mainly the core functionality of the modified
|
|
412
|
-
`refguide-check` doctesting, extracted to a separate package.
|
|
427
|
+
`refguide-check` doctesting, extracted to a separate package.
|
|
413
428
|
We believe having it separate simplifies both addressing the needs of these
|
|
414
429
|
two packages, and potential adoption by other projects.
|
|
415
430
|
|
|
416
|
-
|
|
417
431
|
### Bug reports, feature requests and contributions
|
|
418
432
|
|
|
419
433
|
This package is work in progress. Contributions are most welcome!
|
|
420
434
|
Please don't hesitate to open an issue in the tracker or send a pull request.
|
|
421
435
|
|
|
422
|
-
The current location of the issue tracker is https://github.com/scipy/scipy_doctest
|
|
436
|
+
The current location of the issue tracker is <https://github.com/scipy/scipy_doctest>.
|
|
423
437
|
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# Floating-point aware, human readable, numpy-compatible doctesting.
|
|
2
2
|
|
|
3
|
+
[![PyPI version][pypi-version]][pypi-link]
|
|
4
|
+
[![Conda-Forge][conda-badge]][conda-link]
|
|
5
|
+
|
|
6
|
+
<!-- prettier-ignore-start -->
|
|
7
|
+
[conda-badge]: https://img.shields.io/conda/vn/conda-forge/scipy-doctest
|
|
8
|
+
[conda-link]: https://anaconda.org/conda-forge/scipy-doctest
|
|
9
|
+
[pypi-link]: https://pypi.org/project/scipy-doctest/
|
|
10
|
+
[pypi-version]: https://img.shields.io/pypi/v/scipy-doctest
|
|
11
|
+
<!-- prettier-ignore-end -->
|
|
12
|
+
|
|
3
13
|
## TL;DR
|
|
4
14
|
|
|
5
15
|
This project extends the standard library `doctest` module to allow flexibility
|
|
6
16
|
and easy customization of finding, parsing and checking code examples in
|
|
7
|
-
documentation.
|
|
17
|
+
documentation.
|
|
8
18
|
|
|
9
19
|
Can be used either as drop-in `doctest` replacement or through the `pytest`
|
|
10
20
|
integration. Uses a floating-point aware doctest checker by default.
|
|
@@ -12,7 +22,7 @@ integration. Uses a floating-point aware doctest checker by default.
|
|
|
12
22
|
## Motivation and scope
|
|
13
23
|
|
|
14
24
|
Having examples in the documentation is great. Having wrong examples in the
|
|
15
|
-
documentation is not that great however.
|
|
25
|
+
documentation is not that great however.
|
|
16
26
|
|
|
17
27
|
The standard library `doctest` module is great for making sure that docstring
|
|
18
28
|
examples are correct. However, the `doctest` module is limited in several
|
|
@@ -27,7 +37,7 @@ This looks reasonably clear but does not work, in three different ways.
|
|
|
27
37
|
_First_, `1/3` is not equal to 0.333 because floating-point arithmetic.
|
|
28
38
|
_Second_, `numpy` adds whitespace to its output, this whitespace confuses the
|
|
29
39
|
`doctest`, which is whitespace-sensitive. Therefore, we added a magic directive,
|
|
30
|
-
|
|
40
|
+
`+SKIP` to avoid a doctest error. _Third_, the example is actually
|
|
31
41
|
wrong---notice `0.669` which is not equal to `2/3` to three sig figs. The error
|
|
32
42
|
went unnoticed by the doctester also because of the `+SKIP` directive.
|
|
33
43
|
|
|
@@ -37,44 +47,44 @@ a human reader, and should not be present in the documentation.
|
|
|
37
47
|
This package defines modified doctesting routines which fix these deficiencies.
|
|
38
48
|
Its main features are
|
|
39
49
|
|
|
40
|
-
-
|
|
50
|
+
- _Doctesting is floating-point aware._ In a nutshell, the core check is
|
|
41
51
|
`np.allclose(want, got, atol=..., rtol=...)`, with user-controllable abs
|
|
42
52
|
and relative tolerances. In the example above (_sans_ `# doctest: +SKIP`),
|
|
43
53
|
`want` is the desired output, `array([0.333, 0.669, 1])` and `got` is the
|
|
44
54
|
actual output from numpy: `array([0.33333333, 0.66666667, 1. ])`.
|
|
45
55
|
|
|
46
|
-
-
|
|
56
|
+
- _Human-readable skip markers._ Consider
|
|
47
57
|
```
|
|
48
58
|
>>> np.random.randint(100)
|
|
49
59
|
42 # may vary
|
|
50
60
|
```
|
|
51
|
-
Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
|
|
52
|
-
to either an example's output, or its source.
|
|
61
|
+
Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
|
|
62
|
+
to either an example's output, or its source.
|
|
53
63
|
|
|
54
64
|
Also note a difference with respect to the standard `# doctest: +SKIP`: the latter
|
|
55
65
|
skips the example entirely, while these additional markers only skip checking
|
|
56
66
|
the output. Thus the example source needs to be valid python code still.
|
|
57
67
|
|
|
58
|
-
- A user-configurable list of
|
|
68
|
+
- A user-configurable list of _stopwords_. If an example contains a stopword,
|
|
59
69
|
it is checked to be valid python, but the output is not checked. This can
|
|
60
70
|
be useful e.g. for not littering the documentation with the output of
|
|
61
71
|
`import matplotlib.pyplot as plt; plt.xlim([2.3, 4.5])`.
|
|
62
72
|
|
|
63
|
-
- A user-configurable list of
|
|
73
|
+
- A user-configurable list of _pseudocode_ markers. If an example contains one
|
|
64
74
|
of these markers, it is considered pseudocode and is not checked.
|
|
65
75
|
This is useful for `from example import some_functions` and similar stanzas.
|
|
66
76
|
|
|
67
77
|
- A `# doctest: +SKIPBLOCK` option flag to skip whole blocks of pseudocode. Here
|
|
68
78
|
a 'block' is a sequence of doctest examples without any intervening text.
|
|
69
79
|
|
|
70
|
-
-
|
|
80
|
+
- _Doctest discovery_ is somewhat more flexible then the standard library
|
|
71
81
|
`doctest` module. Specifically, one can use `testmod(module, strategy='api')`
|
|
72
82
|
to only examine public objects of a module. This is helpful for complex
|
|
73
83
|
packages, with non-trivial internal file structure. Alternatively, the default
|
|
74
84
|
value of `strategy=None` is equivalent to the standard `doctest` module
|
|
75
85
|
behavior.
|
|
76
86
|
|
|
77
|
-
-
|
|
87
|
+
- _User configuration_. Essentially all aspects of the behavior are user
|
|
78
88
|
configurable via a `DTConfig` instance attributes. See the `DTConfig`
|
|
79
89
|
docstring for details.
|
|
80
90
|
|
|
@@ -105,7 +115,7 @@ pip install scipy-doctest
|
|
|
105
115
|
|
|
106
116
|
2. **Register or load the plugin**
|
|
107
117
|
|
|
108
|
-
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
|
|
118
|
+
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
|
|
109
119
|
|
|
110
120
|
To do this, add the following line of code:
|
|
111
121
|
|
|
@@ -117,14 +127,16 @@ pytest_plugins = "scipy_doctest"
|
|
|
117
127
|
|
|
118
128
|
Check out the [pytest documentation](https://docs.pytest.org/en/stable/how-to/writing_plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file) for more information on requiring/loading plugins in a test module or `conftest.py` file.
|
|
119
129
|
|
|
120
|
-
3. **Run doctests**
|
|
130
|
+
3. **Run doctests**
|
|
121
131
|
|
|
122
132
|
Once the plugin is registered, run the doctests by executing the following command:
|
|
123
133
|
|
|
124
134
|
```bash
|
|
125
135
|
$ python -m pytest --doctest-modules
|
|
126
136
|
```
|
|
137
|
+
|
|
127
138
|
or
|
|
139
|
+
|
|
128
140
|
```bash
|
|
129
141
|
$ pytest --pyargs <your-package> --doctest-modules
|
|
130
142
|
```
|
|
@@ -136,10 +148,9 @@ use the command flag
|
|
|
136
148
|
$ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
|
|
137
149
|
```
|
|
138
150
|
|
|
139
|
-
See [More fine-grained control](
|
|
151
|
+
See [More fine-grained control](#more-fine-grained-control) section
|
|
140
152
|
for details on how to customize the behavior.
|
|
141
153
|
|
|
142
|
-
|
|
143
154
|
### Basic usage
|
|
144
155
|
|
|
145
156
|
The use of `pytest` is optional, and you can use the `doctest` layer API.
|
|
@@ -152,6 +163,7 @@ For example,
|
|
|
152
163
|
>>> res
|
|
153
164
|
TestResults(failed=0, attempted=764)
|
|
154
165
|
```
|
|
166
|
+
|
|
155
167
|
The second return value, `hist` is a dict which maps the names of the objects
|
|
156
168
|
to the numbers of failures and attempts for individual examples.
|
|
157
169
|
|
|
@@ -159,10 +171,10 @@ For more details, see the `testmod` docstring. Other useful functions are
|
|
|
159
171
|
`find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
|
|
160
172
|
the behavior of the eponymous functions of the `doctest` module).
|
|
161
173
|
|
|
162
|
-
|
|
163
174
|
### Command-line interface
|
|
164
175
|
|
|
165
176
|
There is a basic CLI, which also mimics that of the `doctest` module:
|
|
177
|
+
|
|
166
178
|
```
|
|
167
179
|
$ python -m scipy_doctest foo.py
|
|
168
180
|
```
|
|
@@ -171,28 +183,30 @@ Note that, just like `$ python -m doctest foo.py`, this may
|
|
|
171
183
|
fail if `foo.py` is a part of a package due to package imports.
|
|
172
184
|
|
|
173
185
|
Text files can also be CLI-checked:
|
|
186
|
+
|
|
174
187
|
```
|
|
175
188
|
$ python -m scipy_doctest bar.rst
|
|
176
189
|
```
|
|
177
190
|
|
|
178
191
|
Notice that the command-line usage only uses the default `DTConfig` settings.
|
|
179
192
|
|
|
193
|
+
(more-fine-grained-control)=
|
|
180
194
|
|
|
181
195
|
## More fine-grained control
|
|
182
196
|
|
|
183
197
|
More fine-grained control of the functionality is available via the following
|
|
184
198
|
classes
|
|
185
199
|
|
|
186
|
-
|
|
|
187
|
-
|
|
188
|
-
| `DTChecker` | `DocTestChecker`
|
|
189
|
-
| `DTParser` | `DocTestParser`
|
|
190
|
-
| `DTRunner` | `DocTestRunner`
|
|
191
|
-
| `DTFinder` | `DocTestFinder`
|
|
192
|
-
| `DTContext` |
|
|
200
|
+
| Class | `doctest` analog |
|
|
201
|
+
| ----------- | ---------------- |
|
|
202
|
+
| `DTChecker` | `DocTestChecker` |
|
|
203
|
+
| `DTParser` | `DocTestParser` |
|
|
204
|
+
| `DTRunner` | `DocTestRunner` |
|
|
205
|
+
| `DTFinder` | `DocTestFinder` |
|
|
206
|
+
| `DTContext` | -- |
|
|
193
207
|
|
|
194
208
|
The `DTContext` class is just a bag class which holds various configuration
|
|
195
|
-
settings as attributes.
|
|
209
|
+
settings as attributes. An instance of this class is passed around, so user
|
|
196
210
|
configuration is simply creating an instance, overriding an attribute and
|
|
197
211
|
passing the instance to `testmod` or constructors of `DT*` objects. Defaults
|
|
198
212
|
are provided, based on a long-term usage in SciPy.
|
|
@@ -201,7 +215,7 @@ See the [DTConfig docstring](https://github.com/scipy/scipy_doctest/blob/main/sc
|
|
|
201
215
|
for the full set of attributes that allow you to fine-tune your doctesting experience.
|
|
202
216
|
|
|
203
217
|
To set any of these attributes, create an instance of `DTConfig` and assign the attributes
|
|
204
|
-
in a usual way.
|
|
218
|
+
in a usual way.
|
|
205
219
|
|
|
206
220
|
If using the pytest plugin, it is convenient to use the default instance, which
|
|
207
221
|
is predefined in `scipy_doctest/conftest.py`. This instance will be automatically
|
|
@@ -241,14 +255,12 @@ dt_config.skiplist = {
|
|
|
241
255
|
|
|
242
256
|
If you don't set these attributes, the [default settings](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/impl.py#L94) of the attributes are used.
|
|
243
257
|
|
|
244
|
-
|
|
245
258
|
#### Alternative Checkers
|
|
246
259
|
|
|
247
260
|
By default, we use the floating-point aware `DTChecker`. If you want to use an
|
|
248
261
|
alternative checker, all you need to do is to define the corresponding class,
|
|
249
262
|
and add an attribute to the `DTConfig` instance. For example,
|
|
250
263
|
|
|
251
|
-
|
|
252
264
|
```
|
|
253
265
|
class VanillaOutputChecker(doctest.OutputChecker):
|
|
254
266
|
"""doctest.OutputChecker to drop in for DTChecker.
|
|
@@ -271,10 +283,8 @@ See [a pytest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_do
|
|
|
271
283
|
and [a doctest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_runner.py#L94)
|
|
272
284
|
for more details.
|
|
273
285
|
|
|
274
|
-
|
|
275
286
|
### NumPy and SciPy wrappers
|
|
276
287
|
|
|
277
|
-
|
|
278
288
|
NumPy wraps `scipy-doctest` with the `spin` command
|
|
279
289
|
|
|
280
290
|
```
|
|
@@ -288,13 +298,11 @@ $ python dev.py smoke-docs # check docstrings
|
|
|
288
298
|
$ python dev.py smoke-tutorials # ReST user guide tutorials
|
|
289
299
|
```
|
|
290
300
|
|
|
291
|
-
|
|
292
|
-
|
|
293
301
|
## Rough edges and sharp bits
|
|
294
302
|
|
|
295
303
|
Here is a (non-exhaustive) list of possible gotchas:
|
|
296
304
|
|
|
297
|
-
-
|
|
305
|
+
- _In-place development builds_.
|
|
298
306
|
|
|
299
307
|
Some tools (looking at you `meson-python`) simulate in-place builds with a
|
|
300
308
|
`build-install` directory. If this directory is located under the project root,
|
|
@@ -310,12 +318,15 @@ $ pytest build-install/lib/python3.10/site-packages/scipy/ --doctest-modules
|
|
|
310
318
|
|
|
311
319
|
instead of `$ pytest --pyargs scipy`.
|
|
312
320
|
|
|
313
|
-
If
|
|
321
|
+
If you use actual editable installs, of the `pip install --no-build-isolation -e .` variety, you may
|
|
322
|
+
need to add `--import-mode=importlib` to the `pytest` invocation.
|
|
323
|
+
|
|
324
|
+
If push really comes to shove, you may try using the magic env variable:
|
|
314
325
|
` PY_IGNORE_IMPORTMISMATCH=1 pytest ...`,
|
|
315
326
|
however the need usually indicates an issue with the package itself.
|
|
316
327
|
(see [gh-107](https://github.com/scipy/scipy_doctest/pull/107) for an example).
|
|
317
328
|
|
|
318
|
-
-
|
|
329
|
+
- _Optional dependencies are not that optional_
|
|
319
330
|
|
|
320
331
|
If your package contains optional dependencies, doctests do not know about them
|
|
321
332
|
being optional. So you either guard the imports in doctests (yikes!), or
|
|
@@ -334,11 +345,11 @@ Note that installed packages are no different:
|
|
|
334
345
|
$ pytest --pyargs scipy --doctest-modules --ignore=/path/to/installed/scipy/_lib
|
|
335
346
|
```
|
|
336
347
|
|
|
337
|
-
-
|
|
348
|
+
- _Doctest collection strategies_
|
|
338
349
|
|
|
339
350
|
The default collection strategy follows `doctest` module and `pytest`. This leads
|
|
340
351
|
to duplicates if your package has the split between public and \_private modules,
|
|
341
|
-
where
|
|
352
|
+
where public modules re-export things from private ones. The solution is to
|
|
342
353
|
use `$ pytest --doctest-collect=api` CLI switch: with this, only public
|
|
343
354
|
objects will be collected.
|
|
344
355
|
|
|
@@ -356,8 +367,7 @@ leads to
|
|
|
356
367
|
- `scipy.linalg._basic.det`, collected from `scipy/linalg/_basic.py`, is private.
|
|
357
368
|
- `scipy.linalg.det`, collected from `scipy/linalg/__init__.py`, is public.
|
|
358
369
|
|
|
359
|
-
|
|
360
|
-
- *`pytest`'s assertion rewriting*
|
|
370
|
+
- _`pytest`'s assertion rewriting_
|
|
361
371
|
|
|
362
372
|
In some rare cases you may need to either explicitly register the `scipy_doctest`
|
|
363
373
|
package with the `pytest` assertion rewriting machinery, or ask it to avoid rewriting
|
|
@@ -369,7 +379,6 @@ In general, rewriting assertions is not very useful for doctests, as the
|
|
|
369
379
|
output on error is fixed by the doctest machinery anyway. Therefore, we believe
|
|
370
380
|
adding `--assert=plain` is reasonable.
|
|
371
381
|
|
|
372
|
-
|
|
373
382
|
## Prior art and related work
|
|
374
383
|
|
|
375
384
|
- `pytest` provides some limited floating-point aware `NumericLiteralChecker`.
|
|
@@ -390,14 +399,13 @@ adding `--assert=plain` is reasonable.
|
|
|
390
399
|
to be not easy to reason about, work with, and extend to other projects.
|
|
391
400
|
|
|
392
401
|
This project is mainly the core functionality of the modified
|
|
393
|
-
`refguide-check` doctesting, extracted to a separate package.
|
|
402
|
+
`refguide-check` doctesting, extracted to a separate package.
|
|
394
403
|
We believe having it separate simplifies both addressing the needs of these
|
|
395
404
|
two packages, and potential adoption by other projects.
|
|
396
405
|
|
|
397
|
-
|
|
398
406
|
### Bug reports, feature requests and contributions
|
|
399
407
|
|
|
400
408
|
This package is work in progress. Contributions are most welcome!
|
|
401
409
|
Please don't hesitate to open an issue in the tracker or send a pull request.
|
|
402
410
|
|
|
403
|
-
The current location of the issue tracker is https://github.com/scipy/scipy_doctest
|
|
411
|
+
The current location of the issue tracker is <https://github.com/scipy/scipy_doctest>.
|
|
@@ -25,13 +25,44 @@ dependencies = [
|
|
|
25
25
|
|
|
26
26
|
[project.optional-dependencies]
|
|
27
27
|
test = [
|
|
28
|
-
"scipy",
|
|
28
|
+
"scipy <= 1.14.1", # black-list 1.5.1 ?
|
|
29
29
|
"matplotlib"
|
|
30
30
|
]
|
|
31
|
+
doc = [
|
|
32
|
+
"furo==2024.8.6",
|
|
33
|
+
"myst-parser==4.0.0",
|
|
34
|
+
"sphinx==8.1.3",
|
|
35
|
+
"sphinx-copybutton==0.5.2"
|
|
36
|
+
]
|
|
31
37
|
|
|
32
38
|
[project.urls]
|
|
33
39
|
Home = "https://github.com/scipy/scipy_doctest"
|
|
34
40
|
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = [
|
|
43
|
+
# pycodestyle
|
|
44
|
+
"E",
|
|
45
|
+
# Pyflakes
|
|
46
|
+
"F",
|
|
47
|
+
# pyupgrade
|
|
48
|
+
"UP",
|
|
49
|
+
# flake8-bugbear
|
|
50
|
+
"B",
|
|
51
|
+
# isort
|
|
52
|
+
"I",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint.per-file-ignores]
|
|
56
|
+
"scipy_doctest/tests/module_cases.py" = ["E501"]
|
|
57
|
+
"scipy_doctest/tests/local_file_cases.py" = ["E501"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff.format]
|
|
60
|
+
# Enable reformatting of code snippets in docstrings.
|
|
61
|
+
docstring-code-format = true
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.pydocstyle]
|
|
64
|
+
convention = "numpy"
|
|
65
|
+
|
|
35
66
|
[tool.pytest.ini_options]
|
|
36
67
|
addopts = "--verbose --color=yes"
|
|
37
68
|
|