3

Consider the following function:

# in mymodule.py:

def myfunc(num,mystring):
    """
    replicates a string

    :param num: a positive integer
    :param mystring: a string
    :return: string concatenated with itself num times

    :Example:
        >>> num = 3
        >>> mystring = "lol"
        >>> myfunc(num, mystring)
            "lollollol"
    """
    return num*mystring

How can I make Sphinx doctest work, in order to verify the example code in

:Example:´ actually does work? Whenever I runmake doctest`

it says that 3 tests were run, but 1 fails. Deleting the last two rows, starting with

myfunc(num, mystring),

it says that 2 tests were run sucessfully. So I must be doing something wrong with that line. But what?

EDIT Here's the terminal output (traceback) for the full code; it seems that somehow it can't find the function for which I've written the docstring and I'm running the doctest:

Running Sphinx v1.8.3
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
building [doctest]: targets for 1 source files that are out of date
updating environment: [] 0 added, 1 changed, 0 removed
reading sources... [100%] index                                                 
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
running tests...

Document: index
---------------
**********************************************************************
File "index.rst", line ?, in default
Failed example:
    myfunc(num, mystring)
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib/python3.6/doctest.py", line 1330, in __run
        compileflags, 1), test.globs)
      File "<doctest default[2]>", line 1, in <module>
        myfunc(num, mystring)
    NameError: name 'myfunc' is not defined
**********************************************************************
1 items had failures:
   1 of   3 in default
3 tests in 1 items.
2 passed and 1 failed.
***Test Failed*** 1 failures.

Doctest summary
===============
    3 tests
    1 failure in tests
    0 failures in setup code
    0 failures in cleanup code
build finished with problems.
Makefile:19: recipe for target 'doctest' failed
Demi-Lune
  • 1,868
  • 2
  • 15
  • 26
l7ll7
  • 1,309
  • 1
  • 10
  • 20

4 Answers4

3

Sphinx+doctest is not importing the module. You can either help him in the docstring:

    :Example:
        >>> from mymodule import myfunc
        >>> myfunc(3, 'lol')
        'lollollol'

or add this in conf.py:

doctest_global_setup = '''
#if required, modif sys.path:
import sys
sys.path.append('../directory_containing_mymodule/')
from mymodule import *
'''

cf. https://www.sphinx-doc.org/en/master/usage/extensions/doctest.html

I'll be interested to hear if there are simpler answers.

Demi-Lune
  • 1,868
  • 2
  • 15
  • 26
  • This almost worked! The import in `:Example:` works, but if I add it to the `conf.py` it doesn't. Is there anything I could to get `conf.py` to work? – l7ll7 Oct 02 '19 at 09:09
  • Is there actually any way that I could import everything in `conf.py` from the folder I'm working in, so that I dont' each time I create a new module and write docstrings for functions/methods inside it, I have to add those to `conf.py`? – l7ll7 Oct 02 '19 at 09:14
  • Is your module already packaged (with `setup.py` and `__init__.py`)? sphinx may not be running in the folder that contains `mymodule.py`, so you probably need a `setup.py` or  set `PYTHONPATH` to be safe. – Demi-Lune Oct 02 '19 at 10:50
  • No, my module is just a file in `myproject`, whereas all Sphinx-related files are in `myproject/docs`. I've never packaged a module (and I don't know what to do with `PYTHONPATH` either), because I'm pretty new to Python; sorry :( – l7ll7 Oct 02 '19 at 12:28
  • @l7ll7, PYTHONPATH (or sys.path) tells where your personal modules are (when they are not in current directory), see for example: https://stackoverflow.com/questions/3402168/permanently-add-a-directory-to-pythonpath – Demi-Lune Oct 02 '19 at 20:17
  • I've added for example in conf.py a `sys.path` manipulation. You'll need to adapt to your files locations. – Demi-Lune Oct 02 '19 at 21:24
1

On my terminal and outside Sphinx, I have to left align "lollollol" and use simple quotes in order to successfully run doctest:

    :Example:
        >>> num = 3
        >>> mystring = "lol"
        >>> myfunc(num, mystring)
        'lollollol'
aflp91
  • 641
  • 5
  • 12
  • Unfortunately it doesn't seem to work either :(. I've attaced the traceback to my question. – l7ll7 Oct 02 '19 at 07:18
  • Correct alignment and single quotes are indeed part of the solution. When executing `3 * "lol"` in an interactive session, the output is `'lollollol'`. Doctest does not accept `"lollollol"` as the expected output. – mzjn Oct 02 '19 at 09:01
1

As Demi-Lune has already said, one needs to add the filepath of your .py files to the conf.py file. But this should not be only passed to the command

doctest_global_setup='''.... import sys'''

but rather add it to the general conf.py Path setup by declaring

import os
import sys
sys.path.insert(0, os.path.abspath('.'))

substituting '.' by your absolute path. (You may also leave the '.' and add your module.py files to the source-folder of your sphinx project. Though I would not recommend this for larger projects.)

Then you only need to declare in the conf.py file

doctest_global_setup='from module import *'

Now you may write in your module.py function

def foo():
   """
   >>>foo()
   ['bar','fafa','bara']
   """
   return ['bar','fafa','bara']

The perk of declaring the path for the whole conf.py file is that other extensions such as autodoc can also detect your module.py. In this case you only need to write in your .rst file

..autofunction:: module.foo()

It will take foo() from module.py and execute the doctest commands.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
Jakob
  • 168
  • 1
  • 6
0

I'm pretty sure this kind of example will be correctly found and evaluated by Sybil with this in your conftest.py:

from sybil import Sybil
from sybil.parsers.doctest import DocTestParser

pytest_collect_file = Sybil(
    parsers=[DocTestParser()],
    patterns=['*.py'],
).pytest()
Chris Withers
  • 10,837
  • 4
  • 33
  • 51