xdoctest.directive module

Directives special comments that influence the runtime behavior of doctests. There are two types of directives: block and inline

Block directives are specified on their own line and influence the behavior of multiple lines of code.

Inline directives are specified after in the same line of code and only influence that line / repl part.

Basic Directives

Basic directives correspond directly to an xdoctest runtime state attribute. These can be modified by directly using the xdoctest directive syntax. The following documents all supported basic directives.

The basic directives and their defaults are as follows:

  • DONT_ACCEPT_BLANKLINE: False,

  • ELLIPSIS: True,

  • IGNORE_WHITESPACE: False,

  • IGNORE_EXCEPTION_DETAIL: False,

  • NORMALIZE_WHITESPACE: True,

  • IGNORE_WANT: False,

  • NORMALIZE_REPR: True,

  • REPORT_CDIFF: False,

  • REPORT_NDIFF: False,

  • REPORT_UDIFF: True,

  • SKIP: False

Use - to disable a directive that is enabled by default, e.g. # xdoctest: -ELLIPSIS, or use + to enable a directive that is disabled by default, e.g. # xdoctest +SKIP.

Advanced Directives

Advanced directives may take arguments, be conditional, or modify the runtime state in complex ways. For instance, whereas most directives modify a boolean value in the runtime state, the advanced REQUIRES directive either adds or removes a value from a set of unmet requirements. Doctests will only run if there are no unmet requirements.

