From d51d45aa0d8a7e53c80382dbeded78ae4e9cde1a Mon Sep 17 00:00:00 2001 From: Kosta Ilic Date: Wed, 21 Jan 2026 14:24:04 -0600 Subject: [PATCH 1/5] TST: add test that shows __doctest_requires__ fails with contraint --- tests/test_doctestplus.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/test_doctestplus.py b/tests/test_doctestplus.py index 9476494..5169a4a 100644 --- a/tests/test_doctestplus.py +++ b/tests/test_doctestplus.py @@ -1565,6 +1565,9 @@ def test_requires_module_variable(testdir): __doctest_requires__ = { ("f",): ["module_that_is_not_availabe"], ("g",): ["pytest"], + ("h",): ["pytest>=1.0"], + ("i",): ["pytest<1.0"], + ("j",): ["module_that_is_not_availabe>=1.0"], } def f(): @@ -1575,13 +1578,38 @@ def f(): def g(): ''' - Test that call to `pytest.importorskip` is not visible + Make sure out internal variables are not visible. + >>> assert "ModuleChecker" not in locals() >>> assert "pytest" not in locals() >>> assert "___" not in locals() >>> 1 + 1 2 ''' pass + + def h(): + ''' + Make sure out internal variables are not visible. + + >>> assert "ModuleChecker" not in locals() + >>> assert "pytest" not in locals() + >>> assert "___" not in locals() + >>> 1 + 1 + 2 + ''' + pass + + def i(): + ''' + >>> assert False, "This should be skipped due to version requirement not being met" + ''' + skip + + def j(): + ''' + >>> import module_that_is_not_availabe + ''' + skip """) - testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=1, skipped=1) + testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=2, skipped=3) From 4cb058c426d2e9b985201dfbf7be00d5e534d9a5 Mon Sep 17 00:00:00 2001 From: Kosta Ilic Date: Wed, 21 Jan 2026 14:28:05 -0600 Subject: [PATCH 2/5] BUG: fix __doctest_requires__ when constraint is present --- pytest_doctestplus/plugin.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pytest_doctestplus/plugin.py b/pytest_doctestplus/plugin.py index b8b70aa..23393d6 100644 --- a/pytest_doctestplus/plugin.py +++ b/pytest_doctestplus/plugin.py @@ -874,7 +874,7 @@ def conditionally_insert_skip(test): continue # The pattern does not apply for mod in mods: - self._prepend_importorskip(test, module=mod) + self._prepend_module_check(test, module=mod) return True for _test in tests: @@ -893,17 +893,20 @@ def _prepend_skip(self, test): importorskip = doctest.Example(source=source, want="") test.examples.insert(0, importorskip) - def _prepend_importorskip(self, test, *, module): - """Prepends `pytest.importorskip` before the doctest.""" + def _prepend_module_check(self, test, *, module): + """Prepends module checker before the doctest.""" + escaped_module = module.replace("'", "\\'") source = ( + "from pytest_doctestplus.utils import ModuleChecker; " "import pytest; " # Hide output of this statement in `___`, otherwise doctests fail - f"___ = pytest.importorskip({module!r}); " + f"___ = ModuleChecker().check('{escaped_module}') or " + f"pytest.skip('could not import {escaped_module}'); " # Don't impact what's available in the namespace - "del pytest; del ___" + "del ModuleChecker, pytest, ___" ) - importorskip = doctest.Example(source=source, want="") - test.examples.insert(0, importorskip) + module_check = doctest.Example(source=source, want="") + test.examples.insert(0, module_check) def write_modified_file(fname, new_fname, changes, encoding=None): From 789ba6e8c29c8d55c2b8a77aaf767d1cb6e22917 Mon Sep 17 00:00:00 2001 From: Kosta Ilic Date: Wed, 21 Jan 2026 14:33:35 -0600 Subject: [PATCH 3/5] DOC: update changelist --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0a806e1..10da5f3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,9 @@ 1.8.0 (unreleased) ================== -- No changes yet. +- Fixing bug where `__doctest_requires__` with version specifiers (e.g., + `numpy>=2.0`) incorrectly skipped tests even when dependencies were + satisfied. [#318] 1.7.0 (2026-01-07) ================== From 8d97d4a5cece711830940d9036d25a56e1bfc95c Mon Sep 17 00:00:00 2001 From: Kosta Ilic Date: Thu, 22 Jan 2026 14:37:57 -0600 Subject: [PATCH 4/5] TST: use pass instead of skip in test_requires_module_variable --- tests/test_doctestplus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_doctestplus.py b/tests/test_doctestplus.py index 5169a4a..d36e012 100644 --- a/tests/test_doctestplus.py +++ b/tests/test_doctestplus.py @@ -1604,12 +1604,12 @@ def i(): ''' >>> assert False, "This should be skipped due to version requirement not being met" ''' - skip + pass def j(): ''' >>> import module_that_is_not_availabe ''' - skip + pass """) testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=2, skipped=3) From 223289805bf129d4ae18088c22a49c199826a9dc Mon Sep 17 00:00:00 2001 From: KostaIlic2 Date: Thu, 22 Jan 2026 14:40:10 -0600 Subject: [PATCH 5/5] Update CHANGES.rst Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com> --- CHANGES.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 10da5f3..c1b8b90 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,9 @@ 1.8.0 (unreleased) ================== -- Fixing bug where `__doctest_requires__` with version specifiers (e.g., - `numpy>=2.0`) incorrectly skipped tests even when dependencies were - satisfied. [#318] +- Fixing bug where ``__doctest_requires__`` with version specifiers (e.g., + ``numpy>=2.0``) incorrectly skipped tests even when dependencies were + satisfied. [#319] 1.7.0 (2026-01-07) ==================