scipy-doctest 1.6__tar.gz → 1.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/PKG-INFO +59 -45
  2. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/README.md +50 -42
  3. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/pyproject.toml +32 -1
  4. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/__init__.py +1 -2
  5. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/conftest.py +0 -1
  6. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/frontend.py +6 -7
  7. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/impl.py +94 -35
  8. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/plugin.py +9 -9
  9. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/failure_cases_2.py +0 -1
  10. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/finder_cases.py +0 -2
  11. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/local_file_cases.py +0 -2
  12. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/module_cases.py +0 -2
  13. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst +2 -2
  14. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_finder.py +6 -9
  15. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_parser.py +0 -3
  16. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_pytest_configuration.py +1 -2
  17. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_runner.py +0 -1
  18. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_skipmarkers.py +12 -7
  19. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_testmod.py +1 -1
  20. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/util.py +2 -3
  21. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/LICENSE +0 -0
  22. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/__main__.py +0 -0
  23. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/__init__.py +0 -0
  24. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/failure_cases.py +0 -0
  25. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/finder_cases_2.py +0 -0
  26. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/local_file.txt +0 -0
  27. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/octave_a.mat +0 -0
  28. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/stopwords_cases.py +0 -0
  29. {scipy_doctest-1.6 → scipy_doctest-1.7.1}/scipy_doctest/tests/test_testfile.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: scipy_doctest
