Open
Description
Describe the feature you'd like to request
Goals
- Create a custom role which extends
docutils.parsers.rst.roles.code_role
. - Pass static type checking (
mypy
) - Pass runtime type checking (
beartype
)
Below is a minimized example of my work towards supporting MyST for substitution-code
within https://github.com/adamtheturtle/sphinx-substitution-extensions.
Requirements
beartype==0.19.0
myst-parser==4.0.0
types-docutils==0.21.0.20241128
Files
Layout as described in
https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-extensions.
<project directory>/
├── conf.py
└── sphinxext/
└── extname.py
# conf.py
import sys
from pathlib import Path
# Path configured as per
# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-extensions
sys.path.append(str(Path('sphinxext').resolve()))
extensions = [
"myst_parser",
"extname",
]
# sphinxext/extname.py
from typing import Any
from beartype import beartype
from docutils.nodes import Node, system_message
from docutils.parsers.rst.roles import code_role
from docutils.parsers.rst.states import Inliner
# Typed version of the example from https://docutils.sourceforge.io/docs/howto/rst-roles.html#define-the-role-function
@beartype
def role_fn(
name: str,
rawtext: str,
text: str,
lineno: int,
# This is an `Inliner`, so it can be passed through directly to `code_role`.
inliner: Inliner,
options: dict[Any, Any] = {},
content: list[str] = [],
) -> tuple[list[Node], list[system_message]]:
print("Custom logic")
# Type stubs for `code_role` are at
# https://github.com/python/typeshed/blob/57d7c4334b64856fda6f6e8f992b101ddafe2f57/stubs/docutils/docutils/parsers/rst/roles.pyi#L103
#
# Importantly, they require that `inliner` is an `Inliner`.
return code_role(
role=name,
rawtext=rawtext,
text=text,
lineno=lineno,
inliner=inliner,
options=options,
content=content,
)
@beartype
def setup(app):
app.add_role(name="my-role", role=role_fn)
Command
sphinx-build -M html . build/
Error
beartype.roar.BeartypeCallHintParamViolation: Function extname.role_fn() parameter inliner=<myst_parser.mocking.MockInliner object at 0x10826e120> violates type hint <class 'docutils.parsers.rst.states.Inliner'>, as <class "myst_parser.mocking.MockInliner"> <myst_parser.mocking.MockInliner object at 0x10826e120> not instance of <class "docutils.parsers.rst.states.Inliner">.
Describe the solution you'd like
Modify MockInliner
as per https://beartype.readthedocs.io/en/latest/faq/#mock-types to define a new __class__
property which returns Inliner
.
Describe alternatives you've considered
My workaround is to have role_fn
hint inliner
as inliner: Inliner | MockInliner
, and then to create a new Inliner
object as needed for code_role
:
if isinstance(inliner, MockInliner):
new_inliner = Inliner()
new_inliner.document = inliner_document
inliner = new_inliner
Another alternative is to contribute to types-docutils
to have code_role
take a Protocol
of just what is needed from Inliner
, so it supports MockInliner
, too.