Skip to content

Render each docstring section only where it means something - #308

Merged
rich-iannone merged 7 commits into
posit-dev:mainfrom
has2k1:refactor-docstring-sections
Aug 5, 2026
Merged

Render each docstring section only where it means something#308
rich-iannone merged 7 commits into
posit-dev:mainfrom
has2k1:refactor-docstring-sections

Conversation

@has2k1

@has2k1 has2k1 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

This PR makes every docstring section render only on the objects that can actually have it. Type Parameters sections now work at all — a generic class, function or type alias can document what each parameter means; bounds and constraints both render, and omitting a bound fills it from the signature. Properties can document what they raise, warn, yield and receive, which was silently dropped. Nothing unhandled can leak into a page any more — a section no renderer claims is omitted with a warning naming the object.

Each section is also rendered by its own named method now, so customising one is a single override that changes that section, on that kind of object, and nothing else — previously every handler lived in one registry shared by all renderers, so neither of those could vary independently.

supersedes #303

has2k1 added 7 commits August 4, 2026 13:20
`DocstringSectionTypeParameters` had no `render_docstring_section`
registration, so it fell through to the base handler's `str(el.value)`
and published `<griffe...DocstringTypeParameter object at 0x...>` reprs
under a correct-looking `## Type Parameters` heading.

The section is definition-shaped — name, optional bound, optional PEP 696
default, description — so route it through the definition-list rendering
that `Parameters` already uses. `DocstringTypeParameter` exposes `.name`,
`.annotation` and `.default`, and `.annotation` covers both a bound
(`int`) and constraints (`(str, bytes)`), so no item-level code is needed.

Split the one definition-section handler in two, by what the sections can
describe. `Attributes` (classes and modules) and `Type Parameters`
(classes, functions and type aliases) are not confined to callables, and
`RenderDocModule` and `RenderDocTypeAlias` do not inherit
`RenderDocCallMixin`; they reached the handler only because
`singledispatchmethod` keeps one registry shared by every `RenderDoc`
subclass. Those two now register on `RenderDoc` as
`ObjectDefinitionSection`, while the nine call-only sections stay in
`mixin_call.py` as `CallableDefinitionSection`. Both delegate to a shared
`render_definition_items`, so nothing depends on the shared registry any
more.
…registry

`render_docstring_section` was a `singledispatchmethod`. That descriptor is
created once, when the base class body runs, and holds ONE registry dict that
every subclass inherits, so where a handler was written had no bearing on
which classes could reach it. `RenderDocCallMixin` registered by reaching up
into its parent, and `RenderDocAttribute` — which does not inherit that mixin
— resolved `Parameters` to `__RenderDocCallMixin._`.

Replace it with `SECTION_METHOD`, mapping each section type to the name of the
method that renders it, resolved with `getattr(self, name, None)`. Scoping now
follows the MRO: `render_parameters_section` lives on the call mixin, so a
module renderer has no such attribute and takes the unhandled path. Overriding
a handler is ordinary subclassing — no registry mutation, no import-order
dependence.

This deletes machinery rather than adding it. The `singledispatchmethod`, both
runtime union aliases (`ObjectDefinitionSection`, `CallableDefinitionSection`,
which existed only because `singledispatch` needed types at runtime) and the
`pyright: ignore[reportFunctionMemberAccess]` all go.

Three behaviour changes, all intended:

- A section reaching a renderer with no method for it is omitted and logged,
  rather than rendered through the shared registry.
- Unhandled sections are never stringified. This closes the repr leak that
  published `<griffe...DocstringTypeAlias object at 0x...>` into generated docs.
- Deliberate suppression stays silent. `Methods`, `Functions`, `Classes`,
  `Modules` and `Type Aliases` are valid numpydoc that great-docs drops because
  it generates those groups from real members; warning on them would fire on
  well-formed docstrings. Each has its own method so one can be overridden
  without affecting the others.

Tightening the scoping surfaced a latent bug. `Raises`, `Warns`, `Yields` and
`Receives` had been treated as callable-only, but a property executes code on
access and in griffe has `kind == "attribute"`, so it renders through
`RenderDocAttribute`. Under the shared registry every renderer could reach
every handler, so this was invisible. Those four now have property-gated
renderers there, keyed on `"property" in obj.labels` rather than the display
label, which annotation heuristics can shadow. `Receives` matters specifically
because numpydoc requires it to accompany `Yields`.
… move

Attributes and Type Parameters no longer belong to "any object" — they moved
to the members mixin and the call mixin/type-alias renderer respectively.
Update the SECTION_METHOD grouping comment and two test docstrings that still
described the pre-move scoping.
The four snippets written in `type`-statement and type-parameter syntax are
parsed by griffe with `ast`, so they raise SyntaxError on 3.11 rather than
skipping. Gate them per-test, as test_type_alias_introspection.py does, so the
rest of the file keeps running on the version floor.

@rich-iannone rich-iannone left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@rich-iannone
rich-iannone merged commit 0883236 into posit-dev:main Aug 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants