Hello world,
Would Python ever consider adopting a standard for non-renering executable lines of code in documentation?
Context
Doctests are a great way of ensuring that code documentation is accurate to a library, and that those examples work. However, there are times when it is valuable to show a small snippit of code that require larger chunks of setup to validate, or where testing is desired but the Python console syntax is not.
Idea
The minimum requirement would be Python documentation that states, "lines in documentation beginning with the control sequence ##!
are intended to be hidden by any tool that renders the code, but run (without the control sequence) by any tool intended to execute it.
My proposed control sequence was ##!
. It needs to start with a comment so that nothing breaks when trying to run it. !
tends to hint executable-related in POSIX world ($!
for PID, !cmd
or !!
to re-execute a command, and shebang) but #!
is used for shebang, so I landed on ##!
.
Allowing this syntax has the benefit of breaking nothing; tools that can execute it would be strictly opt-in. Anything that does not support the syntax will just ignore the ##!
'd lines of code.
Example
Assume the following code is in a docstring or documentation source file:
##! from typing import Optional, TYPE_CHECKING
##! if TYPE_CHECKING:
##! from mymodule.something import CoolClass
def fn(x: Optional[int]) -> CoolClass:
return CoolClass(x)
##! assert fn(5) == CoolClass(5)
Anything that renders the code to readers (IDE hints, documentation tool, etc) simply removes lines starting with the control sequences. So, the above example displays as the following terse snippet:
def fn(x: Optional[int]) -> CoolClass:
return CoolClass(x)
Anything that wants to use the full executable code just removes the control sequence itself. So a linter, type checker, or test runner would wind up with:
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from mymodule.something import CoolClass
def fn(x: Optional[int]) -> CoolClass:
return CoolClass(x)
assert fn(5) == CoolClass(5)
The main use case for this is likely plain Python code blocks that are outside the scope of doctest
. However, there is no reason that doctest
or a doctest
runner wouldn’t also be able to understand the syntax; >>> ##! foo()
or ... ##! foo = "bar"
would just get run as if the ##!
were not there.
Inspiration
This is inspired from the way that Rust does documentation tests and allows hiding lines, as shown here: Documentation tests - The rustdoc book. In my opinion, there are quite a few excellent things that they do with documentation, this being one of the nice little things, and I think seeing some of those brought to Python would encourage users to write better docs.
The best existing alternative for hiding code lines is sphinx.ext.doctest. However, that requires boilerplate (.. testsetup
/.. testcleanup
/.. testcode
), is RST-only, doesn’t allow for hiding code lines not at the beginning or end (e.g. if you wanted to show from __future__ import annotations
, hide imports, and show the rest of the code) and requires the pycon syntax that I believe to be somewhat limiting (e.g., copy/paste from working code is not straightforward, IDEs generally do not predict >>>
/...
continuation).
I considered a few possible ways that a thin wrapper could make use of this:
- black could format the example as-is without worrying about maintaining
>>>
/...
- After stripping the control sequences, Mypy could verify the snippet is typed correctly
- flake8 could verify stripped code
- A pytest plugin could execute the stripped code
- Any of these would easily work across RST, Markdown, generic docstrings, and others.
Other considerations
I started a discussion for this at the Sphinx project and included some rough implementation suggestions (with examples) in this comment. But, after consideration, I figured that discussion may be worth having here.
I understand that this may seem like something best implemented by plugins or tools. My motivation for asking here is that I feel something like this has the potential to be quite useful, but also subject to fragmentation, meaning that some tools may begin to adapt control sequences that do not work well with one another. So, my order of acceptance preference from least to most potential for fragmentation is as follows:
- Python style suggestion (here)
- Sphinx (linked)
- Plugin / other tools
Other tags like rustdoc
has could also be useful (ignore
, should_panic
, no_run
, edition2018
), but these may fit better into the relevant tools that would consume them. As opposed to this nondisplay tag, which would be usable by numerous tools.
I appreciate any thoughts, thank you for reading.
Edit: moved from “documentation” to a more fitting “ideas” with “documentation” tag.
Edit 2: grammar