diff --git a/CHANGES.rst b/CHANGES.rst index 095e28f..ba905a0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,10 @@ - Fixing compatibility with pytest-asyncio. [#278] +- Adding new directive ``doctest-requires-all`` to conditionally skip all + doctests in narrative documentations based on availability of + dependencies. [#280] + - Versions of Python <3.9 are no longer supported. [#274] 1.3.0 (2024-11-25) diff --git a/README.rst b/README.rst index 739c22b..48b26f6 100644 --- a/README.rst +++ b/README.rst @@ -336,6 +336,9 @@ the package's ``setup.cfg`` file. The syntax for this option is a list of Multiple requirements can be specified if separated by semicolons. +It is also possible to conditionally skip all the doctests in a narrative +documentation with ``doctest-requires-all``. + Remote Data ~~~~~~~~~~~ diff --git a/pytest_doctestplus/plugin.py b/pytest_doctestplus/plugin.py index 3f8e6da..d323c12 100644 --- a/pytest_doctestplus/plugin.py +++ b/pytest_doctestplus/plugin.py @@ -389,6 +389,10 @@ class DocTestParserPlus(doctest.DocTestParser): doctest chunk if the given modules/packages are not installed. + - ``.. doctest-requires-all:: module1, module2``: Skip all subsequent + doctest chunks if the given modules/packages are not + installed. + - ``.. doctest-skip-all``: Skip all subsequent doctests. - ``.. doctest-remote-data::``: Skip the next doctest chunk if @@ -422,10 +426,18 @@ def parse(self, s, name=None): if isinstance(entry, str) and entry: required = [] + required_all = [] skip_next = False lines = entry.strip().splitlines() + requires_all_match = [re.match( + fr'{comment_char}\s+doctest-requires-all\s*::\s+(.*)', x) for x in lines] + if any(requires_all_match): + required_all = [re.split(r'\s*[,\s]\s*', match.group(1)) for match in requires_all_match if match][0] + + required_modules_all = DocTestFinderPlus.check_required_modules(required_all) + if any(re.match( - f'{comment_char} doctest-skip-all', x.strip()) for x in lines): + f'{comment_char} doctest-skip-all', x.strip()) for x in lines) or not required_modules_all: skip_all = True continue diff --git a/pytest_doctestplus/sphinx/doctestplus.py b/pytest_doctestplus/sphinx/doctestplus.py index b9b24a2..f370005 100644 --- a/pytest_doctestplus/sphinx/doctestplus.py +++ b/pytest_doctestplus/sphinx/doctestplus.py @@ -43,6 +43,7 @@ class DoctestRequiresDirective(DoctestSkipDirective): def setup(app): app.add_directive('doctest-requires', DoctestRequiresDirective) + app.add_directive('doctest-requires-all', DoctestRequiresDirective) app.add_directive('doctest-skip', DoctestSkipDirective) app.add_directive('doctest-skip-all', DoctestSkipDirective) app.add_directive('doctest', DoctestSkipDirective, override=True) diff --git a/tests/test_doctestplus.py b/tests/test_doctestplus.py index 6b54786..e2a3abd 100644 --- a/tests/test_doctestplus.py +++ b/tests/test_doctestplus.py @@ -435,6 +435,29 @@ def test_requires(testdir): testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1) +def test_requires_all(testdir): + testdir.makeini( + """ + [pytest] + doctestplus = enabled + """) + + # should be ignored + p = testdir.makefile( + '.rst', + """ + .. doctest-requires-all:: foobar + + >>> import foobar + + This is a narrative line, before another doctest snippet + + >>> import foobar + """ + ) + testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1) + + def test_ignore_warnings_module(testdir): # First check that we get a warning if we don't add the IGNORE_WARNINGS