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 exampleInline 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
- 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']
- 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. Becausexdoctest
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
- 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:
Example
>>> print(str(parse_directive_optstr('+IGNORE_WHITESPACE'))) <Directive(+IGNORE_WHITESPACE)>