Sphinx: Filtering versionadded/versionchanged Noise on Inherited Members
With inherited members enabled, Sphinx autodoc copies them onto a subclass’s page –
including any .. versionadded:: / .. versionchanged:: notes in the base-class
docstrings. The problem here is that this version information is about the superclass,
but our user-facing docs are about the subclass, and that subclass may be newer than the superclass.
Imagine if the whole class that’s being documented was introduced in version 3.11, but we have “Changed in version 3.9” on some inherited members – that’s confusing, isn’t it? You’d wonder whether the class really was new in version 3.11 or whether you could trust the docs at all. On the pages of older sibling classes, the same notes are useful; that’s why we annotated them in the first place.
My fix is a per-page threshold. Each affected page declares the version it
first appeared in, as a docinfo field
at the very top of the .rst file:
:min-version: 3.11
Page Title
==========
.. autoclass:: mypackage.BananaCounter
:members:
:inherited-members:
The :inherited-members: flag is what pulls the base-class members – and their
version notes – onto this page in the first place; without it there’d be no noise
to filter. A doctree-read hook in conf.py then drops version notes at or below
that threshold, but only inside autodoc member descriptions:
def filter_version_notes(app, doctree):
from packaging.version import parse as parse_version
from sphinx import addnodes
threshold = app.env.metadata.get(app.env.docname, {}).get("min-version")
if not threshold:
return
threshold = parse_version(threshold)
for node in list(doctree.findall(addnodes.versionmodified)):
if node["type"] == "deprecated" or parse_version(node["version"]) > threshold:
continue
ancestor = node.parent
while ancestor is not None and not isinstance(ancestor, addnodes.desc):
ancestor = ancestor.parent
if ancestor is not None: # inside an autodoc member; not a page-level note
node.parent.remove(node)
def setup(app):
app.connect("doctree-read", filter_version_notes)
Notes on the details:
versionadded,versionchanged, anddeprecatedall produce the sameversionmodifiednode type.- Deprecations are explicitly kept, whatever their version.
- The
addnodes.descancestor walk is what protects handwritten page-level notes: the page’s own “Added in version 3.11” survives because it isn’t inside a member description. packaging.version.parseis used for comparison, so3.9correctly sorts below the3.11threshold. A plain string comparison would rank"3.9"above"3.11"(because"9">"1") and wrongly keep the note.- Pages without a
:min-version:field are untouched.
