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.
repomatic.myst_docstrings APIΒΆ
Convert MyST-flavored docstrings to reST for sphinx.ext.autodoc.
Lightweight replacement for
sphinx-autodoc2,
which provided native MyST docstring parsing but is abandoned (last release
0.5.0, November 2023; incompatible with current Sphinx and docutils).
Hooks into autodoc-process-docstring to transparently convert MyST markdown
syntax in Python docstrings to reStructuredText before Sphinx processes them.
Preserves full compatibility with sphinx_autodoc_typehints,
autodoc_default_options, and every other extension that builds on
sphinx.ext.autodoc. See MyST docstrings for setup, usage, and
limitations.
The conversion is idempotent: docstrings already in reST pass through unchanged. This allows incremental migration one module at a time.
Supported conversions:
Inline code (single backtick) is converted to reST double backticks. Field list markers (:param:, :return:) need no conversion; the content inside field list entries is converted normally (inline code, cross-references, links).
``{note}
Register this extension in your Sphinx ``conf.py, before
sphinx_autodoc_typehints if present:
extensions = [
"sphinx.ext.autodoc",
"repomatic.myst_docstrings",
"sphinx_autodoc_typehints", # must come after
]
This requires repomatic in your docs dependency group.ΒΆ
repomatic.myst_converter APIΒΆ
Convert reST docstrings to MyST in Python source files.
Transforms reST markup in docstrings and #: comment blocks to MyST
markdown. The companion Sphinx extension repomatic.myst_docstrings
converts the MyST back to reST at build time, so sphinx.ext.autodoc
still works.
Conversions applied (in order):
Cross-references:
{role}`target`->{role}`target`Named links:
`text <url>``_->text <url>`_Inline code:
``code``->`code`#:comment blocks: strip prefix, convert directives, re-wrap.Directives:
.. directive::+ indented body -> `````{directive} ``/ ````` ``
Safe to re-run: already-converted MyST syntax does not match the reST patterns, so the script is idempotent.
Note
f-string exclusion: Cross-reference and inline-code regexes exclude
targets containing { so that f-string interpolations (like
f":func:`~{self.id}`") are untouched.
Note
Nested directives stay as reST: A .. code-block:: inside a
converted backtick-fenced warning directive is emitted as-is. The
hook handles this correctly because it re-indents the body when
converting back to reST.
Note
Link labels lose backticks: `sys.platform <url>`_ is valid MyST
but reST has no nested markup. The hook strips backticks from labels
before emitting the reST link.
- repomatic.myst_converter.convert_xrefs(text)[source]ΒΆ
Convert reST cross-references to MyST syntax.
- Return type:
- repomatic.myst_converter.convert_links(text)[source]ΒΆ
Convert reST named hyperlinks to markdown links.
- Return type:
- repomatic.myst_converter.convert_inline_code(text)[source]ΒΆ
Convert reST double-backtick literals to single-backtick.
- Return type:
- repomatic.myst_converter.convert_directives(text)[source]ΒΆ
Convert reST directives to MyST backtick fences in a single pass.
Body lines are collected by indentation (deeper than the
..line) and dedented to the fence level. Trailing blank lines between consecutive directives are preserved as a single separator.Nested directives (like
.. code-block::inside.. warning::) are emitted as-is in the fence body. The hook re-indents them during the reST round-trip.- Return type:
- repomatic.myst_converter.convert_comment_blocks(text)[source]ΒΆ
Convert
#:Sphinx comment docstrings.Consecutive
#:lines are collected, the prefix is stripped, all conversions are applied to the extracted content, and the prefix is re-added.- Return type:
- repomatic.myst_converter.convert_file(filepath)[source]ΒΆ
Apply all conversions to a single Python file.
Returns
Trueif the file was modified.Ordering matters:
Cross-references and links are simple global regexes.
Inline code runs before directives so that
codeinside directive bodies is converted when the body is dedented.#:comment blocks are processed before directives so that their inner directives are converted in isolation.Directives run last on the full text.
- Return type:
repomatic.rst_to_myst APIΒΆ
Convert sphinx-apidoc RST output to MyST markdown.
Note
The converter handles only the narrow RST subset that sphinx-apidoc
generates: section headings (title + underline), automodule directives
with indented options, and structural headers like Submodules.
Autodoc directives cannot be used as native MyST directives because they
perform internal rST nested parsing that requires an rST parser context
only {eval-rst} provides. See MyST-Parser #587.
- repomatic.rst_to_myst.convert_apidoc_rst_to_myst(content)[source]ΒΆ
Convert
sphinx-apidocRST to MyST markdown with{eval-rst}blocks.
- repomatic.rst_to_myst.convert_rst_files_in_directory(directory)[source]ΒΆ
Convert
sphinx-apidocRST files to MyST markdown in the given directory.For each
.rstfile containing.. automodule::directives:If a
.mdfile with the same stem exists, delete the.rst(the existing markdown takes precedence).Otherwise, convert the RST content to MyST and write a
.mdfile, then delete the.rst.