Writing sphinx-friendly docstrings¶
The HTML docs are built from docstrings by sphinx autoclass/autodoc, with the
sphinx.ext.napoleon extension enabled (see docs/source/conf.py). Docstrings are
rendered as reStructuredText, and napoleon additionally parses NumPy/Google-style
sections. A few conventions keep the rendered output clean. (See also
notes/pybind11.md for the pybind-specific version of these rules.)
Class docstrings for classes with method injections¶
A pybind11-wrapped class that is extended via ksgpu.inject_methods (see
notes/pybind11.md) can carry its class docstring on either side. inject_methods
copies the injector class’s docstring onto the C++ class when the injector defines
one – and that overrides the docstring set in the pybind11 binding. If the injector
has no docstring, the pybind11 docstring stands.
To avoid confusion and accidental clobbering, put each such class’s docstring on exactly ONE side, and leave a short comment on the other side pointing to it:
Option 1 – docstring in the pybind11 binding. Write the class docstring in the C++
py::class_<...>(m, "Name", "docstring..."). In the injector class, write no docstring; instead put a#comment noting the docstring lives in pybind11 (so the next reader knows not to add one here, since it would override).Option 2 – docstring in the injector class. Write the class docstring as the Python injector’s docstring, and set no docstring in the C++
py::class_<...>(m, "Name"); put a C++ comment there noting the docstring lives in the injector.
Choosing between them – put the docstring where the class is most naturally described:
Use option 1 for classes that are essentially C++ objects with a thin Python shim (a constructor/
launch/setter that just converts dtypes, streams, or aflags), especially when a good C++ docstring already exists. Examples:BumpAllocator,SlabAllocator,DedispersionConfig,GpuDequantizationKernel,CasmBeamformer.Use option 2 when the class’s primary Python interface is the injection – a context-manager usage pattern, or accessors that return Python wrapper objects – so the narrative (and the read-only-attribute bullet list, per the next section) belongs next to that Python code. Examples:
FrbGrouper(thewith ... as g:/g.get_output(...)API),GpuDedisperser(get_input/get_outputcontext managers),CudaStreamPool(accessors returningksgpu.CudaStreamWrapper).
Either way the docstring is plain rST/napoleon, so all the conventions below apply regardless of which side it lives on.
Simple properties / attributes: document them in the class docstring¶
Prefer documenting “simple” members – typical read-only pybind11 properties
(def_readonly) and plain data attributes – with a one-line bullet in the class
docstring, and leave the binding itself bare (no per-property docstring). For example,
in C++:
py::class_<Foo>(m, "Foo",
"One-line summary of Foo.\n"
"\n"
"Attributes (read-only):\n"
"\n"
"- ``nbatches`` (int) -- beam-batches per time chunk.\n"
"- ``grouper_ip_addr`` (str) -- the grouper's listen address (``ip:port``).\n")
.def_readonly("nbatches", &Foo::nbatches)
.def_readonly("grouper_ip_addr", &Foo::grouper_ip_addr);
Why this, rather than a per-property docstring:
It renders compactly (one line per member), instead of the multi-line name / type / description block that a per-property docstring produces.
The members are not registered as separate sphinx objects, so they stay out of the right-hand “on this page” sidebar (a long auto-generated property list there is mostly noise).
It sidesteps a napoleon gotcha: napoleon reads a property docstring as “type: description” and splits it at the first colon, which mangles any docstring containing a colon (see “the colon gotcha” below).
Conventions for the bullets:
Use
--(space-dash-dash-space) between the name and the description, not a colon. (--renders as an en-dash; a colon is easier to misparse.)Wrap the member name – and any token containing a colon,
*, or_– in double backticks, so it renders as monospace and is inert in rST.Do not start the lead-in line with a bare section keyword followed by a colon (
Attributes:,Parameters:, …): napoleon would treat it as a section header and re-parse the bullets.Attributes (read-only):is safe (the(defeats the match).
This applies to attributes set from Python too – e.g. in __init__, or attached by a
context manager’s __enter__ – not just pybind11 properties. Document the key public
ones in the same bullet style; omit attributes that exist only for the class’s internal
use (conventionally underscore-prefixed).
Methods are different: leave them as normal :members:-documented methods, each with
its own docstring – they belong in the sidebar.
The colon gotcha. If you do write a per-member docstring anyway (e.g. a pybind11
def_readonly("x", &C::x, "..."), so help(obj.x) shows text), keep a bare colon out
of its first line. napoleon reads a member/attribute docstring as type: description
and splits it at the first colon, so e.g. "listen address ('ip:port')" renders as a
mangled “Type” field in the autoclass output. Wrap the colon-bearing (or */_-
bearing) token in a double-backtick inline literal, which prevents the split and renders
it as monospace:
Bad: "The grouper's listen address ('ip:port'), set at construction."
Good: "The grouper's listen address (``ip:port``), set at construction."
(This only affects member/attribute docstrings, not method docstrings.)
Method / function docstrings¶
For methods with parameters or return values, use NumPy-style sections:
Summary line.
Extended description (free prose) goes here, ABOVE the sections.
Parameters
----------
x : int
Description of x.
Returns
-------
SomeType
Description.
Put all free prose in the summary / extended description, above the first section. Do NOT put prose paragraphs between sections (e.g. between Parameters and Returns): napoleon parses the base-indented lines after a parameter list as more parameters, shredding the prose.
Recognized section names include Parameters, Returns, Yields, Raises, Notes; each is underlined with dashes (NumPy style).
General rST hygiene¶
Keep bullet lists (and paragraphs) flush with the surrounding text. Indenting a block more than its surroundings makes rST render it as a block quote – a grey, extra-indented box. Autodoc strips the docstring’s common leading indent first, so what matters is indentation relative to the other body lines, not the raw column.
Inline literal (monospace): wrap the text in double backticks.
A bare
*or_can be read as rST emphasis/target markup; wrap such tokens in double backticks if they are meant literally.A literal block is introduced by
::at the end of a line, followed by an indented block (used e.g. for theUsage::examples in some class docstrings).