MyST docstringsΒΆ
Python docstrings in this project use MyST markdown instead of reStructuredText. A Sphinx extension (repomatic.myst_docstrings) transparently converts MyST back to reST at build time, so sphinx.ext.autodoc works without modification. A companion CLI command (repomatic convert-to-myst) handles the one-time source migration.
Why not sphinx-autodoc2?ΒΆ
sphinx-autodoc2 was designed as a full replacement for sphinx.ext.autodoc with native MyST support. It parses docstrings as MyST directly, eliminating the need for a conversion step. In practice the project is abandoned: the last release (v0.5.0) dates from November 2023, its test suite does not pass against current Sphinx or docutils versions, and it has no compatibility with sphinx_autodoc_typehints.
repomatic.myst_docstrings fills the same gap with a lighter approach: it hooks into sphinx.ext.autodocβs existing autodoc-process-docstring event and converts MyST constructs to reST before Sphinx processes them. This preserves full compatibility with sphinx_autodoc_typehints, autodoc_default_options, autoclass_content, and every other extension or setting that builds on sphinx.ext.autodoc. The conversion is regex-based, idempotent, and handles all inline and block constructs that are valid inside docstrings: cross-references, inline code, links, fenced directives, plain code blocks, and footnotes. See Comparison with sphinx-autodoc2 for a detailed feature matrix.
SetupΒΆ
1. Add the extensionΒΆ
In your Sphinx conf.py, add repomatic.myst_docstrings alongside sphinx.ext.autodoc:
extensions = [
"sphinx.ext.autodoc",
"repomatic.myst_docstrings",
"sphinx_autodoc_typehints", # must come after myst_docstrings
# ... other extensions
]
If sphinx.ext.autodoc is absent, the autodoc-process-docstring event never fires and the extension silently does nothing.
Warning
If you also use sphinx_autodoc_typehints, list repomatic.myst_docstrings before it in the extensions list. The extension registers its autodoc-process-docstring hook at priority 400 (vs the default 500 used by sphinx_autodoc_typehints), so MyST-to-reST conversion always runs first regardless of registration order. Listing it first makes the intent explicit and is enforced at load time: if sphinx_autodoc_typehints is already registered when repomatic.myst_docstrings loads, the build fails with a clear error.
The concrete failure mode if the order is wrong: the inline-code converter doubles the backticks inside domain-qualified roles (like :py:obj:`None`) that sphinx_autodoc_typehints injects into the docstring after type-hint resolution. The result is visible β¦ in the rendered HTML around return-type annotations.
2. Add the dependencyΒΆ
repomatic must be importable during the docs build. Add it to your docs dependency group in pyproject.toml:
[dependency-groups]
docs = [
"repomatic",
"sphinx>=8",
# ...
]
3. Migrate existing docstringsΒΆ
Run the converter on your source directory:
$ uv run repomatic convert-to-myst
The command auto-detects the package directory from pyproject.toml entry points. You can also pass an explicit path:
$ uv run repomatic convert-to-myst src/mypackage
The conversion is idempotent: re-running it on already-converted files is a no-op.
Syntax referenceΒΆ
Write docstrings in standard MyST markdown. The extension handles the reST translation at build time.
Cross-referencesΒΆ
Use MyST {role}`target` syntax instead of reST :role:`target` syntax:
MyST (write this) |
reST (produced at build time) |
|---|---|
|
|
|
|
|
|
The ~ prefix for abbreviating to the last component works the same way.
AdmonitionsΒΆ
Use backtick fences:
def detect():
"""Detect the current platform.
```{note}
Falls back to generic detection if the specific
platform check is unavailable.
```
"""
All standard Sphinx admonitions work: note, warning, caution, hint, tip, seealso, danger, important. Colon fences (:::{note} / :::) parse identically in MyST but mdformat treats them as literal text and escapes the colons, so prefer backtick fences.
Admonitions with titles:
"""
```{warning} Experimental API
This function may change in future releases.
```
"""
LinksΒΆ
Use standard markdown links:
MyST (write this) |
reST (produced at build time) |
|---|---|
|
|
|
|
Backticks in link labels are stripped automatically because reST does not support nested markup.
Inline codeΒΆ
Use single backticks. The extension doubles them for reST:
MyST (write this) |
reST (produced at build time) |
|---|---|
|
|
|
|
Code blocksΒΆ
Both plain triple-backtick fences and {code-block} directive fences are supported:
"""
```python
extensions = [
"sphinx.ext.autodoc",
"repomatic.myst_docstrings",
]
```
"""
Plain fences (```python) are converted to .. code-block:: python directives. Directive fences (```{code-block} python) are also converted. The language identifier is optional: a bare ``` fence becomes .. code-block:: with no language.
FootnotesΒΆ
Footnote references and definitions are converted:
MyST (write this) |
reST (produced at build time) |
|---|---|
|
|
|
|
Continuation lines in multi-line footnote definitions pass through with their indentation preserved.
Field listsΒΆ
:param:, :return:, :raises: and other Sphinx field list entries use the same syntax in MyST and reST, so the field list markers themselves need no conversion. The content inside field list entries is converted normally: inline code, cross-references, and links all work:
def read(path, config):
"""Read a file and return its contents.
:param path: Filesystem `path` to process.
:param config: A {class}`~repomatic.config.Config` instance.
:return: `True` if the file was read successfully.
:raises FileNotFoundError: If `path` does not exist.
"""
In the example above, single-backtick code spans (path, True) are doubled for reST, and the {class} cross-reference is converted to :class:. The field list markers (:param path:, :return:, :raises FileNotFoundError:) pass through unchanged.
What the converter doesΒΆ
repomatic convert-to-myst applies these transformations to Python source files, in order:
Cross-references:
:role:`target`becomes{role}`target`Named links:
`text <url>`_becomes[text](url)Inline code:
``code``becomes`code`#:comment blocks: prefix stripped, directives converted, prefix restoredDirectives:
.. directive::+ indented body becomes```{directive}/```
Content containing { inside inline code is left as double backticks to avoid clashing with MyST cross-reference syntax. These pass through the extension unchanged.
LimitationsΒΆ
The extension handles the constructs listed above. It does not convert:
Nested fences of the same type (
/``` ````). A single nesting level works because the inner directive (like.. code-block::) stays as reST inside the converted outer fence.Complex tables (
```{list-table},```{csv-table}). These work in module-level docstrings processed bymyst-parserbut are unlikely to appear in function docstrings.{inside single backticks. Content like`{version}`would be misinterpreted as a cross-reference. The converter intentionally keeps these as double backticks (``{version}``), which the extension passes through to Sphinx as-is.MyST substitution references (
{{variable}}). These are amyst-parserfeature for.mdfiles and are not processed inside docstrings.MyST definition lists. The
deflistextension syntax (term on one line,: definitionon the next) is not converted. The:prefix is ambiguous with field list continuations and other reST constructs, making reliable regex detection impractical without a full parser. Use reST definition lists or restructure as a field list.Heading syntax (
#,##). Markdown headings inside docstrings are not converted to reST sections. Docstrings should not contain headings.Strikethrough (
~~text~~). Not a standard reST construct; no conversion target exists.Task lists (
- [x],- [ ]). No reST equivalent.
For constructs the extension does not handle, use reST syntax directly in the docstring body. The extension is idempotent: reST content passes through unchanged.
Comparison with sphinx-autodoc2ΒΆ
sphinx-autodoc2 took a different architectural approach: it replaced sphinx.ext.autodoc entirely and parsed docstrings as native MyST using myst-parser directly. This eliminated the need for any conversion step. The project is abandoned (last release v0.5.0, November 2023; incompatible with Sphinx 8+, docutils 0.21+, and astroid 4+).
repomatic.myst_docstrings covers the same docstring-authoring use case with a lighter approach: regex-based conversion inside autodoc-process-docstring. The trade-off is a handful of unsupported constructs (listed above) in exchange for full compatibility with the existing sphinx.ext.autodoc ecosystem.
Architectural differences that are inherent to sphinx.ext.autodoc and cannot be addressed by a conversion extension:
Capability |
|
|
|---|---|---|
Static analysis (no module import) |
Yes (via |
No: modules must be importable at build time |
Integrated module discovery |
Yes (no |
No: requires separate |
Incremental per-object rebuilds |
Yes |
No: full rebuild on any change |
|
Yes (static analysis sees all imports) |
No: only sees runtime imports |
Native MyST output files |
Yes (generates |
No: generates reST internally |
These are limitations of sphinx.ext.autodoc itself, not of the conversion extension. They affect how Sphinx discovers and imports modules, not how docstring content is authored or rendered.