scipy-doctest 1.2.0__tar.gz → 1.4__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.2.0 → scipy_doctest-1.4}/PKG-INFO +130 -105
  2. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/README.md +127 -102
  3. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/pyproject.toml +1 -1
  4. scipy_doctest-1.4/scipy_doctest/__init__.py +22 -0
  5. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/impl.py +36 -4
  6. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/failure_cases.py +8 -0
  7. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/local_file_cases.py +1 -1
  8. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_pytest_configuration.py +1 -3
  9. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_skipmarkers.py +18 -1
  10. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_testmod.py +10 -0
  11. scipy_doctest-1.2.0/scipy_doctest/__init__.py +0 -12
  12. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/LICENSE +0 -0
  13. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/__main__.py +0 -0
  14. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/conftest.py +0 -0
  15. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/frontend.py +0 -0
  16. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/plugin.py +0 -0
  17. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/__init__.py +0 -0
  18. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/failure_cases_2.py +0 -0
  19. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/finder_cases.py +0 -0
  20. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/local_file.txt +0 -0
  21. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/module_cases.py +0 -0
  22. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/octave_a.mat +0 -0
  23. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst +0 -0
  24. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/stopwords_cases.py +0 -0
  25. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_finder.py +0 -0
  26. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_parser.py +0 -0
  27. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_runner.py +0 -0
  28. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/tests/test_testfile.py +0 -0
  29. {scipy_doctest-1.2.0 → scipy_doctest-1.4}/scipy_doctest/util.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scipy_doctest
3
- Version: 1.2.0
4
- Summary: Doctests on steroids.
3
+ Version: 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
7
7
  Description-Content-Type: text/markdown
@@ -14,7 +14,7 @@ Requires-Dist: numpy>=1.19.5
14
14
  Requires-Dist: pytest
15
15
  Requires-Dist: scipy ; extra == "test"
16
16
  Requires-Dist: matplotlib ; extra == "test"
17
- Project-URL: Home, https://github.com/ev-br/scpdt
17
+ Project-URL: Home, https://github.com/scipy/scipy_doctest
18
18
  Provides-Extra: test
19
19
 
20
20
  # Floating-point aware, human readable, numpy-compatible doctesting.
@@ -67,8 +67,8 @@ Its main features are
67
67
  >>> np.random.randint(100)
68
68
  42 # may vary
69
69
  ```
70
- Note that the markers (by default, `"# may vary"` and `"# random"`) are applied
71
- to an example's output, not its source.
70
+ Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
71
+ to either an example's output, or its source.
72
72
 
73
73
  Also note a difference with respect to the standard `# doctest: +SKIP`: the latter
74
74
  skips the example entirely, while these additional markers only skip checking
@@ -100,7 +100,7 @@ the output. Thus the example source needs to be valid python code still.
100
100
  ## Install and test
101
101
 
102
102
  ```
103
- $ pip install -e .
103
+ $ pip install scipy-doctest
104
104
  $ pytest --pyargs scipy_doctest
105
105
  ```
106
106
 
@@ -112,9 +112,56 @@ or nearly so.
112
112
 
113
113
  The other layer is the `pytest` plugin.
114
114
 
115
+ ### Run doctests via pytest
116
+
117
+ To run doctests on your package or project, follow these steps:
118
+
119
+ 1. **Install the plugin**
120
+
121
+ ```bash
122
+ pip install scipy-doctest
123
+ ```
124
+
125
+ 2. **Register or load the plugin**
126
+
127
+ Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
128
+
129
+ To do this, add the following line of code:
130
+
131
+ ```python
132
+ # In your conftest.py file or test module
133
+
134
+ pytest_plugins = "scipy_doctest"
135
+ ```
136
+
137
+ 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
+
139
+ 3. **Run doctests**
140
+
141
+ Once the plugin is registered, run the doctests by executing the following command:
142
+
143
+ ```bash
144
+ $ python -m pytest --doctest-modules
145
+ ```
146
+ or
147
+ ```bash
148
+ $ pytest --pyargs <your-package> --doctest-modules
149
+ ```
150
+
151
+ By default, all doctests are collected. To only collect public objects, `strategy="api"`,
152
+ use the command flag
153
+
154
+ ```bash
155
+ $ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
156
+ ```
157
+
158
+ See [More fine-grained control](https://github.com/scipy/scipy_doctest#More-fine-grained-control) section
159
+ for details on how to customize the behavior.
160
+
115
161
 
116
162
  ### Basic usage
117
163
 
164
+ The use of `pytest` is optional, and you can use the `doctest` layer API.
118
165
  For example,
119
166
 
120
167
  ```
@@ -131,7 +178,8 @@ For more details, see the `testmod` docstring. Other useful functions are
131
178
  `find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
132
179
  the behavior of the eponymous functions of the `doctest` module).
133
180
 
134
- #### Command-line interface
181
+
182
+ ### Command-line interface
135
183
 
136
184
  There is a basic CLI, which also mimics that of the `doctest` module:
