|
""" |
|
Handlers for IPythonDirective's @doctest pseudo-decorator. |
|
|
|
The Sphinx extension that provides support for embedded IPython code provides |
|
a pseudo-decorator @doctest, which treats the input/output block as a |
|
doctest, raising a RuntimeError during doc generation if the actual output |
|
(after running the input) does not match the expected output. |
|
|
|
An example usage is: |
|
|
|
.. code-block:: rst |
|
|
|
.. ipython:: |
|
|
|
In [1]: x = 1 |
|
|
|
@doctest |
|
In [2]: x + 2 |
|
Out[3]: 3 |
|
|
|
One can also provide arguments to the decorator. The first argument should be |
|
the name of a custom handler. The specification of any other arguments is |
|
determined by the handler. For example, |
|
|
|
.. code-block:: rst |
|
|
|
.. ipython:: |
|
|
|
@doctest float |
|
In [154]: 0.1 + 0.2 |
|
Out[154]: 0.3 |
|
|
|
allows the actual output ``0.30000000000000004`` to match the expected output |
|
due to a comparison with `np.allclose`. |
|
|
|
This module contains handlers for the @doctest pseudo-decorator. Handlers |
|
should have the following function signature:: |
|
|
|
handler(sphinx_shell, args, input_lines, found, submitted) |
|
|
|
where `sphinx_shell` is the embedded Sphinx shell, `args` contains the list |
|
of arguments that follow: '@doctest handler_name', `input_lines` contains |
|
a list of the lines relevant to the current doctest, `found` is a string |
|
containing the output from the IPython shell, and `submitted` is a string |
|
containing the expected output from the IPython shell. |
|
|
|
Handlers must be registered in the `doctests` dict at the end of this module. |
|
|
|
""" |
|
|
|
def str_to_array(s): |
|
""" |
|
Simplistic converter of strings from repr to float NumPy arrays. |
|
|
|
If the repr representation has ellipsis in it, then this will fail. |
|
|
|
Parameters |
|
---------- |
|
s : str |
|
The repr version of a NumPy array. |
|
|
|
Examples |
|
-------- |
|
>>> s = "array([ 0.3, inf, nan])" |
|
>>> a = str_to_array(s) |
|
|
|
""" |
|
import numpy as np |
|
|
|
|
|
|
|
from numpy import inf, nan |
|
|
|
if s.startswith(u'array'): |
|
|
|
s = s[6:-1] |
|
|
|
if s.startswith(u'['): |
|
a = np.array(eval(s), dtype=float) |
|
else: |
|
|
|
a = np.atleast_1d(float(s)) |
|
return a |
|
|
|
def float_doctest(sphinx_shell, args, input_lines, found, submitted): |
|
""" |
|
Doctest which allow the submitted output to vary slightly from the input. |
|
|
|
Here is how it might appear in an rst file: |
|
|
|
.. code-block:: rst |
|
|
|
.. ipython:: |
|
|
|
@doctest float |
|
In [1]: 0.1 + 0.2 |
|
Out[1]: 0.3 |
|
|
|
""" |
|
import numpy as np |
|
|
|
if len(args) == 2: |
|
rtol = 1e-05 |
|
atol = 1e-08 |
|
else: |
|
|
|
try: |
|
rtol = float(args[2]) |
|
atol = float(args[3]) |
|
except IndexError as e: |
|
e = ("Both `rtol` and `atol` must be specified " |
|
"if either are specified: {0}".format(args)) |
|
raise IndexError(e) from e |
|
|
|
try: |
|
submitted = str_to_array(submitted) |
|
found = str_to_array(found) |
|
except: |
|
|
|
error = True |
|
else: |
|
found_isnan = np.isnan(found) |
|
submitted_isnan = np.isnan(submitted) |
|
error = not np.allclose(found_isnan, submitted_isnan) |
|
error |= not np.allclose(found[~found_isnan], |
|
submitted[~submitted_isnan], |
|
rtol=rtol, atol=atol) |
|
|
|
TAB = ' ' * 4 |
|
directive = sphinx_shell.directive |
|
if directive is None: |
|
source = 'Unavailable' |
|
content = 'Unavailable' |
|
else: |
|
source = directive.state.document.current_source |
|
|
|
content = '\n'.join([TAB + line for line in directive.content]) |
|
|
|
if error: |
|
|
|
e = ('doctest float comparison failure\n\n' |
|
'Document source: {0}\n\n' |
|
'Raw content: \n{1}\n\n' |
|
'On input line(s):\n{TAB}{2}\n\n' |
|
'we found output:\n{TAB}{3}\n\n' |
|
'instead of the expected:\n{TAB}{4}\n\n') |
|
e = e.format(source, content, '\n'.join(input_lines), repr(found), |
|
repr(submitted), TAB=TAB) |
|
raise RuntimeError(e) |
|
|
|
|
|
|
|
doctests = { |
|
'float': float_doctest, |
|
} |
|
|