scipy-doctest 1.1__py3-none-any.whl

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.
@@ -0,0 +1,390 @@
1
+ Metadata-Version: 2.1
2
+ Name: scipy_doctest
3
+ Version: 1.1
4
+ Summary: Doctests on steroids.
5
+ Maintainer-email: SciPy developers <scipy-dev@python.org>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: License :: OSI Approved :: BSD License
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Framework :: Pytest
13
+ Requires-Dist: numpy>=1.19.5
14
+ Requires-Dist: pytest
15
+ Requires-Dist: scipy ; extra == "test"
16
+ Requires-Dist: matplotlib ; extra == "test"
17
+ Project-URL: Home, https://github.com/ev-br/scpdt
18
+ Provides-Extra: test
19
+
20
+ # Floating-point aware, human readable, numpy-compatible doctesting.
21
+
22
+ ## Motivation and scope
23
+
24
+ Having examples in the documentation is great. Having wrong examples in the
25
+ documentation is not that great however.
26
+
27
+ The standard library `doctest` module is great for making sure that docstring
28
+ examples are correct. However, the `doctest` module is limited in several
29
+ respects. Consider:
30
+
31
+ ```
32
+ >>> np.array([1/3, 2/3, 3/3]) # doctest: +SKIP
33
+ array([0.333, 0.669, 1])
34
+ ```
35
+
36
+ This looks reasonably clear but does not work, in three different ways.
37
+ _First_, `1/3` is not equal to 0.333 because floating-point arithmetic.
38
+ _Second_, `numpy` adds whitespace to its output, this whitespace confuses the
39
+ `doctest`, which is whitespace-sensitive. Therefore, we added a magic directive,
40
+ `+SKIP` to avoid a doctest error. _Third_, the example is actually
41
+ wrong---notice `0.669` which is not equal to `2/3` to three sig figs. The error
42
+ went unnoticed by the doctester also because of the `+SKIP` directive.
43
+
44
+ We believe these `# doctest: +SKIP` directives do not add any value to
45
+ a human reader, and should not be present in the documentation.
46
+
47
+ This package defines modified doctesting routines which fix these deficiencies.
48
+ Its main features are
49
+
50
+ - *Doctesting is floating-point aware.* In a nutshell, the core check is
51
+ `np.allclose(want, got, atol=..., rtol=...)`, with user-controllable abs
52
+ and relative tolerances. In the example above (_sans_ `# doctest: +SKIP`),
53
+ `want` is the desired output, `array([0.333, 0.669, 1])` and `got` is the
54
+ actual output from numpy: `array([0.33333333, 0.66666667, 1. ])`.
55
+
56
+ - *Human-readable skip markers.* Consider
57
+ ```
58
+ >>> np.random.randint(100)
59
+ 42 # may vary
60
+ ```
61
+ Note that the markers (by default, `"# may vary"` and `"# random"`) are applied
62
+ to an example's output, not its source.
63
+
64
+ Also note a difference with respect to the standard `# doctest: +SKIP`: the latter
65
+ skips the example entirely, while these additional markers only skip checking
66
+ the output. Thus the example source needs to be valid python code still.
67
+
68
+ - A user-configurable list of *stopwords*. If an example contains a stopword,
69
+ it is checked to be valid python, but the output is not checked. This can
70
+ be useful e.g. for not littering the documentation with the output of
71
+ `import matplotlib.pyplot as plt; plt.xlim([2.3, 4.5])`.
72
+
73
+ - A user-configurable list of *pseudocode* markers. If an example contains one
74
+ of these markers, it is considered pseudocode and is not checked.
75
+ This is useful for `from example import some_functions` and similar stanzas.
76
+
77
+ - A `# doctest: +SKIPBLOCK` option flag to skip whole blocks of pseudocode. Here
78
+ a 'block' is a sequence of doctest examples without any intervening text.
79
+
80
+ - *Doctest discovery* is somewhat more flexible then the standard library
81
+ `doctest` module. Specifically, one can use `testmod(module, strategy='api')`
82
+ to only examine public objects of a module. This is helpful for complex
83
+ packages, with non-trivial internal file structure. Alternatively, the default
84
+ value of `strategy=None` is equivalent to the standard `doctest` module
85
+ behavior.
86
+
87
+ - *User configuration*. Essentially all aspects of the behavior are user
88
+ configurable via a `DTConfig` instance attributes. See the `DTConfig`
89
+ docstring for details.
90
+
91
+ ## Install and test
92
+
93
+ ```
94
+ $ pip install -e .
95
+ $ pytest --pyargs scpdt
96
+ ```
97
+
98
+ ## Usage
99
+
100
+ The API of the package has two layers: the basic layer follows the API of the
101
+ standard library `doctest` module, and we strive to provide drop-in replacements,
102
+ or nearly so.
103
+
104
+ The other layer is the `pytest` plugin.
105
+
106
+
107
+ ### Basic usage
108
+
109
+ For example,
110
+
111
+ ```
112
+ >>> from scipy import linalg
113
+ >>> from scpdt import testmod
114
+ >>> res, hist = testmod(linalg, strategy='api')
115
+ >>> res
116
+ TestResults(failed=0, attempted=764)
117
+ ```
118
+ The second return value, `hist` is a dict which maps the names of the objects
119
+ to the numbers of failures and attempts for individual examples.
120
+
121
+ For more details, see the `testmod` docstring. Other useful functions are
122
+ `find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
123
+ the behavior of the eponymous functions of the `doctest` module).
124
+
125
+ #### Command-line interface
126
+
127
+ There is a basic CLI, which also mimics that of the `doctest` module:
128
+ ```
129
+ $ python -m scpdt foo.py
130
+ ```
131
+
132
+ Note that, just like `$ python -m doctest foo.py`, this may
133
+ fail if `foo.py` is a part of a package due to package imports.
134
+
135
+ Text files can also be CLI-checked:
136
+ ```
137
+ $ python -m scpdt bar.rst
138
+ ```
139
+
140
+
141
+ #### More fine-grained control
142
+
143
+ More fine-grained control of the functionality is available via the following
144
+ classes
145
+
146
+ | Class | `doctest` analog |
147
+ |-------------|--------------------|
148
+ | `DTChecker` | `DocTestChecker` |
149
+ | `DTParser` | `DocTestParser` |
150
+ | `DTRunner` | `DocTestRunner` |
151
+ | `DTFinder` | `DocTestFinder` |
152
+ | `DTContext` | -- |
153
+
154
+ The `DTContext` class is just a bag class which holds various configuration
155
+ settings as attributes. An instance of this class is passed around, so user
156
+ configuration is simply creating an instance, overriding an attribute and
157
+ passing the instance to `testmod` or constructors of `DT*` objects. Defaults
158
+ are provided, based on a long-term usage in SciPy.
159
+
160
+
161
+ ### The Scpdt Pytest Plugin
162
+
163
+ The pytest plugin enables the use of scpdt tools to perform doctests.
164
+
165
+ Follow the given instructions to utilize the pytest plugin for doctesting.
166
+
167
+ ### Running doctests on Scipy
168
+ 1. **Install plugin**
169
+
170
+ Start by installing the pytest plugin via pip:
171
+
172
+ ```bash
173
+ pip install git+https://github.com/ev-br/scpdt.git@main
174
+ ```
175
+
176
+ 2. **Configure Your Doctesting Experience**
177
+
178
+ To tailor your doctesting experience, you can utilize an instance of `DTConfig`.
179
+ An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/ev-br/scpdt#tailoring-your-doctesting-experience) section.
180
+
181
+ 3. **Run Doctests**
182
+
183
+ Doctesting is configured to execute on SciPy using the `dev.py` module.
184
+
185
+ To run all doctests, use the following command:
186
+ ```bash
187
+ python dev.py test --doctests
188
+ ```
189
+
190
+ To run doctests on specific SciPy modules, e.g: `cluster`, use the following command:
191
+
192
+ ```bash
193
+ python dev.py test --doctests -s cluster
194
+ ```
195
+
196
+ ### Running Doctests on Other Packages/Projects
197
+
198
+ If you want to run doctests on packages or projects other than SciPy, follow these steps:
199
+
200
+ 1. **Install the plugin**
201
+
202
+ ```bash
203
+ pip install git+https://github.com/ev-br/scpdt.git@main
204
+ ```
205
+
206
+ 2. **Register or Load the Plugin**
207
+
208
+ Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
209
+
210
+ To do this, add the following line of code:
211
+
212
+ ```python
213
+ # In your conftest.py file or test module
214
+
215
+ pytest_plugins = "scpdt"
216
+ ```
217
+
218
+ 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.
219
+
220
+ 3. **Configure your doctesting experience**
221
+
222
+ An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/ev-br/scpdt#tailoring-your-doctesting-experience) section.
223
+
224
+ 4. **Run doctests**
225
+
226
+ Once the plugin is registered, you can run your doctests by executing the following command:
227
+
228
+ ```bash
229
+ $ python -m pytest --doctest-modules
230
+ ```
231
+ or
232
+ ```bash
233
+ $ pytest --pyargs <your-package> --doctest-modules
234
+ ```
235
+
236
+ By default, all doctests are collected. To only collect public objects, `strategy="api"`,
237
+ use the command flag
238
+
239
+ ```bash
240
+ $ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
241
+ ```
242
+
243
+ ### Tailoring Your Doctesting Experience
244
+
245
+ [DTConfig](https://github.com/ev-br/scpdt/blob/671083d65b54111770cee71c9bc790ac652d59ab/scpdt/impl.py#L16) offers a variety of attributes that allow you to fine-tune your doctesting experience.
246
+
247
+ These attributes include:
248
+ 1. **default_namespace (dict):** Defines the namespace in which examples are executed.
249
+ 2. **check_namespace (dict):** Specifies the namespace for conducting checks.
250
+ 3. **rndm_markers (set):** Provides additional directives which act like `# doctest: + SKIP`.
251
+ 4. **atol (float) and rtol (float):** Sets absolute and relative tolerances for validating doctest examples.
252
+ Specifically, it governs the check using `np.allclose(want, got, atol=atol, rtol=rtol)`.
253
+ 5. **optionflags (int):** These are doctest option flags.
254
+ The default setting includes `NORMALIZE_WHITESPACE` | `ELLIPSIS` | `IGNORE_EXCEPTION_DETAIL`.
255
+ 6. **stopwords (set):** If an example contains any of these stopwords, the output is not checked (though the source's validity is still assessed).
256
+ 7. **pseudocode (list):** Lists strings that, when found in an example, result in no doctesting. This resembles the `# doctest +SKIP` directive and is useful for pseudocode blocks or similar cases.
257
+ 8. **skiplist (set):** A list of names of objects with docstrings known to fail doctesting and are intentionally excluded from testing.
258
+ 9. **user_context_mgr:** A context manager for running tests.
259
+ Typically, it is entered for each DocTest (especially in API documentation), ensuring proper testing isolation.
260
+ 10. **local_resources (dict):** Specifies local files needed for specific tests. The format is `{test.name: list-of-files}`. File paths are relative to the path of `test.filename`.
261
+ 11. **parse_namedtuples (bool):** Determines whether to perform a literal comparison (e.g., `TTestResult(pvalue=0.9, statistic=42)`) or extract and compare the tuple values (e.g., `(0.9, 42)`). The default is `True`.
262
+ 12. **nameerror_after_exception (bool):** Controls whether subsequent examples in the same test, after one has failed, may raise spurious NameErrors. Set to `True` if you want to observe these errors or if your test is expected to raise NameErrors. The default is `False`.
263
+
264
+ To set any of these attributes, create an instance of `DTConfig` called `dt_config`.
265
+ This instance is already set as an [attribute of pytest's `Config` object](https://github.com/ev-br/scpdt/blob/671083d65b54111770cee71c9bc790ac652d59ab/scpdt/plugin.py#L27).
266
+
267
+ **Example:**
268
+
269
+ ```python
270
+ dt_config = DTConfig()
271
+ dt_config.stopwords = {'plt.', '.hist', '.show'}
272
+ dt_config.local_resources = {
273
+ 'scpdt.tests.local_file_cases.local_files': ['scpdt/tests/local_file.txt'],
274
+ 'scpdt.tests.local_file_cases.sio': ['scpdt/tests/octave_a.mat']
275
+ }
276
+ dt_config.skiplist = {
277
+ 'scipy.special.sinc',
278
+ 'scipy.misc.who',
279
+ 'scipy.optimize.show_options'
280
+ }
281
+ ```
282
+
283
+ If you don't set these attributes, the [default settings](https://github.com/ev-br/scpdt/blob/671083d65b54111770cee71c9bc790ac652d59ab/scpdt/impl.py#L73) of the attributes are used.
284
+
285
+ By following these steps, you will be able to effectively use the Scpdt pytest plugin for doctests in your Python projects.
286
+
287
+ Happy testing!
288
+
289
+ ## Rough edges and sharp bits
290
+
291
+ Here is a (non-exhaustive) list of possible gotchas:
292
+
293
+ - *In-place development builds*.
294
+
295
+ Some tools (looking at you `meson-python`) simulate in-place builds with a
296
+ `build-install` directory. If this directory is located under the project root,
297
+ `pytest` is getting confused by duplicated items under the root and build-install
298
+ folders.
299
+
300
+ The solution is to make pytest only look into the `build-install` directory
301
+ (the specific path to `build-install` may vary):
302
+
303
+ ```
304
+ $ pytest build-install/lib/python3.10/site-packages/scipy/ --doctest-modules
305
+ ```
306
+
307
+ instead of `$ pytest --pyargs scipy`.
308
+
309
+ If push comes to shove, you may try using the magic env variable:
310
+ ` PY_IGNORE_IMPORTMISMATCH=1 pytest ...`,
311
+ however the need usually indicates an issue with the package itself.
312
+ (see [gh-107](https://github.com/ev-br/scpdt/pull/107) for an example).
313
+
314
+ - *Optional dependencies are not that optional*
315
+
316
+ If your package contains optional dependencies, doctests do not know about them
317
+ being optional. So you either guard the imports in doctests (yikes!), or
318
+ the collections fails if dependencies are not available.
319
+
320
+ The solution is to explicitly `--ignore` the paths to modules with optionals.
321
+ (or use `DTConfig.pytest_extra_ignore` list):
322
+
323
+ ```
324
+ $ pytest --ignore=/build-install/lib/scipy/python3.10/site-packages/scipy/_lib ...
325
+ ```
326
+
327
+ Note that installed packages are no different:
328
+
329
+ ```
330
+ $ pytest --pyargs scipy --doctest-modules --ignore=/path/to/installed/scipy/_lib
331
+ ```
332
+
333
+ - *Doctest collection strategies*
334
+
335
+ The default collection strategy follows `doctest` module and `pytest`. This leads
336
+ to duplicates if your package has the split between public and \_private modules,
337
+ where public modules re-export things from private ones. The solution is to
338
+ use `$ pytest --doctest-collect=api` CLI switch: with this, only public
339
+ objects will be collected.
340
+
341
+ The decision on what is public is as follows: an object is public iff
342
+
343
+ - it is included into the `__all__` list of a public module;
344
+ - the name of the object does not have a leading underscore;
345
+ - the name of the module from which the object is collected does not have
346
+ a leading underscore.
347
+
348
+ Consider an example: `scipy.linalg.det` is defined in `scipy/linalg/_basic.py`,
349
+ so it is collected twice, from `_basic.py` and from `__init__.py`. The rule above
350
+ leads to
351
+
352
+ - `scipy.linalg._basic.det`, collected from `scipy/linalg/_basic.py`, is private.
353
+ - `scipy.linalg.det`, collected from `scipy/linalg/__init__.py`, is public.
354
+
355
+
356
+ ## Prior art and related work
357
+
358
+ - `pytest` provides some limited floating-point aware `NumericLiteralChecker`.
359
+
360
+ - `pytest-doctestplus` plugin from the `AstroPy` project has similar goals.
361
+ The package is well established and widely used. From a user perspective, main
362
+ differences are: (i) `pytest-doctestplus` is more sensitive to formatting,
363
+ including whitespace---thus if numpy tweaks its output formatting, doctests
364
+ may start failing; (ii) there is still a need for `# doctest: +FLOAT_CMP`
365
+ directives; (iii) being a pytest plugin, `pytest-doctestplus` is tightly
366
+ coupled to `pytest`. It thus needs to follow `pytest` releases, and
367
+ some maintenance work may be required to adapt when `pytest` publishes a new
368
+ release.
369
+
370
+ This project takes a different approach: in addition to plugging into `pytest`,
371
+ we closely follow the `doctest` API and implementation, which are naturally
372
+ way more stable then `pytest`.
373
+
374
+ - `NumPy` and `SciPy` use modified doctesting in their `refguide-check` utilities.
375
+ These utilities are tightly coupled to their libraries, and have been reported
376
+ to be not easy to reason about, work with, and extend to other projects.
377
+
378
+ This project is nothing but the core functionality of the modified
379
+ `refguide-check` doctesting, extracted to a separate package.
380
+ We believe having it separate simplifies both addressing the needs of these
381
+ two packages, and potential adoption by other projects.
382
+
383
+
384
+ ### Bug reports, feature requests and contributions
385
+
386
+ This package is work in progress. Contributions are most welcome!
387
+ Please don't hesitate to open an issue in the tracker or send a pull request.
388
+
389
+ The current location of the issue tracker is https://github.com/scipy/scipy_doctest.
390
+
@@ -0,0 +1,29 @@
1
+ scipy_doctest/__init__.py,sha256=0jQJkLe2DHAcACSEXnLxfuCh8DcbJuSi-0NURHcIO9Y,312
2
+ scipy_doctest/__main__.py,sha256=H8jTO13GlOLzexbgu7lHMJW1y3_NhLOSArARFZ5WS7o,90
3
+ scipy_doctest/conftest.py,sha256=5vZxzuH042urYIToiKWANDJHQPoGkfQI-ppQ_cuHgM0,53
4
+ scipy_doctest/frontend.py,sha256=1cJTzoxhQ2_lXEK8wvm3JEwhwdnYlaKKdKrPrwhDTw0,18503
5
+ scipy_doctest/impl.py,sha256=Bl_NmcooHtjLX0J3G3T2Pufs9xVRxg9-h1tcMfXf_Xg,20550
6
+ scipy_doctest/plugin.py,sha256=DzTPBCDIre5b2e8JLiTGw7d9zhCP9hYqXcxeKpFTtys,12900
7
+ scipy_doctest/util.py,sha256=6q3VPL-EhoSvDm9aoJM6WZWc18eDa6GS25ob7wLGh08,8047
8
+ scipy_doctest/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ scipy_doctest/tests/failure_cases.py,sha256=Sw0TxHOfyL3Tkpc6g1gL61ReqJ09vIbrVHYnAj0RQ3g,255
10
+ scipy_doctest/tests/failure_cases_2.py,sha256=gupqwSICvzurGIiKVNJRJX9jkmtFR7_Rf3snWU-4Nac,784
11
+ scipy_doctest/tests/finder_cases.py,sha256=s4sq5HZ7m5mXEi1N8dbkgCRY6AxEGcirFRYhEyEG7rw,872
12
+ scipy_doctest/tests/local_file.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ scipy_doctest/tests/local_file_cases.py,sha256=8MrDVEMgZxD4Hh_zjFI0gJGVGjCHYozDdL-BQd0Xm5Y,908
14
+ scipy_doctest/tests/module_cases.py,sha256=leZh1bdP325B2VJwW9BXaw50fvnXtVCAUzWh2H0S1uk,4635
15
+ scipy_doctest/tests/octave_a.mat,sha256=lOfXBSOdMG7_kruTnOHjixXkPy3zSUt10A1FSVjfngI,288
16
+ scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst,sha256=uCtH99RQM0hvDK2_5lgBO2hqtMUOUQhPKxWn4zwHjSs,81594
17
+ scipy_doctest/tests/stopwords_cases.py,sha256=OEZkoFW3B9nHUCG_5adSkLI91avSwNjw-NeUS0D6Yz0,156
18
+ scipy_doctest/tests/test_finder.py,sha256=9bBa3OvB8aTPixMkAFAPlCul7Bm7J95GKri0npOMp9M,6136
19
+ scipy_doctest/tests/test_parser.py,sha256=cmK5kXqTWPUdSVor4bPu6yoikIukDIkVXjIjk1TTPI8,925
20
+ scipy_doctest/tests/test_pytest_configuration.py,sha256=V6hLUmdupVluqYo6Spqa5sdAId0wGAJmMp4o5-Zahw0,2640
21
+ scipy_doctest/tests/test_runner.py,sha256=qP4u8ngbUK946HhMM6Py70hi0W0DcZGcCN258phhM7g,2936
22
+ scipy_doctest/tests/test_skipmarkers.py,sha256=dCng4YfW7OWNnURSlxe7uoYUzURksPXi2AIlehbGk3w,7204
23
+ scipy_doctest/tests/test_testfile.py,sha256=66ZHUpEGGg8MfQT8EKSZ8Zx5pV55gP__TZejGMYwBHA,733
24
+ scipy_doctest/tests/test_testmod.py,sha256=rWjUp7hhSXS1Y9XbsonPN6IjUzj1ABPFp89AK8t1cSA,4930
25
+ scipy_doctest-1.1.dist-info/entry_points.txt,sha256=dFda3z6PjFL7pEWokv_QmoLwE8X1HETCY1H60xopQ-s,47
26
+ scipy_doctest-1.1.dist-info/LICENSE,sha256=xH5PVX8bm8e1JxkmJ-e5FsZsOa7FsNOMfepmCvMoR9g,1523
27
+ scipy_doctest-1.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
28
+ scipy_doctest-1.1.dist-info/METADATA,sha256=UqYs5v9TuaunBsGZ0YEqT03N9Sjj0Q9V-K2gQIuaJ9c,15586
29
+ scipy_doctest-1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [pytest11]
2
+ scipy_doctest=scipy_doctest.plugin
3
+