diff --git a/CHANGES b/CHANGES index 983ce93..444e527 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,16 @@ $ uvx --from 'gp-libs' --prerelease allow gp-libs _Notes on upcoming releases will be added here_ +### What's new + +#### New doctest flag: `+HIDE` (#79) + +Doctest examples can now be marked `# doctest: +HIDE`. The flag is a +no-op for the test runner — the example still executes and its output is +still checked — and exists so documentation tooling can drop the marked +lines from *rendered* output while the runner keeps executing them from +source. + ## gp-libs 0.0.18 (2026-06-27) gp-libs 0.0.18 refreshes the project documentation and maintainer workflow around the shared git-pull docs stack. The docs now use the Library Skeleton page shape, the [gp-sphinx](https://gp-sphinx.git-pull.com/) theme and API presentation packages, and a [just](https://just.systems/)-based command surface that matches the rest of the workspace. Readers get a more navigable site; maintainers get reusable commands and fewer repo-local Sphinx assets to keep in sync. diff --git a/src/pytest_doctest_docutils.py b/src/pytest_doctest_docutils.py index a5ca7f7..13c2db0 100644 --- a/src/pytest_doctest_docutils.py +++ b/src/pytest_doctest_docutils.py @@ -67,6 +67,11 @@ def pytest_configure(config: pytest.Config) -> None: Todo: Find a way to make these plugins cooperate without collecting twice. """ + # Register HIDE eagerly, before collection parses any docstring. The .py + # path delegates to pytest's own DoctestModule (which never calls our + # _get_flag_lookup), so registering it here is what lets a docstring carry + # ``# doctest: +HIDE`` without raising ``invalid option`` at parse time. + _get_hide_flag() if config.pluginmanager.has_plugin("doctest"): config.pluginmanager.set_blocked("doctest") @@ -232,6 +237,20 @@ def _get_number_flag() -> int: return doctest.register_optionflag("NUMBER") +def _get_hide_flag() -> int: + """Register and return the HIDE flag. + + ``HIDE`` is a no-op for execution: the output checker never consults it. + It marks a doctest example that documentation tooling should drop from the + rendered output while still running it as a test. Registering it here means + ``# doctest: +HIDE`` parses instead of raising ``ValueError: invalid + option`` at collection time. + """ + import doctest + + return doctest.register_optionflag("HIDE") + + def _get_flag_lookup() -> dict[str, int]: import doctest @@ -245,6 +264,7 @@ def _get_flag_lookup() -> dict[str, int]: "ALLOW_UNICODE": _get_allow_unicode_flag(), "ALLOW_BYTES": _get_allow_bytes_flag(), "NUMBER": _get_number_flag(), + "HIDE": _get_hide_flag(), } diff --git a/tests/test_pytest_doctest_docutils.py b/tests/test_pytest_doctest_docutils.py index 6a5d06c..70fbd5a 100644 --- a/tests/test_pytest_doctest_docutils.py +++ b/tests/test_pytest_doctest_docutils.py @@ -480,3 +480,43 @@ def test_ignore_build_artifacts( result = pytester.runpytest(str(docs_path), "--doctest-docutils-modules") result.assert_outcomes(passed=1) + + +def test_hide_optionflag_py_docstring( + pytester: _pytest.pytester.Pytester, +) -> None: + """``# doctest: +HIDE`` parses and runs as a no-op in a .py docstring. + + ``HIDE`` is registered so documentation tooling can mark a setup example to + drop from rendered output. Without the registration, collection would raise + ``ValueError: ... invalid option: '+HIDE'``. Here it must simply run. + """ + pytester.plugins = ["pytest_doctest_docutils"] + pytester.makefile( + ".ini", + pytest=textwrap.dedent( + """ +[pytest] +addopts=-p no:doctest + """.strip(), + ), + ) + example = pytester.path / "example.py" + example.write_text( + textwrap.dedent( + ''' +def demo() -> int: + """Return a computed value. + + >>> base = 40 # doctest: +HIDE + >>> base + 2 + 42 + """ + return 42 + ''', + ), + encoding="utf-8", + ) + + result = pytester.runpytest(str(example), "--doctest-docutils-modules") + result.assert_outcomes(passed=1)