| """ |
| 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: |
| 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, |
| } |
|
|