137
185
  ```
@@ -146,8 +194,10 @@ Text files can also be CLI-checked:
146
194
  $ python -m scipy_doctest bar.rst
147
195
  ```
148
196
 
197
+ Notice that the command-line usage only uses the default `DTConfig` settings.
198
+
149
199
 
150
- #### More fine-grained control
200
+ ## More fine-grained control
151
201
 
152
202
  More fine-grained control of the functionality is available via the following
153
203
  classes
@@ -166,133 +216,98 @@ configuration is simply creating an instance, overriding an attribute and
166
216
  passing the instance to `testmod` or constructors of `DT*` objects. Defaults
167
217
  are provided, based on a long-term usage in SciPy.
168
218
 
219
+ See the [DTConfig docstring](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/impl.py#L24)
220
+ for the full set of attributes that allow you to fine-tune your doctesting experience.
169
221
 
170
- ### The SciPy Doctest Pytest Plugin
222
+ To set any of these attributes, create an instance of `DTConfig` and assign the attributes
223
+ in a usual way.
171
224
 
172
- The pytest plugin enables the use of `scipy_doctest` tools to perform doctests.
225
+ If using the pytest plugin, it is convenient to use the default instance, which
226
+ is predefined in `scipy_doctest/conftest.py`. This instance will be automatically
227
+ passed around via an
228
+ [attribute of pytest's `Config` object](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/plugin.py#L39).
173
229
 
174
- Follow the given instructions to utilize the pytest plugin for doctesting.
230
+ ### Examples
175
231
 
176
- ### Running Doctests on SciPy
177
-
178
- 1. **Install plugin**
179
-
180
- ```bash
181
- pip install scipy-doctest
182
232
  ```
183
-
184
- 2. **Configure Your Doctesting Experience**
185
-
186
- To tailor your doctesting experience, you can utilize an instance of `DTConfig`.
187
- An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/scipy/scipy_doctest#tailoring-your-doctesting-experience) section.
188
-
189
- 3. **Run Doctests**
190
-
191
- Doctesting is configured to execute on SciPy using the `dev.py` module.
192
-
193
- To run all doctests, use the following command:
194
- ```bash
195
- python dev.py smoke-docs
233
+ dt_config = DTConfig()
196
234
  ```
197
235
 
198
- To run doctests on specific SciPy modules, e.g: `cluster`, use the following command:
236
+ or, if using pytest,
199
237
 
200
- ```bash
201
- python dev.py smoke-docs -s cluster
238
+ ```python
239
+ from scipy_doctest.conftest import dt_config # a DTConfig instance with default settings
202
240
  ```
203
241
 
204
- ### Running Doctests on Other Packages/Projects
242
+ and then
205
243
 
206
- If you want to run doctests on packages or projects other than SciPy, follow these steps:
207
-
208
- 1. **Install the plugin**
209
-
210
- ```bash
211
- pip install scipy-doctest
212
244
  ```
245
+ dt_config.rndm_markers = {'# unintialized'}
213
246
 
214
- 2. **Register or Load the Plugin**
215
-
216
- Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
217
-
218
- To do this, add the following line of code:
247
+ dt_config.stopwords = {'plt.', 'plt.hist', 'plt.show'}
219
248
 
220
- ```python
221
- # In your conftest.py file or test module
249
+ dt_config.local_resources = {
250
+ 'scipy_doctest.tests.local_file_cases.local_files': ['scipy_doctest/tests/local_file.txt'],
251
+ 'scipy_doctest.tests.local_file_cases.sio': ['scipy_doctest/tests/octave_a.mat']
252
+ }
222
253
 
223
- pytest_plugins = "scipy_doctest"
254
+ dt_config.skiplist = {
255
+ 'scipy.special.sinc',
256
+ 'scipy.misc.who',
257
+ 'scipy.optimize.show_options'
258
+ }
224
259
  ```
225
260
 
226
- 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.
261
+ 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.
227
262
 
228
- 3. **Configure your doctesting experience**
229
263
 
230
- An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/scipy/scipy_doctest#tailoring-your-doctesting-experience) section.
264
+ #### Alternative Checkers
231
265
 
232
- 4. **Run doctests**
266
+ By default, we use the floating-point aware `DTChecker`. If you want to use an
267
+ alternative checker, all you need to do is to define the corresponding class,
268
+ and add an attribute to the `DTConfig` instance. For example,
233
269
 
234
- Once the plugin is registered, you can run your doctests by executing the following command:
235
270
 
236
- ```bash
237
- $ python -m pytest --doctest-modules
238
271
  ```
239
- or
240
- ```bash
241
- $ pytest --pyargs <your-package> --doctest-modules
272
+ class VanillaOutputChecker(doctest.OutputChecker):
273
+ """doctest.OutputChecker to drop in for DTChecker.
274
+
275
+ LSP break: OutputChecker does not have __init__,
276
+ here we add it to agree with DTChecker.
277
+ """
278
+ def __init__(self, config):
279
+ pass
242
280
  ```
243
281
 
244
- By default, all doctests are collected. To only collect public objects, `strategy="api"`,
245
- use the command flag
282
+ and
246
283
 
247
- ```bash
248
- $ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
284
+ ```
285
+ dt_config = DTConfig()
286
+ dt_config.CheckerKlass = VanillaOutputChecker
249
287
  ```
250
288
 
251
- ### Tailoring Your Doctesting Experience
289
+ See [a pytest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_pytest_configuration.py#L63)
290
+ and [a doctest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_runner.py#L94)
291
+ for more details.
252
292
 
253
- [DTConfig](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/impl.py#L23) offers a variety of attributes that allow you to fine-tune your doctesting experience.
254
293
 
255
- These attributes include:
256
- 1. **default_namespace (dict):** Defines the namespace in which examples are executed.
257
- 2. **check_namespace (dict):** Specifies the namespace for conducting checks.
258
- 3. **rndm_markers (set):** Provides additional directives which act like `# doctest: + SKIP`.
259
- 4. **atol (float) and rtol (float):** Sets absolute and relative tolerances for validating doctest examples.
260
- Specifically, it governs the check using `np.allclose(want, got, atol=atol, rtol=rtol)`.
261
- 5. **optionflags (int):** These are doctest option flags.
262
- The default setting includes `NORMALIZE_WHITESPACE` | `ELLIPSIS` | `IGNORE_EXCEPTION_DETAIL`.
263
- 6. **stopwords (set):** If an example contains any of these stopwords, the output is not checked (though the source's validity is still assessed).
264
- 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.
265
- 8. **skiplist (set):** A list of names of objects with docstrings known to fail doctesting and are intentionally excluded from testing.
266
- 9. **user_context_mgr:** A context manager for running tests.
267
- Typically, it is entered for each DocTest (especially in API documentation), ensuring proper testing isolation.
268
- 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`.
269
- 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`.
270
- 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`.
294
+ ### NumPy and SciPy wrappers
271
295
 
272
- To set any of these attributes, create an instance of `DTConfig` called `dt_config`.
273
- This instance is already set as an [attribute of pytest's `Config` object](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/plugin.py#L39).
274
296
 
275
- **Example:**
297
+ NumPy wraps `scipy-doctest` with the `spin` command
276
298
 
277
- ```python
278
- dt_config = DTConfig()
279
- dt_config.stopwords = {'plt.', '.hist', '.show'}
280
- dt_config.local_resources = {
281
- 'scipy_doctest.tests.local_file_cases.local_files': ['scipy_doctest/tests/local_file.txt'],
282
- 'scipy_doctest.tests.local_file_cases.sio': ['scipy_doctest/tests/octave_a.mat']
283
- }
284
- dt_config.skiplist = {
285
- 'scipy.special.sinc',
286
- 'scipy.misc.who',
287
- 'scipy.optimize.show_options'
288
- }
299
+ ```
300
+ $ spin check-docs
289
301
  ```
290
302
 
291
- 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.
303
+ SciPy wraps `scipy-doctest` with custom `dev.py` commands:
304
+
305
+ ```
306
+ $ python dev.py smoke-docs # check docstrings
307
+ $ python dev.py smoke-tutorials # ReST user guide tutorials
308
+ ```
292
309
 
293
- By following these steps, you will be able to effectively use the SciPy Doctest pytest plugin for doctests in your Python projects.
294
310
 
295
- Happy testing!
296
311
 
297
312
  ## Rough edges and sharp bits
298
313
 
@@ -326,7 +341,7 @@ being optional. So you either guard the imports in doctests (yikes!), or
326
341
  the collections fails if dependencies are not available.
327
342
 
328
343
  The solution is to explicitly `--ignore` the paths to modules with optionals.
329
- (or use `DTConfig.pytest_extra_ignore` list):
344
+ (or, equivalently, use `DTConfig.pytest_extra_ignore` list):
330
345
 
331
346
  ```
332
347
  $ pytest --ignore=/build-install/lib/scipy/python3.10/site-packages/scipy/_lib ...
@@ -361,6 +376,19 @@ leads to
361
376
  - `scipy.linalg.det`, collected from `scipy/linalg/__init__.py`, is public.
362
377
 
363
378
 
379
+ - *`pytest`'s assertion rewriting*
380
+
381
+ In some rare cases you may need to either explicitly register the `scipy_doctest`
382
+ package with the `pytest` assertion rewriting machinery, or ask it to avoid rewriting
383
+ completely, via `pytest --assert=plain`.
384
+ See [the `pytest documentation`](https://docs.pytest.org/en/7.1.x/how-to/assert.html)
385
+ for more details.
386
+
387
+ In general, rewriting assertions is not very useful for doctests, as the
388
+ output on error is fixed by the doctest machinery anyway. Therefore, we believe
389
+ adding `--assert=plain` is reasonable.
390
+
391
+
364
392
  ## Prior art and related work
365
393
 
366
394
  - `pytest` provides some limited floating-point aware `NumericLiteralChecker`.
@@ -370,20 +398,17 @@ leads to
370
398
  differences are: (i) `pytest-doctestplus` is more sensitive to formatting,
371
399
  including whitespace---thus if numpy tweaks its output formatting, doctests
372
400
  may start failing; (ii) there is still a need for `# doctest: +FLOAT_CMP`
373
- directives; (iii) being a pytest plugin, `pytest-doctestplus` is tightly
374
- coupled to `pytest`. It thus needs to follow `pytest` releases, and
375
- some maintenance work may be required to adapt when `pytest` publishes a new
376
- release.
401
+ directives.
377
402
 
378
403
  This project takes a different approach: in addition to plugging into `pytest`,
379
404
  we closely follow the `doctest` API and implementation, which are naturally
380
405
  way more stable then `pytest`.
381
406
 
382
- - `NumPy` and `SciPy` use modified doctesting in their `refguide-check` utilities.
407
+ - `NumPy` and `SciPy` were using modified doctesting in their `refguide-check` utilities.
383
408
  These utilities are tightly coupled to their libraries, and have been reported
384
409
  to be not easy to reason about, work with, and extend to other projects.
385
410
 
386
- This project is nothing but the core functionality of the modified
411
+ This project is mainly the core functionality of the modified
387
412
  `refguide-check` doctesting, extracted to a separate package.
388
413
  We believe having it separate simplifies both addressing the needs of these
389
414
  two packages, and potential adoption by other projects.
@@ -48,8 +48,8 @@ Its main features are
48
48
  >>> np.random.randint(100)
49
49
  42 # may vary
50
50
  ```
51
- Note that the markers (by default, `"# may vary"` and `"# random"`) are applied
52
- to an example's output, not its source.
51
+ Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
52
+ to either an example's output, or its source.
53
53
 
54
54
  Also note a difference with respect to the standard `# doctest: +SKIP`: the latter
55
55
  skips the example entirely, while these additional markers only skip checking
@@ -81,7 +81,7 @@ the output. Thus the example source needs to be valid python code still.
81
81
  ## Install and test
82
82
 
83
83
  ```
84
- $ pip install -e .
84
+ $ pip install scipy-doctest
85
85
  $ pytest --pyargs scipy_doctest
86
86
  ```
87
87
 
@@ -93,9 +93,56 @@ or nearly so.
93
93
 
94
94
  The other layer is the `pytest` plugin.
95
95
 
96
+ ### Run doctests via pytest
97
+
98
+ To run doctests on your package or project, follow these steps:
99
+
100
+ 1. **Install the plugin**
101
+
102
+ ```bash
103
+ pip install scipy-doctest
104
+ ```
105
+
106
+ 2. **Register or load the plugin**
107
+
108
+ Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
109
+
110
+ To do this, add the following line of code:
111
+
112
+ ```python
113
+ # In your conftest.py file or test module
114
+
115
+ pytest_plugins = "scipy_doctest"
116
+ ```
117
+
118
+ 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
+
120
+ 3. **Run doctests**
121
+
122
+ Once the plugin is registered, run the doctests by executing the following command:
123
+
124
+ ```bash
125
+ $ python -m pytest --doctest-modules
126
+ ```
127
+ or
128
+ ```bash
129
+ $ pytest --pyargs <your-package> --doctest-modules
130
+ ```
131
+
132
+ By default, all doctests are collected. To only collect public objects, `strategy="api"`,
133
+ use the command flag
134
+
135
+ ```bash
136
+ $ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
137
+ ```
138
+
139
+ See [More fine-grained control](https://github.com/scipy/scipy_doctest#More-fine-grained-control) section
140
+ for details on how to customize the behavior.
141
+
96
142
 
97
143
  ### Basic usage
98
144
 
145
+ The use of `pytest` is optional, and you can use the `doctest` layer API.
99
146
  For example,
100
147
 
101
148
  ```
@@ -112,7 +159,8 @@ For more details, see the `testmod` docstring. Other useful functions are
112
159
  `find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
113
160
  the behavior of the eponymous functions of the `doctest` module).
114
161
 
115
- #### Command-line interface
162
+
163
+ ### Command-line interface
116
164
 
117
165
  There is a basic CLI, which also mimics that of the `doctest` module:
118
166
  ```
@@ -127,8 +175,10 @@ Text files can also be CLI-checked:
127
175
  $ python -m scipy_doctest bar.rst
128
176
  ```
129
177
 
178
+ Notice that the command-line usage only uses the default `DTConfig` settings.
179
+
130
180
 
131
- #### More fine-grained control
181
+ ## More fine-grained control
132
182
 
133
183
  More fine-grained control of the functionality is available via the following
134
184
  classes
@@ -147,133 +197,98 @@ configuration is simply creating an instance, overriding an attribute and
147
197
  passing the instance to `testmod` or constructors of `DT*` objects. Defaults
148
198
  are provided, based on a long-term usage in SciPy.
149
199
 
200
+ See the [DTConfig docstring](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/impl.py#L24)
201
+ for the full set of attributes that allow you to fine-tune your doctesting experience.
150
202
 
151
- ### The SciPy Doctest Pytest Plugin
203
+ To set any of these attributes, create an instance of `DTConfig` and assign the attributes
204
+ in a usual way.
152
205
 
153
- The pytest plugin enables the use of `scipy_doctest` tools to perform doctests.
206
+ If using the pytest plugin, it is convenient to use the default instance, which
207
+ is predefined in `scipy_doctest/conftest.py`. This instance will be automatically
208
+ passed around via an
209
+ [attribute of pytest's `Config` object](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/plugin.py#L39).
154
210
 
155
- Follow the given instructions to utilize the pytest plugin for doctesting.
211
+ ### Examples
156
212
 
157
- ### Running Doctests on SciPy
158
-
159
- 1. **Install plugin**
160
-
161
- ```bash
162
- pip install scipy-doctest
163
213
  ```
164
-
165
- 2. **Configure Your Doctesting Experience**
166
-
167
- To tailor your doctesting experience, you can utilize an instance of `DTConfig`.
168
- An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/scipy/scipy_doctest#tailoring-your-doctesting-experience) section.
169
-
170
- 3. **Run Doctests**
171
-
172
- Doctesting is configured to execute on SciPy using the `dev.py` module.
173
-
174
- To run all doctests, use the following command:
175
- ```bash
176
- python dev.py smoke-docs
214
+ dt_config = DTConfig()
177
215
  ```
178
216
 
179
- To run doctests on specific SciPy modules, e.g: `cluster`, use the following command:
217
+ or, if using pytest,
180
218
 
181
- ```bash
182
- python dev.py smoke-docs -s cluster
219
+ ```python
220
+ from scipy_doctest.conftest import dt_config # a DTConfig instance with default settings
183
221
  ```
184
222
 
185
- ### Running Doctests on Other Packages/Projects
223
+ and then
186
224
 
187
- If you want to run doctests on packages or projects other than SciPy, follow these steps:
188
-
189
- 1. **Install the plugin**
190
-
191
- ```bash
192
- pip install scipy-doctest
193
225
  ```
226
+ dt_config.rndm_markers = {'# unintialized'}
194
227
 
195
- 2. **Register or Load the Plugin**
196
-
197
- Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
198
-
199
- To do this, add the following line of code:
228
+ dt_config.stopwords = {'plt.', 'plt.hist', 'plt.show'}
200
229
 
201
- ```python
202
- # In your conftest.py file or test module
230
+ dt_config.local_resources = {
231
+ 'scipy_doctest.tests.local_file_cases.local_files': ['scipy_doctest/tests/local_file.txt'],
232
+ 'scipy_doctest.tests.local_file_cases.sio': ['scipy_doctest/tests/octave_a.mat']
233
+ }
203
234
 
204
- pytest_plugins = "scipy_doctest"
235
+ dt_config.skiplist = {
236
+ 'scipy.special.sinc',
237
+ 'scipy.misc.who',
238
+ 'scipy.optimize.show_options'
239
+ }
205
240
  ```
206
241
 
207
- 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.
242
+ 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.
208
243
 
209
- 3. **Configure your doctesting experience**
210
244
 
211
- An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/scipy/scipy_doctest#tailoring-your-doctesting-experience) section.
245
+ #### Alternative Checkers
212
246
 
213
- 4. **Run doctests**
247
+ By default, we use the floating-point aware `DTChecker`. If you want to use an
248
+ alternative checker, all you need to do is to define the corresponding class,
249
+ and add an attribute to the `DTConfig` instance. For example,
214
250
 
215
- Once the plugin is registered, you can run your doctests by executing the following command:
216
251
 
217
- ```bash
218
- $ python -m pytest --doctest-modules
219
252
  ```
220
- or
221
- ```bash
222
- $ pytest --pyargs <your-package> --doctest-modules
253
+ class VanillaOutputChecker(doctest.OutputChecker):
254
+ """doctest.OutputChecker to drop in for DTChecker.
255
+
256
+ LSP break: OutputChecker does not have __init__,
257
+ here we add it to agree with DTChecker.
258
+ """
259
+ def __init__(self, config):
260
+ pass
223
261
  ```
224
262
 
225
- By default, all doctests are collected. To only collect public objects, `strategy="api"`,
226
- use the command flag
263
+ and
227
264
 
228
- ```bash
229
- $ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
265
+ ```
266
+ dt_config = DTConfig()
267
+ dt_config.CheckerKlass = VanillaOutputChecker
230
268
  ```
231
269
 
232
- ### Tailoring Your Doctesting Experience
270
+ See [a pytest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_pytest_configuration.py#L63)
271
+ and [a doctest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_runner.py#L94)
272
+ for more details.
233
273
 
234
- [DTConfig](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/impl.py#L23) offers a variety of attributes that allow you to fine-tune your doctesting experience.
235
274
 
236
- These attributes include:
237
- 1. **default_namespace (dict):** Defines the namespace in which examples are executed.
238
- 2. **check_namespace (dict):** Specifies the namespace for conducting checks.
239
- 3. **rndm_markers (set):** Provides additional directives which act like `# doctest: + SKIP`.
240
- 4. **atol (float) and rtol (float):** Sets absolute and relative tolerances for validating doctest examples.
241
- Specifically, it governs the check using `np.allclose(want, got, atol=atol, rtol=rtol)`.
242
- 5. **optionflags (int):** These are doctest option flags.
243
- The default setting includes `NORMALIZE_WHITESPACE` | `ELLIPSIS` | `IGNORE_EXCEPTION_DETAIL`.
244
- 6. **stopwords (set):** If an example contains any of these stopwords, the output is not checked (though the source's validity is still assessed).
245
- 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.
246
- 8. **skiplist (set):** A list of names of objects with docstrings known to fail doctesting and are intentionally excluded from testing.
247
- 9. **user_context_mgr:** A context manager for running tests.
248
- Typically, it is entered for each DocTest (especially in API documentation), ensuring proper testing isolation.
249
- 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`.
250
- 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`.
251
- 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`.
275
+ ### NumPy and SciPy wrappers
252
276
 
253
- To set any of these attributes, create an instance of `DTConfig` called `dt_config`.
254
- This instance is already set as an [attribute of pytest's `Config` object](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/plugin.py#L39).
255
277
 
256
- **Example:**
278
+ NumPy wraps `scipy-doctest` with the `spin` command
257
279
 
258
- ```python
259
- dt_config = DTConfig()
260
- dt_config.stopwords = {'plt.', '.hist', '.show'}
261
- dt_config.local_resources = {
262
- 'scipy_doctest.tests.local_file_cases.local_files': ['scipy_doctest/tests/local_file.txt'],
263
- 'scipy_doctest.tests.local_file_cases.sio': ['scipy_doctest/tests/octave_a.mat']
264
- }
265
- dt_config.skiplist = {
266
- 'scipy.special.sinc',
267
- 'scipy.misc.who',
268
- 'scipy.optimize.show_options'
269
- }
280
+ ```
281
+ $ spin check-docs
270
282
  ```
271
283
 
272
- 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.
284
+ SciPy wraps `scipy-doctest` with custom `dev.py` commands:
285
+
286
+ ```
287
+ $ python dev.py smoke-docs # check docstrings
288
+ $ python dev.py smoke-tutorials # ReST user guide tutorials
289
+ ```
273
290
 
274
- By following these steps, you will be able to effectively use the SciPy Doctest pytest plugin for doctests in your Python projects.
275
291
 
276
- Happy testing!
277
292
 
278
293
  ## Rough edges and sharp bits
279
294
 
@@ -307,7 +322,7 @@ being optional. So you either guard the imports in doctests (yikes!), or
307
322
  the collections fails if dependencies are not available.
308
323
 
309
324
  The solution is to explicitly `--ignore` the paths to modules with optionals.
310
- (or use `DTConfig.pytest_extra_ignore` list):
325
+ (or, equivalently, use `DTConfig.pytest_extra_ignore` list):
311
326
 
312
327
  ```
313
328
  $ pytest --ignore=/build-install/lib/scipy/python3.10/site-packages/scipy/_lib ...
@@ -342,6 +357,19 @@ leads to
342
357
  - `scipy.linalg.det`, collected from `scipy/linalg/__init__.py`, is public.
343
358
 
344
359
 
360
+ - *`pytest`'s assertion rewriting*
361
+
362
+ In some rare cases you may need to either explicitly register the `scipy_doctest`
363
+ package with the `pytest` assertion rewriting machinery, or ask it to avoid rewriting
364
+ completely, via `pytest --assert=plain`.
365
+ See [the `pytest documentation`](https://docs.pytest.org/en/7.1.x/how-to/assert.html)
366
+ for more details.
367
+
368
+ In general, rewriting assertions is not very useful for doctests, as the
369
+ output on error is fixed by the doctest machinery anyway. Therefore, we believe
370
+ adding `--assert=plain` is reasonable.
371
+
372
+
345
373
  ## Prior art and related work
346
374
 
347
375
  - `pytest` provides some limited floating-point aware `NumericLiteralChecker`.
@@ -351,20 +379,17 @@ leads to
351
379
  differences are: (i) `pytest-doctestplus` is more sensitive to formatting,
352
380
  including whitespace---thus if numpy tweaks its output formatting, doctests
353
381
  may start failing; (ii) there is still a need for `# doctest: +FLOAT_CMP`
354
- directives; (iii) being a pytest plugin, `pytest-doctestplus` is tightly
355
- coupled to `pytest`. It thus needs to follow `pytest` releases, and
356
- some maintenance work may be required to adapt when `pytest` publishes a new
357
- release.
382
+ directives.
358
383
 
359
384
  This project takes a different approach: in addition to plugging into `pytest`,
360
385
  we closely follow the `doctest` API and implementation, which are naturally
361
386
  way more stable then `pytest`.
362
387
 
363
- - `NumPy` and `SciPy` use modified doctesting in their `refguide-check` utilities.
388
+ - `NumPy` and `SciPy` were using modified doctesting in their `refguide-check` utilities.
364
389
  These utilities are tightly coupled to their libraries, and have been reported
365
390
  to be not easy to reason about, work with, and extend to other projects.
366
391
 
367
- This project is nothing but the core functionality of the modified
392
+ This project is mainly the core functionality of the modified
368
393
  `refguide-check` doctesting, extracted to a separate package.
369
394
  We believe having it separate simplifies both addressing the needs of these
370
395
  two packages, and potential adoption by other projects.
@@ -30,7 +30,7 @@ test = [
30
30
  ]
31
31
 
32
32
  [project.urls]
33
- Home = "https://github.com/ev-br/scpdt"
33
+ Home = "https://github.com/scipy/scipy_doctest"
34
34
 
35
35
  [tool.pytest.ini_options]
36
36
  addopts = "--verbose --color=yes"
@@ -0,0 +1,22 @@
1
+ """
2
+ Configurable, whitespace-insensitive, floating-point-aware doctest helpers.
3
+ """
4
+
5
+
6
+ __version__ = "1.4"
7
+
8
+ try:
9
+ # register internal modules with pytest; obscure errors galore otherwise
10
+ import pytest
11
+ pytest.register_assert_rewrite(
12
+ "scipy_doctest.conftest", "scipy_doctest.impl", "scipy_doctest.util",
13
+ "scipy_doctest.frontend", "scipy_doctest.plugin"
14
+ )
15
+ except ModuleNotFoundError:
16
+ # pytest is optional, so nothing to do
17
+ pass
18
+
19
+
20
+ from .impl import DTChecker, DTFinder, DTParser, DTRunner, DebugDTRunner, DTConfig # noqa
21
+ from .frontend import testmod, testfile, find_doctests, run_docstring_examples # noqa
22
+
@@ -40,6 +40,11 @@ class DTConfig:
40
40
  rtol : float
41
41
  Absolute and relative tolerances to check doctest examples with.
42
42
  Specifically, the check is ``np.allclose(want, got, atol=atol, rtol=rtol)``
43
+ strict_check : bool
44
+ Whether to check that dtypes match or rely on the lax definition of
45
+ equality of numpy objects. For instance, `3 == np.float64(3)`, but
46
+ dtypes do not match.
47
+ Default is False.
43
48
  optionflags : int
44
49
  doctest optionflags
45
50
  Default is ``NORMALIZE_WHITESPACE | ELLIPSIS | IGNORE_EXCEPTION_DETAIL``
@@ -91,6 +96,14 @@ class DTConfig:
91
96
  adding `# may vary` to the outputs of all examples.
92
97
  Each key is a doctest name to skip, and the corresponding value is
93
98
  a string. If not empty, the string value is used as the skip reason.
99
+ CheckerKlass : object, optional
100
+ The class for the Checker object. Must mimic the ``DTChecker`` API:
101
+ subclass the `doctest.OutputChecker` and make the constructor signature
102
+ read ``__init__(self, config=None)``, where `config` is a ``DTConfig``
103
+ instance.
104
+ This class will be instantiated by ``DTRunner``.
105
+ Defaults to `DTChecker`.
106
+
94
107
  """
95
108
  def __init__(self, *, # DTChecker configuration
96
109
  CheckerKlass=None,
@@ -99,6 +112,7 @@ class DTConfig:
99
112
  rndm_markers=None,
100
113
  atol=1e-8,
101
114
  rtol=1e-2,
115
+ strict_check=False,
102
116
  # DTRunner configuration
103
117
  optionflags=None,
104
118
  # DTFinder/DTParser configuration
@@ -153,8 +167,8 @@ class DTConfig:
153
167
  '#random', '#Random',
154
168
  "# may vary"}
155
169
  self.rndm_markers = rndm_markers
156
-
157
170
  self.atol, self.rtol = atol, rtol
171
+ self.strict_check = strict_check
158
172
 
159
173
  ### DTRunner configuration ###
160
174
 
@@ -355,23 +369,35 @@ class DTChecker(doctest.OutputChecker):
355
369
  return False
356
370
 
357
371
  # ... and defer to numpy
372
+ strict = self.config.strict_check
358
373
  try:
359
- return self._do_check(a_want, a_got)
374
+ return self._do_check(a_want, a_got, strict)
360
375
  except Exception:
361
376
  # heterog tuple, eg (1, np.array([1., 2.]))
362
377
  try:
363
- return all(self._do_check(w, g) for w, g in zip_longest(a_want, a_got))
378
+ return all(
379
+ self._do_check(w, g, strict) for w, g in zip_longest(a_want, a_got)
380
+ )
364
381
  except (TypeError, ValueError):
365
382
  return False
366
383
 
367
- def _do_check(self, want, got):
384
+ def _do_check(self, want, got, strict_check):
368
385
  # This should be done exactly as written to correctly handle all of
369
386
  # numpy-comparable objects, strings, and heterogeneous tuples
387
+
388
+ # NB: 3 == np.float64(3.0) but dtypes differ
389
+ if strict_check:
390
+ want_dtype = np.asarray(want).dtype
391
+ got_dtype = np.asarray(got).dtype
392
+ if want_dtype != got_dtype:
393
+ return False
394
+
370
395
  try:
371
396
  if want == got:
372
397
  return True
373
398
  except Exception:
374
399
  pass
400
+
375
401
  with warnings.catch_warnings():
376
402
  # NumPy's ragged array deprecation of np.array([1, (2, 3)])
377
403
  warnings.simplefilter('ignore', VisibleDeprecationWarning)
@@ -506,6 +532,7 @@ class DTParser(doctest.DocTestParser):
506
532
  """
507
533
  stopwords = self.config.stopwords
508
534
  pseudocode = self.config.pseudocode
535
+ rndm_markers = self.config.rndm_markers
509
536
 
510
537
  SKIP = doctest.OPTIONFLAGS_BY_NAME['SKIP']
511
538
  keep_skipping_this_block = False
@@ -529,6 +556,11 @@ class DTParser(doctest.DocTestParser):
529
556
  # NB: Could have just skipped it via `continue`.
530
557
  example.options[SKIP] = True
531
558
 
559
+ if any(word in example.source for word in rndm_markers):
560
+ # Found a `# may vary`. Do not check the output (but do check
561
+ # that the source is valid python).
562
+ example.want += " # _ignore\n"
563
+
532
564
  if any(word in example.source for word in stopwords):
533
565
  # Found a stopword. Do not check the output (but do check
534
566
  # that the source is valid python).
@@ -45,3 +45,11 @@ def tuple_and_list_2():
45
45
  >>> (0, 1, 2)
46
46
  [0, 1, 2]
47
47
  """
48
+
49
+
50
+ def dtype_mismatch():
51
+ """
52
+ >>> import numpy as np
53
+ >>> 3.0
54
+ 3
55
+ """
@@ -3,7 +3,7 @@ from ..conftest import dt_config
3
3
  # Specify local files required by doctests
4
4
  dt_config.local_resources = {
5
5
  'scipy_doctest.tests.local_file_cases.local_files': ['local_file.txt'],
6
- 'scipy_doctest.local_file_cases.sio': ['octave_a.mat']
6
+ 'scipy_doctest.tests.local_file_cases.sio': ['octave_a.mat']
7
7
  }
8
8
 
9
9
 
@@ -49,11 +49,9 @@ def test_stopword_cases(pytester):
49
49
  assert result.ret == pytest.ExitCode.OK
50
50
 
51
51
 
52
- @pytest.mark.xfail(reason="XXX: passes locally, fails on CI")
53
52
  @pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
54
53
  def test_local_file_cases(pytester):
55
- """Test that local files are found for use in doctests.
56
- """
54
+ """Test that local files are found for use in doctests."""
57
55
  path_str = local_file_cases.__file__
58
56
  python_file = Path(path_str)
59
57
  result = pytester.inline_run(python_file, "--doctest-modules")
@@ -161,8 +161,25 @@ class TestMayVary:
161
161
  filename='none', lineno=0)
162
162
 
163
163
  runner = DebugDTRunner()
164
- with pytest.raises(doctest.DocTestFailure):
164
+ runner.run(test)
165
+
166
+ # one example tried, of which zero failed
167
+ assert runner.get_history() == {'may_vary_source': (0, 1)}
168
+
169
+ def test_may_vary_syntax_error(self):
170
+ # `# may vary` markers do not mask syntax errors, unlike `# doctest: +SKIP`
171
+ string = ">>> 1 += 2 # may vary\n"
172
+ string += "42\n"
173
+
174
+ parser = DTParser()
175
+ test = parser.get_doctest(string, globs={},
176
+ name='may_vary_err',
177
+ filename='none', lineno=0)
178
+
179
+ runner = DebugDTRunner()
180
+ with pytest.raises(Exception) as exc_info:
165
181
  runner.run(test)
182
+ assert exc_info.type == doctest.UnexpectedException
166
183
 
167
184
 
168
185
  string='''\
@@ -117,6 +117,16 @@ def test_tuple_and_list():
117
117
  assert res.failed == 2
118
118
 
119
119
 
120
+ @pytest.mark.parametrize('strict, num_fails', [(True, 1), (False, 0)])
121
+ class TestStrictDType:
122
+ def test_np_fix(self, strict, num_fails):
123
+ config = DTConfig(strict_check=strict)
124
+ res, _ = _testmod(failure_cases,
125
+ strategy=[failure_cases.dtype_mismatch],
126
+ config=config)
127
+ assert res.failed == num_fails
128
+
129
+
120
130
  class TestLocalFiles:
121
131
  def test_local_files(self):
122
132
  # A doctest tries to open a local file. Test that it works
@@ -1,12 +0,0 @@
1
- """
2
- Doctests on steroids.
3
-
4
- Whitespace-insensitive, numpy-aware, floating-point-aware doctest helpers.
5
- """
6
-
7
-
8
- __version__ = "1.2.0"
9
-
10
- from .impl import DTChecker, DTFinder, DTParser, DTRunner, DebugDTRunner, DTConfig # noqa
11
- from .frontend import testmod, testfile, find_doctests, run_docstring_examples # noqa
12
-
File without changes