3
- Version: 1.6
3
+ Version: 1.7.1
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: scipy ; extra == "test"
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
- `+SKIP` to avoid a doctest error. _Third_, the example is actually
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
- - *Doctesting is floating-point aware.* In a nutshell, the core check is
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
- - *Human-readable skip markers.* Consider
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 *stopwords*. If an example contains a stopword,
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 *pseudocode* markers. If an example contains one
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
- - *Doctest discovery* is somewhat more flexible then the standard library
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
- - *User configuration*. Essentially all aspects of the behavior are user
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](https://github.com/scipy/scipy_doctest#More-fine-grained-control) section
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
- | Class | `doctest` analog |
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. An instance of this class is passed around, so user
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
- - *In-place development builds*.
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 push comes to shove, you may try using the magic env variable:
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
- - *Optional dependencies are not that optional*
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
- - *Doctest collection strategies*
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 public modules re-export things from private ones. The solution is to
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
- `+SKIP` to avoid a doctest error. _Third_, the example is actually
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
- - *Doctesting is floating-point aware.* In a nutshell, the core check is
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
- - *Human-readable skip markers.* Consider
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 *stopwords*. If an example contains a stopword,
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 *pseudocode* markers. If an example contains one
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
- - *Doctest discovery* is somewhat more flexible then the standard library
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
- - *User configuration*. Essentially all aspects of the behavior are user
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](https://github.com/scipy/scipy_doctest#More-fine-grained-control) section
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
- | Class | `doctest` analog |
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. An instance of this class is passed around, so user
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
- - *In-place development builds*.
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 push comes to shove, you may try using the magic env variable:
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
- - *Optional dependencies are not that optional*
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
- - *Doctest collection strategies*
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 public modules re-export things from private ones. The solution is to
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
 
@@ -3,7 +3,7 @@ Configurable, whitespace-insensitive, floating-point-aware doctest helpers.
3
3
  """
4
4
 
5
5
 
6
- __version__ = "1.6"
6
+ __version__ = "1.7.1"
7
7
 
8
8
  try:
9
9
  # register internal modules with pytest; obscure errors galore otherwise
@@ -19,4 +19,3 @@ except ModuleNotFoundError:
19
19
 
20
20
  from .impl import DTChecker, DTFinder, DTParser, DTRunner, DebugDTRunner, DTConfig # noqa
21
21
  from .frontend import testmod, testfile, find_doctests, run_docstring_examples # noqa
22
-
@@ -2,4 +2,3 @@ from .impl import DTConfig
2
2
 
3
3
 
4
4
  dt_config = DTConfig()
5
-
@@ -46,7 +46,7 @@ def find_doctests(module, strategy=None,
46
46
  Returns
47
47
  -------
48
48
  tests : list
49
- A list of `doctest.DocTest`s that are defined by the module docstring,
49
+ A list of `doctest.DocTest` s that are defined by the module docstring,
50
50
  and by its contained objects’ docstrings. The selection is controlled
51
51
  by the `strategy` argument.
52
52
 
@@ -141,7 +141,7 @@ def testmod(m=None, name=None, globs=None, verbose=None,
141
141
  In verbose mode, the summary is detailed, else very brief (in fact,
142
142
  empty if all tests passed)
143
143
  Default is True.
144
- verbose : int
144
+ verbose : int
145
145
  Control the run verbosity:
146
146
  0 means only report failures,
147
147
  1 means emit object names,
@@ -175,7 +175,7 @@ def testmod(m=None, name=None, globs=None, verbose=None,
175
175
  (result, history)
176
176
  `result` is a namedtuple ``TestResult(failed, attempted)``
177
177
  `history` is a dict with details of which objects were examined (the
178
- keys are object names and values are individual objects' ``TestResult``s)
178
+ keys are object names and values are individual objects' ``TestResult`` s)
179
179
 
180
180
  Examples
181
181
  --------
@@ -204,7 +204,7 @@ def testmod(m=None, name=None, globs=None, verbose=None,
204
204
  For complex packages, prefer `strategy='api'`, which works as follows:
205
205
  - take the names of public objects from the `__all__` attribute of the package.
206
206
  - if `__all__` is not defined, take `dir(module)` and filter out names
207
- which start with a leading underscore and dunders.
207
+ which start with a leading underscore and dunders.
208
208
  - filter out deprecated items, i.e. those which raise `DeprecationWarning`.
209
209
 
210
210
  """
@@ -306,7 +306,7 @@ def testfile(filename, module_relative=True, name=None, package=None,
306
306
  In verbose mode, the summary is detailed, else very brief (in fact,
307
307
  empty if all tests passed)
308
308
  Default is True.
309
- verbose : int
309
+ verbose : int
310
310
  Control the run verbosity:
311
311
  0 means only report failures,
312
312
  1 means emit object names,
@@ -335,7 +335,7 @@ def testfile(filename, module_relative=True, name=None, package=None,
335
335
  (result, history)
336
336
  `result` is a namedtuple ``TestResult(failed, attempted)``
337
337
  `history` is a dict with details of which objects were examined (the
338
- keys are object names and values are individual objects' ``TestResult``s)
338
+ keys are object names and values are individual objects' ``TestResult`` s)
339
339
  """
340
340
  # initial configuration
341
341
  if config is None:
@@ -478,4 +478,3 @@ def _main():
478
478
  if result.failed:
479
479
  return 1
480
480
  return 0
481
-
@@ -4,19 +4,12 @@ import inspect
4
4
  import doctest
5
5
  from doctest import NORMALIZE_WHITESPACE, ELLIPSIS, IGNORE_EXCEPTION_DETAIL
6
6
  from itertools import zip_longest
7
+ from sys import version_info
7
8
 
8
9
  import numpy as np
9
10
 
10
11
  from . import util
11
12
 
12
-
13
- ## shim numpy 1.x vs 2.0
14
- if np.__version__ < "2":
15
- VisibleDeprecationWarning = np.VisibleDeprecationWarning
16
- else:
17
- VisibleDeprecationWarning = np.exceptions.VisibleDeprecationWarning
18
-
19
-
20
13
  # Register the optionflag to skip whole blocks, i.e.
21
14
  # sequences of Examples without an intervening text.
22
15
  SKIPBLOCK = doctest.register_optionflag('SKIPBLOCK')
@@ -68,6 +61,7 @@ class DTConfig:
68
61
  >>> for test in tests:
69
62
  ... with user_context(test):
70
63
  ... runner.run(test)
64
+
71
65
  Default is a noop.
72
66
  local_resources: dict
73
67
  If a test needs some local files, list them here. The format is
@@ -190,7 +184,8 @@ class DTConfig:
190
184
  '.bar(', '.title', '.ylabel', '.xlabel', 'set_ylim', 'set_xlim',
191
185
  '# reformatted', '.set_xlabel(', '.set_ylabel(', '.set_zlabel(',
192
186
  '.set(xlim=', '.set(ylim=', '.set(xlabel=', '.set(ylabel=', '.xlim(',
193
- 'ax.set('}
187
+ 'ax.set(', '.text(',
188
+ }
194
189
  self.stopwords = stopwords
195
190
 
196
191
  if pseudocode is None:
@@ -224,8 +219,13 @@ class DTConfig:
224
219
 
225
220
 
226
221
  def try_convert_namedtuple(got):
227
- # suppose that "got" is smth like MoodResult(statistic=10, pvalue=0.1).
228
- # Then convert it to the tuple (10, 0.1), so that can later compare tuples.
222
+ """
223
+ Converts a string representation of a named tuple into a plain tuple.
224
+
225
+ Suppose that "got" is smth like MoodResult(statistic=10, pvalue=0.1).
226
+ Then convert it to the tuple (10, 0.1), so that can later compare tuples.
227
+ """
228
+
229
229
  num = got.count('=')
230
230
  if num == 0:
231
231
  # not a nameduple, bail out
@@ -264,6 +264,8 @@ def try_convert_printed_array(got):
264
264
 
265
265
 
266
266
  def has_masked(got):
267
+ """Check if a given string represents a NumPy masked array.
268
+ """
267
269
  return 'masked_array' in got and '--' in got
268
270
 
269
271
 
@@ -291,6 +293,20 @@ def try_split_shape_from_abbrv(s_got):
291
293
 
292
294
 
293
295
  class DTChecker(doctest.OutputChecker):
296
+ """
297
+ A drop-in replacement for `doctest.OutputChecker`.
298
+
299
+ Allows robust output comparison for numerical values and special
300
+ cases involving NumPy arrays, masked arrays, namedtuples, and object
301
+ memory addresses. It is configurable via a `DTConfig` object.
302
+
303
+ Parameters:
304
+ -----------
305
+ config : DTConfig, optional
306
+ Configuration object that controls various aspects of output checking.
307
+ If not provided, a default `DTConfig` instance is used.
308
+
309
+ """
294
310
  obj_pattern = re.compile(r'at 0x[0-9a-fA-F]+>')
295
311
  vanilla = doctest.OutputChecker()
296
312
 
@@ -331,13 +347,8 @@ class DTChecker(doctest.OutputChecker):
331
347
  # OK then, convert strings to objects
332
348
  ns = dict(self.config.check_namespace)
333
349
  try:
334
- with warnings.catch_warnings():
335
- # NumPy's ragged array deprecation of np.array([1, (2, 3)]);
336
- # also array abbreviations: try `np.diag(np.arange(1000))`
337
- warnings.simplefilter('ignore', VisibleDeprecationWarning)
338
-
339
- a_want = eval(want, dict(ns))
340
- a_got = eval(got, dict(ns))
350
+ a_want = eval(want, dict(ns))
351
+ a_got = eval(got, dict(ns))
341
352
  except Exception:
342
353
  # Maybe we're printing a numpy array? This produces invalid python
343
354
  # code: `print(np.arange(3))` produces "[0 1 2]" w/o commas between
@@ -351,9 +362,9 @@ class DTChecker(doctest.OutputChecker):
351
362
  s_got = try_convert_printed_array(s_got)
352
363
 
353
364
  return self.check_output(s_want, s_got, optionflags)
354
-
365
+
355
366
  #handle array abbreviation for n-dimensional arrays, n >= 1
356
- ndim_array = (s_want.startswith("array([") and "..." in s_want and
367
+ ndim_array = (s_want.startswith("array([") and "..." in s_want and
357
368
  s_got.startswith("array([") and "..." in s_got)
358
369
  if ndim_array:
359
370
  s_want, want_shape = try_split_shape_from_abbrv(s_want)
@@ -432,16 +443,31 @@ class DTChecker(doctest.OutputChecker):
432
443
  except Exception:
433
444
  pass
434
445
 
435
- with warnings.catch_warnings():
436
- # NumPy's ragged array deprecation of np.array([1, (2, 3)])
437
- warnings.simplefilter('ignore', VisibleDeprecationWarning)
438
-
439
- # This line is the crux of the whole thing. The rest is mostly scaffolding.
440
- result = np.allclose(want, got, atol=self.atol, rtol=self.rtol, equal_nan=True)
446
+ # This line is the crux of the whole thing. The rest is mostly scaffolding.
447
+ result = np.allclose(want, got, atol=self.atol, rtol=self.rtol, equal_nan=True)
441
448
  return result
442
449
 
443
450
 
444
451
  class DTRunner(doctest.DocTestRunner):
452
+ """
453
+ A drop-in replacement for `doctest.DocTestRunner`.
454
+
455
+ Improves how test names are reported and allows better control over exception handling.
456
+ It integrates with `DTConfig` to apply customized settings for doctest execution.
457
+
458
+ Parameters:
459
+ -----------
460
+ checker : doctest.OutputChecker, optional
461
+ A custom output checker, defaults to `DTConfig.CheckerKlass(config)`.
462
+ verbose : bool, optional
463
+ If `True`, enables verbose output.
464
+ optionflags : int, optional
465
+ Bitwise OR of `doctest` option flags; defaults to `DTConfig.optionflags`.
466
+ config : DTConfig, optional
467
+ A configuration object controlling doctest behavior; a default instance is used if not provided.
468
+
469
+ """
470
+
445
471
  DIVIDER = "\n"
446
472
 
447
473
  def __init__(self, checker=None, verbose=None, optionflags=None, config=None):
@@ -489,15 +515,20 @@ class DTRunner(doctest.DocTestRunner):
489
515
  def get_history(self):
490
516
  """Return a dict with names of items which were run.
491
517
 
492
- Actually the dict is `{name : (f, t)}`, where `name` is the name of
493
- an object, and the value is a tuple of the numbers of examples which
494
- failed and which were tried.
518
+ Actually the dict is `{name : (failures, tries, skips)}` (before Python
519
+ 3.13, just `{name : (failures, tries, skips)}`) where `name` is the
520
+ name of an object, and the value is a tuple of the numbers of examples
521
+ which failed, were tried, and were skipped, respectively.
495
522
  """
496
- return self._name2ft
523
+ if version_info >= (3, 13):
524
+ return self._stats
525
+ else:
526
+ return self._name2ft
497
527
 
498
528
 
499
529
  class DebugDTRunner(DTRunner):
500
- """Doctest runner which raises on a first error.
530
+ """
531
+ Doctest runner which raises an exception on the first error.
501
532
 
502
533
  Almost verbatim copy of `doctest.DebugRunner`.
503
534
  """
@@ -521,8 +552,24 @@ class DebugDTRunner(DTRunner):
521
552
 
522
553
 
523
554
  class DTFinder(doctest.DocTestFinder):
524
- """A Finder with helpful defaults.
525
555
  """
556
+ A drop-in replacement for `doctest.DocTestFinder` with helpful defaults.
557
+
558
+ Parameters:
559
+ -----------
560
+ verbose : bool, optional
561
+ If `True`, enables verbose output during doctest discovery.
562
+ parser : doctest.DocTestParser, optional
563
+ A custom parser for extracting doctests; defaults to `DTParser(config)`.
564
+ recurse : bool, default=True
565
+ Whether to recursively search for doctests in nested objects.
566
+ exclude_empty : bool, default=True
567
+ Whether to exclude objects that have no doctests.
568
+ config : DTConfig, optional
569
+ A configuration object controlling doctest behavior; a default instance is used if not provided.
570
+
571
+ """
572
+
526
573
  def __init__(self, verbose=None, parser=None, recurse=True,
527
574
  exclude_empty=True, config=None):
528
575
  if config is None:
@@ -537,7 +584,7 @@ class DTFinder(doctest.DocTestFinder):
537
584
  if globs is None:
538
585
  globs = dict(self.config.default_namespace)
539
586
  # XXX: does this make similar checks in testmod/testfile duplicate?
540
- if module not in self.config.skiplist:
587
+ if module not in self.config.skiplist:
541
588
  tests = super().find(obj, name, module, globs, extraglobs)
542
589
 
543
590
  if inspect.isclass(obj):
@@ -550,8 +597,21 @@ class DTFinder(doctest.DocTestFinder):
550
597
 
551
598
 
552
599
  class DTParser(doctest.DocTestParser):
553
- """A Parser with a stopword list.
554
600
  """
601
+ A drop-in replacement for `doctest.DocTestParser` with a stopword list.
602
+
603
+ It filters out stopwords, pseudocode, and random markers from doctests.
604
+
605
+ Parameters:
606
+ -----------
607
+ config : DTConfig, optional
608
+ A configuration object containing:
609
+ - `stopwords`: A list of words that signal a doctest should be ignored.
610
+ - `pseudocode`: A list of markers indicating non-executable code.
611
+ - `rndm_markers`: A list of markers indicating results may vary.
612
+
613
+ """
614
+
555
615
  def __init__(self, config=None):
556
616
  if config is None:
557
617
  config = DTConfig()
@@ -608,4 +668,3 @@ class DTParser(doctest.DocTestParser):
608
668
  example.want += " # _ignore\n"
609
669
  examples.append(example)
610
670
  return examples
611
-
@@ -47,7 +47,7 @@ def pytest_ignore_collect(collection_path, config):
47
47
  """
48
48
  Determine whether to ignore the specified collection path.
49
49
  This function is used to exclude the 'tests' directory and test modules when
50
- the '--doctest-modules' option is used.
50
+ the `--doctest-modules` option is used.
51
51
  """
52
52
  if config.getoption("--doctest-modules"):
53
53
  path_str = str(collection_path)
@@ -62,7 +62,7 @@ def pytest_ignore_collect(collection_path, config):
62
62
  def is_private(item):
63
63
  """Decide if an DocTestItem `item` is private.
64
64
 
65
- Private items are ignored in pytest_collect_modifyitem`.
65
+ Private items are ignored in `pytest_collect_modifyitem`.
66
66
  """
67
67
  # Here we look at the name of a test module/object. A seemingly less
68
68
  # hacky alternative is to populate a set of seen `item.dtest` attributes
@@ -174,7 +174,7 @@ def _is_deprecated(module):
174
174
  class DTModule(DoctestModule):
175
175
  """
176
176
  This class extends the DoctestModule class provided by pytest.
177
-
177
+
178
178
  DTModule is responsible for overriding the behavior of the collect method.
179
179
  The collect method is called by pytest to collect and generate test items for doctests
180
180
  in the specified module or file.
@@ -222,7 +222,7 @@ class DTModule(DoctestModule):
222
222
 
223
223
  optionflags = dt_config.optionflags
224
224
 
225
- # Plug in the custom runner: `PytestDTRunner`
225
+ # Plug in the custom runner: `PytestDTRunner`
226
226
  runner = _get_runner(self.config,
227
227
  verbose=False,
228
228
  optionflags=optionflags,
@@ -245,7 +245,7 @@ class DTModule(DoctestModule):
245
245
  class DTTextfile(DoctestTextfile):
246
246
  """
247
247
  This class extends the DoctestTextfile class provided by pytest.
248
-
248
+
249
249
  DTTextfile is responsible for overriding the behavior of the collect method.
250
250
  The collect method is called by pytest to collect and generate test items for doctests
251
251
  in the specified text files.
@@ -282,7 +282,7 @@ class DTTextfile(DoctestTextfile):
282
282
  def _get_runner(config, verbose, optionflags):
283
283
  """
284
284
  Override function to return an instance of PytestDTRunner.
285
-
285
+
286
286
  This function creates and returns an instance of PytestDTRunner, a custom runner class
287
287
  that extends the behavior of DebugDTRunner for running doctests in pytest.
288
288
  """
@@ -290,13 +290,13 @@ def _get_runner(config, verbose, optionflags):
290
290
  def run(self, test, compileflags=None, out=None, clear_globs=False):
291
291
  """
292
292
  Run tests in context managers.
293
-
293
+
294
294
  Restore the errstate/print state after each docstring.
295
295
  Also, make MPL backend non-GUI and close the figures.
296
-
296
+
297
297
  The order of context managers is actually relevant. Consider
298
298
  user_context_mgr that turns warnings into errors.
299
-
299
+
300
300
  Additionally, suppose that MPL deprecates something and plt.something
301
301
  starts issuing warnings. Now all of those become errors
302
302
  *unless* the `mpl()` context mgr has a chance to filter them out
@@ -24,4 +24,3 @@ def func_name_error():
24
24
  Further name errors are also suppressed (which is a bug, too):
25
25
  >>> raise NameError('Also legit')
26
26
  """
27
-
@@ -61,5 +61,3 @@ def _underscored_private_func():
61
61
  >>> 9 + 12
62
62
  21
63
63
  """
64
-
65
-
@@ -33,5 +33,3 @@ def sio():
33
33
  [ 2., 5., 8., 11.],
34
34
  [ 3., 6., 9., 12.]]])}
35
35
  """
36
-
37
-
@@ -3,8 +3,6 @@ __all__ = [
3
3
  'func7', 'manip_printoptions', 'array_abbreviation'
4
4
  ]
5
5
 
6
- import numpy as np
7
- import pytest
8
6
 
9
7
  def func():
10
8
  """
@@ -215,7 +215,7 @@ Smoothing filters
215
215
  corresponds to convolution with a Gaussian kernel. An order of 1, 2,
216
216
  or 3 corresponds to convolution with the first, second, or third
217
217
  derivatives of a Gaussian. Higher-order derivatives are not
218
- implemented.
218
+ implemented.
219
219
 
220
220
 
221
221
 
@@ -232,7 +232,7 @@ Smoothing filters
232
232
  number, to specify the same order for all axes, or a sequence of
233
233
  numbers to specify a different order for each axis. The example below
234
234
  shows the filter applied on test data with different values of *sigma*.
235
- The *order* parameter is kept at 0.
235
+ The *order* parameter is kept at 0.
236
236
 
237
237
  .. plot:: tutorial/examples/gaussian_filter_plot1.py
238
238
  :align: center
@@ -86,8 +86,8 @@ class TestSkiplist:
86
86
  assert sorted(names) == sorted(wanted_names)
87
87
 
88
88
  def test_get_doctests_strategy_None(self):
89
- # Add a skiplist: strategy=None skips listed items
90
- base = finder_cases.__name__
89
+ # Add a skiplist: strategy=None skips listed items
90
+ base = finder_cases.__name__
91
91
  skips = [base + '.func', base + '.Klass.meth_2']
92
92
  config = DTConfig(skiplist=skips)
93
93
 
@@ -102,8 +102,8 @@ class TestSkiplist:
102
102
  assert sorted(names) == sorted(wanted_names)
103
103
 
104
104
  def test_get_doctests_strategy_api(self):
105
- # Add a skiplist: strategy='api' skips listed items
106
- base = finder_cases.__name__
105
+ # Add a skiplist: strategy='api' skips listed items
106
+ base = finder_cases.__name__
107
107
  skips = [base + '.func', base + '.Klass.meth_2']
108
108
  config = DTConfig(skiplist=skips)
109
109
 
@@ -120,8 +120,8 @@ class TestSkiplist:
120
120
  assert sorted(names) == sorted(wanted_names)
121
121
 
122
122
  def test_get_doctests_strategy_list(self):
123
- # Add a skiplist: strategy=<list> skips listed items
124
- base = finder_cases.__name__
123
+ # Add a skiplist: strategy=<list> skips listed items
124
+ base = finder_cases.__name__
125
125
  skips = [base + '.func', base + '.Klass.meth_2']
126
126
  config = DTConfig(skiplist=skips)
127
127
 
@@ -217,6 +217,3 @@ def test_private_superclasses_2():
217
217
 
218
218
  assert len(tests) == len(expected_names)
219
219
  assert names == set(expected_names)
220
-
221
-
222
-
@@ -31,6 +31,3 @@ def test_config_nocopy():
31
31
  config = DTConfig()
32
32
  parser = DTParser(config)
33
33
  assert parser.config is config
34
-
35
-
36
-
@@ -39,7 +39,7 @@ def test_failure_cases(pytester):
39
39
  result = pytester.inline_run(python_file, "--doctest-modules")
40
40
  assert result.ret == pytest.ExitCode.TESTS_FAILED
41
41
 
42
-
42
+
43
43
  @pytest.mark.skipif(not HAVE_MATPLOTLIB, reason='need matplotlib')
44
44
  def test_stopword_cases(pytester):
45
45
  """Test that pytest uses the DTParser for doctests."""
@@ -90,4 +90,3 @@ def test_alt_checker(pytester):
90
90
  # run all tests with pytest
91
91
  result = pytester.inline_run(f, '--doctest-modules')
92
92
  assert result.ret == pytest.ExitCode.TESTS_FAILED
93
-
@@ -102,4 +102,3 @@ class TestCheckerDropIn:
102
102
  with pytest.raises(doctest.DocTestFailure):
103
103
  for t in tests:
104
104
  runner.run(t)
105
-
@@ -1,4 +1,5 @@
1
1
  import doctest
2
+ from sys import version_info
2
3
 
3
4
  try:
4
5
  import matplotlib.pyplot as plt # noqa
@@ -57,7 +58,8 @@ class TestSyntaxErrors:
57
58
  lineno=0)
58
59
  runner = DebugDTRunner()
59
60
  runner.run(test)
60
- assert runner.get_history() == {'none : +SKIP': (0, 0)}
61
+ stats = (0, 1, 1) if version_info >= (3, 13) else (0, 0)
62
+ assert runner.get_history() == {'none : +SKIP': stats}
61
63
 
62
64
  def test_invalid_python_pseudocode(self):
63
65
  # Marking a test as pseudocode is equivalent to a +SKIP:
@@ -74,7 +76,8 @@ class TestSyntaxErrors:
74
76
  lineno=0)
75
77
  runner = DebugDTRunner()
76
78
  runner.run(test)
77
- assert runner.get_history() == {'none : pseudocode': (0, 0)}
79
+ stats = (0, 1, 1) if version_info >= (3, 13) else (0, 0)
80
+ assert runner.get_history() == {'none : pseudocode': stats}
78
81
 
79
82
 
80
83
  class TestPseudocodeMarkers:
@@ -125,7 +128,8 @@ class TestStopwords:
125
128
  runner.run(test)
126
129
 
127
130
  # one example tried, of which zero failed
128
- assert runner.get_history() == {'stopwords_bogus_output': (0, 1)}
131
+ stats = (0, 1, 0) if version_info >= (3, 13) else (0, 1)
132
+ assert runner.get_history() == {'stopwords_bogus_output': stats}
129
133
 
130
134
 
131
135
  class TestMayVary:
@@ -148,7 +152,8 @@ class TestMayVary:
148
152
  runner.run(test)
149
153
 
150
154
  # one example tried, of which zero failed
151
- assert runner.get_history() == {'may_vary_markers': (0, 1)}
155
+ stats = (0, 1, 0) if version_info >= (3, 13) else (0, 1)
156
+ assert runner.get_history() == {'may_vary_markers': stats}
152
157
 
153
158
  def test_may_vary_source(self):
154
159
  # The marker needs to be added to the example output, not source.
@@ -164,7 +169,8 @@ class TestMayVary:
164
169
  runner.run(test)
165
170
 
166
171
  # one example tried, of which zero failed
167
- assert runner.get_history() == {'may_vary_source': (0, 1)}
172
+ stats = (0, 1, 0) if version_info >= (3, 13) else (0, 1)
173
+ assert runner.get_history() == {'may_vary_source': stats}
168
174
 
169
175
  def test_may_vary_syntax_error(self):
170
176
  # `# may vary` markers do not mask syntax errors, unlike `# doctest: +SKIP`
@@ -193,7 +199,7 @@ below
193
199
 
194
200
  Note how the block above will fail doctesting unless the second line is
195
201
  skipped. A standard solution is to add a +SKIP marker to every line, but this
196
- is ugly and we skip the whole block instead.
202
+ is ugly and we skip the whole block instead.
197
203
 
198
204
  Once the block is over, we get back to usual doctests, which are not skipped
199
205
 
@@ -216,4 +222,3 @@ def test_SKIPBLOCK():
216
222
  assert test.examples[0].options[SKIP] is True
217
223
  assert test.examples[1].options[SKIP] is True
218
224
  assert test.examples[2].options == {} # not skipped
219
-
@@ -53,7 +53,7 @@ def test_stopwords():
53
53
  res, _ = _testmod(stopwords, verbose=_VERBOSE)
54
54
  assert res.failed == 0
55
55
  assert res.attempted != 0
56
-
56
+
57
57
 
58
58
  @pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
59
59
  def test_public_obj_discovery():
@@ -260,9 +260,9 @@ modules = []
260
260
  def generate_log(module, test):
261
261
  """
262
262
  Generate a log of the doctested items.
263
-
263
+
264
264
  This function logs the items being doctested to a file named 'doctest.log'.
265
-
265
+
266
266
  Args:
267
267
  module (module): The module being doctested.
268
268
  test (str): The name of the doctest item.
@@ -276,4 +276,3 @@ def generate_log(module, test):
276
276
  LOGFILE.write(f"{test}\n")
277
277
  except AttributeError:
278
278
  LOGFILE.write(f"{test}\n")
279
-
File without changes