Currently the only advanced directive is REQUIRES(.). Multiple arguments may be specified, by separating them with commas. The currently available arguments allow you to condition on:

  • Special operating system / python implementation / python version tags, via: WIN32, LINUX, DARWIN, POSIX, NT, JAVA, CPYTHON, IRONPYTHON, JYTHON, PYPY, PY2, PY3. (e.g. # xdoctest +REQUIRES(WIN32))

  • Command line flags, via: --<someflag>, (e.g. # xdoctest +REQUIRES(--verbose))

  • If a python module is installed, via: module:<modname>, (e.g. # xdoctest +REQUIRES(module:numpy))

  • Environment variables, via: env:<varname>==<val>, (e.g. # xdoctest +REQUIRES(env:MYENVIRON==1))

Todo

  • [ ] Directive for Python version: e.g. xdoctest: +REQUIRES(Python>=3.7)

  • [ ] Directive for module version: e.g. xdoctest: +REQUIRES(module:rich>=1.0)

  • [ ] Customize directive.

  • [ ] Add SKIPIF directive

Customized Requirements Design:

  • Allow user to specify a customized requirement on the CLI or environ. e.g. XDOCTEST_CUSTOM_MY_REQUIRE=”import torch; torch.cuda.is_available()”

    Then xdoctest: +REQUIRES(custom:MY_REQUIRE) would invoke it and enable the missing requirement if that snippet ended with a truthy or falsy value

CommandLine

python -m xdoctest.directive __doc__

The following example shows how the +SKIP directives may be used to bypass certain places in the code.

Example

>>> # An inline directive appears on the same line as a command and
>>> # only applies to the current line.
>>> raise AssertionError('this will not be run (a)')  # xdoctest: +SKIP
>>> print('This line will print: (A)')
>>> print('This line will print: (B)')
>>> # However, if a directive appears on its own line, then it applies
>>> # too all subsequent lines.
>>> # xdoctest: +SKIP()
>>> raise AssertionError('this will not be run (b)')
>>> print('This line will not print: (A)')
>>> # Note, that SKIP is simply a state and can be disabled to allow
>>> # the program to continue executing.
>>> # xdoctest: -SKIP
>>> print('This line will print: (C)')
>>> print('This line will print: (D)')
>>> # This applies to inline directives as well
>>> # xdoctest: +SKIP("an assertion would occur")
>>> raise AssertionError('this will not be run (c)')
>>> print('This line will print: (E)')  # xdoctest: -SKIP
>>> raise AssertionError('this will not be run (d)')
>>> # xdoctest: -SKIP("a reason can be given as an argument")
>>> print('This line will print: (F)')

This next examples illustrates how to use the advanced +REQUIRES() directive. Note, the REQUIRES and SKIP states are independent.

Example

>>> import sys
>>> plat = sys.platform
>>> count = 0
>>> # xdoctest: +REQUIRES(WIN32)
>>> assert plat.startswith('win32'), 'this only runs on windows'
>>> count += 1
>>> # xdoctest: -REQUIRES(WIN32)
>>> # xdoctest: +REQUIRES(LINUX)
>>> assert plat.startswith('linux'), 'this only runs on linux'
>>> count += 1
>>> # xdoctest: -REQUIRES(LINUX)
>>> # xdoctest: +REQUIRES(DARWIN)
>>> assert plat.startswith('darwin'), 'this only runs on osx'
>>> count += 1
>>> # xdoctest: -REQUIRES(DARWIN)
>>> print(count)
>>> import sys
>>> if any(plat.startswith(n) for n in {'linux', 'win32', 'darwin'}):
>>>     assert count == 1, 'Exactly one of the above parts should have run'
>>> else:
>>>     assert count == 0, 'Nothing should have run on plat={}'.format(plat)
>>> # xdoctest: +REQUIRES(--verbose)
>>> print('This is only printed if you run with --verbose')

Example

>>> # New in 0.7.3: the requires directive can accept module names
>>> # xdoctest: +REQUIRES(module:foobar)
xdoctest.directive.named(key, pattern)[source]

helper for regex

Parameters:
  • key (str)

  • pattern (str)

Returns:

str

class xdoctest.directive.Effect(action, key, value)

Bases: tuple

Create new instance of Effect(action, key, value)

action

Alias for field number 0

key

Alias for field number 1

value

Alias for field number 2

class xdoctest.directive.RuntimeState(default_state=None)[source]

Bases: NiceRepr

Maintains the runtime state for a single run() of an example

Inline directives are pushed and popped after the line is run. Otherwise directives persist until another directive disables it.

CommandLine

xdoctest -m xdoctest.directive RuntimeState

Example

>>> from xdoctest.directive import *
>>> runstate = RuntimeState()
>>> assert not runstate['IGNORE_WHITESPACE']
>>> # Directives modify the runtime state
>>> directives = list(Directive.extract('# xdoc: -ELLIPSIS, +IGNORE_WHITESPACE'))
>>> runstate.update(directives)
>>> assert not runstate['ELLIPSIS']
>>> assert runstate['IGNORE_WHITESPACE']
>>> # Inline directives only persist until the next update
>>> directives = [Directive('IGNORE_WHITESPACE', False, inline=True)]
>>> runstate.update(directives)
>>> assert not runstate['IGNORE_WHITESPACE']
>>> runstate.update({})
>>> assert runstate['IGNORE_WHITESPACE']

Example

>>> # xdoc: +IGNORE_WHITESPACE
>>> print(str(RuntimeState()))
<RuntimeState({
    DONT_ACCEPT_BLANKLINE: False,
    ELLIPSIS: True,
    IGNORE_EXCEPTION_DETAIL: False,
    IGNORE_WANT: False,
    IGNORE_WHITESPACE: False,
    NORMALIZE_REPR: True,
    NORMALIZE_WHITESPACE: True,
    REPORT_CDIFF: False,
    REPORT_NDIFF: False,
    REPORT_UDIFF: True,
    REQUIRES: set(...),
    SKIP: False
})>
Parameters:

default_state (None | dict) – starting default state, if unspecified falls back to the global DEFAULT_RUNTIME_STATE

to_dict()[source]
Returns:

OrderedDict

set_report_style(reportchoice, state=None)[source]
Parameters:
  • reportchoice (str) – name of report style

  • state (None | Dict) – if unspecified defaults to the global state

Example

>>> from xdoctest.directive import *
>>> runstate = RuntimeState()
>>> assert runstate['REPORT_UDIFF']
>>> runstate.set_report_style('ndiff')
>>> assert not runstate['REPORT_UDIFF']
>>> assert runstate['REPORT_NDIFF']
update(directives)[source]

Update the runtime state given a set of directives

Parameters:

directives (List[Directive]) – list of directives. The effects method is used to update this object.

class xdoctest.directive.Directive(name, positive=True, args=[], inline=None)[source]

Bases: NiceRepr

Directives modify the runtime state.

Parameters:
  • name (str) – The name of the directive

  • positive (bool) – if it is enabling / disabling

  • args (List[str]) – arguments given to the directive

  • inline (bool | None) – True if this is an inline directive (i.e. only impacts a single line)

classmethod extract(text)[source]

Parses directives from a line or repl line

Parameters:

text (str) – must correspond to exactly one PS1 line and its PS2 followups.

Yields:

Directive – directive - the parsed directives

Note

The original doctest module sometimes yielded false positives for a directive pattern. Because xdoctest is parsing the text, this issue does not occur.

Example

>>> from xdoctest.directive import Directive, RuntimeState
>>> state = RuntimeState()
>>> assert len(state['REQUIRES']) == 0
>>> extracted1 = list(Directive.extract('# xdoctest: +REQUIRES(CPYTHON)'))
>>> extracted2 = list(Directive.extract('# xdoctest: +REQUIRES(PYPY)'))
>>> print('extracted1 = {!r}'.format(extracted1))
>>> print('extracted2 = {!r}'.format(extracted2))
>>> effect1 = extracted1[0].effects()[0]
>>> effect2 = extracted2[0].effects()[0]
>>> print('effect1 = {!r}'.format(effect1))
>>> print('effect2 = {!r}'.format(effect2))
>>> assert effect1.value == 'CPYTHON'
>>> assert effect2.value == 'PYPY'
>>> # At least one of these will not be satisfied
>>> assert effect1.action == 'set.add' or effect2.action == 'set.add'
>>> state.update(extracted1)
>>> state.update(extracted2)
>>> print('state = {!r}'.format(state))
>>> assert len(state['REQUIRES']) > 0

Example

>>> from xdoctest.directive import Directive
>>> text = '# xdoc: + SKIP'
>>> print(', '.join(list(map(str, Directive.extract(text)))))
<Directive(+SKIP)>
>>> # Directive with args
>>> text = '# xdoctest: requires(--show)'
>>> print(', '.join(list(map(str, Directive.extract(text)))))
<Directive(+REQUIRES(--show))>
>>> # Malformatted directives are ignored
>>> # xdoctest: +REQUIRES(module:pytest)
>>> text = '# xdoctest: does_not_exist, skip'
>>> import pytest
>>> with pytest.warns(Warning) as record:
>>>     print(', '.join(list(map(str, Directive.extract(text)))))
<Directive(+SKIP)>
>>> # Two directives in one line
>>> text = '# xdoctest: +ELLIPSIS, -NORMALIZE_WHITESPACE'
>>> print(', '.join(list(map(str, Directive.extract(text)))))
<Directive(+ELLIPSIS)>, <Directive(-NORMALIZE_WHITESPACE)>
>>> # Make sure commas inside parens are not split
>>> text = '# xdoctest: +REQUIRES(module:foo,module:bar)'
>>> print(', '.join(list(map(str, Directive.extract(text)))))
<Directive(+REQUIRES(module:foo, module:bar))>

Example

>>> from xdoctest.directive import Directive, RuntimeState
>>> any(Directive.extract(' # xdoctest: skip'))
True
>>> any(Directive.extract(' # badprefix: not-a-directive'))
False
>>> any(Directive.extract(' # xdoctest: skip'))
True
>>> any(Directive.extract(' # badprefix: not-a-directive'))
False
effect(argv=None, environ=None)[source]
effects(argv=None, environ=None)[source]

Returns how this directive modifies a RuntimeState object

This is called by RuntimeState.update() to update itself

Parameters:
  • argv (List[str] | None) – Command line the directive is interpreted in the context of. If unspecified, uses sys.argv.

  • environ (Dict[str, str] | None) – Environment variables the directive is interpreted in the context of. If unspecified, uses os.environ.

Returns:

list of named tuples containing:

action (str): code indicating how to update key (str): name of runtime state item to modify value (object): value to modify with

Return type:

List[Effect]

CommandLine

xdoctest -m xdoctest.directive Directive.effects

Example

>>> Directive('SKIP').effects()[0]
Effect(action='assign', key='SKIP', value=True)
>>> Directive('SKIP', inline=True).effects()[0]
Effect(action='assign', key='SKIP', value=True)
>>> Directive('REQUIRES', args=['-s']).effects(argv=['-s'])[0]
Effect(action='noop', key='REQUIRES', value='-s')
>>> Directive('REQUIRES', args=['-s']).effects(argv=[])[0]
Effect(action='set.add', key='REQUIRES', value='-s')
>>> Directive('ELLIPSIS', args=['-s']).effects(argv=[])[0]
Effect(action='assign', key='ELLIPSIS', value=True)

Doctest

>>> # requirement directive with module
>>> directive = list(Directive.extract('# xdoctest: requires(module:xdoctest)'))[0]
>>> print('directive = {}'.format(directive))
>>> print('directive.effects() = {}'.format(directive.effects()[0]))
directive = <Directive(+REQUIRES(module:xdoctest))>
directive.effects() = Effect(action='noop', key='REQUIRES', value='module:xdoctest')
>>> directive = list(Directive.extract('# xdoctest: requires(module:notamodule)'))[0]
>>> print('directive = {}'.format(directive))
>>> print('directive.effects() = {}'.format(directive.effects()[0]))
directive = <Directive(+REQUIRES(module:notamodule))>
directive.effects() = Effect(action='set.add', key='REQUIRES', value='module:notamodule')
>>> directive = list(Directive.extract('# xdoctest: requires(env:FOO==1)'))[0]
>>> print('directive = {}'.format(directive))
>>> print('directive.effects() = {}'.format(directive.effects(environ={})[0]))
directive = <Directive(+REQUIRES(env:FOO==1))>
directive.effects() = Effect(action='set.add', key='REQUIRES', value='env:FOO==1')
>>> directive = list(Directive.extract('# xdoctest: requires(env:FOO==1)'))[0]
>>> print('directive = {}'.format(directive))
>>> print('directive.effects() = {}'.format(directive.effects(environ={'FOO': '1'})[0]))
directive = <Directive(+REQUIRES(env:FOO==1))>
directive.effects() = Effect(action='noop', key='REQUIRES', value='env:FOO==1')
>>> # requirement directive with two args
>>> directive = list(Directive.extract('# xdoctest: requires(--show, module:xdoctest)'))[0]
>>> print('directive = {}'.format(directive))
>>> for effect in directive.effects():
>>>     print('effect = {!r}'.format(effect))
directive = <Directive(+REQUIRES(--show, module:xdoctest))>
effect = Effect(action='set.add', key='REQUIRES', value='--show')
effect = Effect(action='noop', key='REQUIRES', value='module:xdoctest')
xdoctest.directive.parse_directive_optstr(optpart, inline=None)[source]

Parses the information in the directive from the “optpart”

optstrs are:

optionally prefixed with + (default) or - comma separated may contain one paren enclosed argument (experimental) all spaces are ignored

Parameters:
  • optpart (str) – the string corresponding to the operation

  • inline (None | bool) – True if the directive only applies to a single line.

Returns:

the parsed directive

Return type:

Directive

Example

>>> print(str(parse_directive_optstr('+IGNORE_WHITESPACE')))
<Directive(+IGNORE_WHITESPACE)>