Skip to content

multipledispatch.dispatch decorator disables docstring tests of doctest #96

Open

Description

@louis-red

Problem description :

import doctest
from multipledispatch import dispatch

def foo(n):
    """
    >>> a = 1
    >>> a
    1
    """

print(doctest.testmod())
# displays TestResults(failed=0, attempted=2)

@dispatch(int)
def foo(n):
    """
    >>> a = 1
    >>> a
    1
    """
    
print(doctest.testmod())
# displays TestResults(failed=0, attempted=0)

Expected results

Ideally, the doctest's tests would still get executed with the multiple dispatch.

Activity

  1. llllllllll commented on Dec 13, 2018

    @llllllllll
    Collaborator

    I am not sure I understand what you mean by "indentation level". This issue is likely because doctest only runs tests on classes and types.FunctionType objects at module scope, not all objects with a __doc__. I ran into a similar issue with toolz.curry. You can explicitly add test cases to doctest by putting a __test__ attribute at module scope. This should be a dictionary from test name (function or class name) to the object which as a __doc__ you wish to test. For example:

    @dispatch(int)
    def foo(n):
        """
        >>> a = 1
        >>> a
        1
        """
    
    
    __test__ = {'foo': foo}

    That can be a bit cumbersome, so I often use a decorator similar to this:

    def ensure_doctest(f):
        """Ensure that an object gets doctested. This is useful for instances
        of objects like curry which are not discovered by default.
    
        Parameters
        ----------
        f : any
            The thing to doctest.
    
        Returns
        -------
        f : any
           ``f`` unchanged.
        """
        sys._getframe(1).f_globals.setdefault('__test__', {})[f.__name__] = f
        return f

    which is used like:

    @ensure_doctest
    @dispatch(int)
    def foo(n):
        ...

    This decorator checks the calling scope (sys._getframe(1)) and looks in the globals (.f_globals) as a dictionary. It adds a __test__ dictionary if it doesn't already exist, and then it adds the function to the dict under it's given __name__.

  2. louis-red commented on Dec 14, 2018

    @louis-red
    Author

    You're right, this has nothing to do with indentation level (thought so without testing this assumption, as functions decorated by multipledispatch.dispatch have their docstrings indented one level more). Will update the issue.

    Forcing doctest to search in decorated foo, by setting __test__ or with your ensure_doctest function, results in :

    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-3-fb60117592cb> in <module>()
         22     """
         23 
    ---> 24 print(doctest.testmod())
         25 # displays TestResults(failed=0, attempted=0)
    
    ~\AppData\Local\Continuum\anaconda3\lib\doctest.py in testmod(m, name, globs, verbose, report, optionflags, extraglobs, raise_on_error, exclude_empty)
       1948         runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
       1949 
    -> 1950     for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
       1951         runner.run(test)
       1952 
    
    ~\AppData\Local\Continuum\anaconda3\lib\doctest.py in find(self, obj, name, module, globs, extraglobs)
        931         # Recursively explore `obj`, extracting DocTests.
        932         tests = []
    --> 933         self._find(tests, obj, name, module, source_lines, globs, {})
        934         # Sort the tests by alpha order of names, for consistency in
        935         # verbose-mode output.  This was a feature of doctest in Pythons
    
    ~\AppData\Local\Continuum\anaconda3\lib\doctest.py in _find(self, tests, obj, name, module, source_lines, globs, seen)
       1008                                      "must be strings, functions, methods, "
       1009                                      "classes, or modules: %r" %
    -> 1010                                      (type(val),))
       1011                 valname = '%s.__test__.%s' % (name, valname)
       1012                 self._find(tests, val, valname, module, source_lines,
    
    ValueError: DocTestFinder.find: __test__ values must be strings, functions, methods, classes, or modules: <class 'multipledispatch.dispatcher.Dispatcher'>
  3. changed the title [-]multipledispatch.dispatch decorator disables docstring tests of doctest with additional indentation[/-] [+]multipledispatch.dispatch decorator disables docstring tests of doctest[/+] on Dec 14, 2018
  4. louis-red commented on Dec 14, 2018

    @louis-red
    Author

    But that worked :

    __test__ = {str(k): v for k, v in foo.funcs.items()}

    giving

    def ensure_dispatch_doctest(f):
        sys._getframe(1).f_globals.setdefault('__test__', {}).update(
            {str(k): v for k, v in f.funcs.items()})
        return f

    Thank you for your help !

  5. tanders commented on Nov 20, 2019

    @tanders

    It appears using functools.wraps within the decorator is another way around that, which would avoid the need for an additional decorator (like ensure_dispatch_doctest). See https://www.rosipov.com/blog/python-doctests-and-decorators-bug/

    Quote from update_wrapper doc (https://docs.python.org/2/library/functools.html#functools.update_wrapper)

    The main intended use for this function is in decorator functions which wrap the decorated function and return the wrapper. If the wrapper function is not updated, the metadata of the returned function will reflect the wrapper definition rather than the original function definition, which is typically less than helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions