mirror of
https://github.com/pybricks/pybricks-api.git
synced 2026-09-11 09:05:07 +00:00
doc: Replace fork with extensions.
Use clean upstream Sphinx and use extensions for the awaitable types and work around a Sphinx docstring parsing bug.
This commit is contained in:
+5
-2
@@ -57,9 +57,11 @@ extensions = [
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.todo",
|
||||
# Custom Pybricks extensions
|
||||
"awaitable",
|
||||
"blockimg",
|
||||
"color",
|
||||
"classlink",
|
||||
"docstring_signature",
|
||||
"requirements",
|
||||
"requirements-static",
|
||||
"versionchanged",
|
||||
@@ -133,8 +135,9 @@ nitpick_ignore = [
|
||||
# not sure why, but this is needed for typing.IO in uselect
|
||||
nitpick_ignore.append(("py:obj", "typing.IO"))
|
||||
|
||||
# MaybeAwaitable* stub types have no documented target; rendering hacks were
|
||||
# dropped with the Sphinx upgrade (may be revisited later).
|
||||
# MaybeAwaitable* stub types have no documented target; the awaitable
|
||||
# extension renders them as an "await" prefix instead, but the raw names can
|
||||
# still leak into signatures (e.g. overloads), so suppress those warnings.
|
||||
nitpick_ignore_regex = [
|
||||
("py:class", r"MaybeAwaitable\w*"),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Show ``await`` in front of multitasking functions and methods.
|
||||
|
||||
The Pybricks API returns awaitable objects when a run loop is active but
|
||||
blocks otherwise. Such functions are annotated with ``MaybeAwaitable*``
|
||||
return types. This extension:
|
||||
|
||||
* makes autodoc treat functions/methods returning ``MaybeAwaitable*`` as
|
||||
async (there is no public hook for this, so ``is_async`` is patched);
|
||||
* renders the signature prefix as ``await`` (linked to the multitasking
|
||||
section in tools) instead of ``async``, which better matches how users
|
||||
call these functions.
|
||||
"""
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.addnodes import desc_sig_keyword
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.domains.python import PyFunction, PyMethod, type_to_xref
|
||||
from sphinx.ext.autodoc._property_types import _FunctionDefProperties
|
||||
|
||||
|
||||
def _is_async(self: _FunctionDefProperties) -> bool:
|
||||
if "async" in self.properties:
|
||||
return True
|
||||
|
||||
try:
|
||||
return_type = self._obj.__annotations__["return"]
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return "MaybeAwaitable" in str(return_type)
|
||||
|
||||
|
||||
class _AwaitPrefixMixin:
|
||||
"""Replaces the ``async`` keyword prefix with a linked ``await``."""
|
||||
|
||||
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
|
||||
prefix = []
|
||||
for node in super().get_signature_prefix(sig):
|
||||
if isinstance(node, desc_sig_keyword) and node.astext() == "async":
|
||||
node = type_to_xref("await", self.env, suppress_prefix=True)
|
||||
prefix.append(node)
|
||||
return prefix
|
||||
|
||||
|
||||
class PybricksPyFunction(_AwaitPrefixMixin, PyFunction):
|
||||
pass
|
||||
|
||||
|
||||
class PybricksPyMethod(_AwaitPrefixMixin, PyMethod):
|
||||
pass
|
||||
|
||||
|
||||
def setup(app: Sphinx):
|
||||
_FunctionDefProperties.is_async = property(_is_async)
|
||||
|
||||
app.add_directive_to_domain("py", "function", PybricksPyFunction, override=True)
|
||||
app.add_directive_to_domain("py", "method", PybricksPyMethod, override=True)
|
||||
|
||||
return {"parallel_read_safe": True}
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Make explicit docstring signatures win over ``@overload`` signatures.
|
||||
|
||||
Workaround for https://github.com/sphinx-doc/sphinx/issues/10436: when a
|
||||
function or method has overloads, autodoc unconditionally replaces the
|
||||
signature(s) found in the docstring with the overload signatures. Classes
|
||||
already behave correctly, so this only patches the function/method path. There
|
||||
is no event hook for this in autodoc's new (Sphinx 9) pipeline, so
|
||||
``_format_signatures`` is wrapped: when a docstring signature is present,
|
||||
``autodoc_typehints`` is set to ``'none'`` for that single call, which
|
||||
disables only the overload substitution branch.
|
||||
"""
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.ext.autodoc._dynamic import _loader, _signatures
|
||||
from sphinx.ext.autodoc._shared import _AutodocConfig
|
||||
|
||||
_orig_format_signatures = _signatures._format_signatures
|
||||
|
||||
|
||||
def _format_signatures(**kwargs):
|
||||
config: _AutodocConfig = kwargs["config"]
|
||||
docstrings = kwargs.get("docstrings")
|
||||
options = kwargs["options"]
|
||||
props = kwargs["props"]
|
||||
|
||||
if (
|
||||
kwargs.get("args") is None
|
||||
and docstrings
|
||||
and config.autodoc_docstring_signature
|
||||
and config.autodoc_typehints != "none"
|
||||
and props.obj_type in {"function", "method", "decorator"}
|
||||
):
|
||||
# Probe on a copy: extraction strips signature lines from docstrings.
|
||||
docstring_signatures = _signatures._extract_signatures_from_docstrings(
|
||||
[list(lines) for lines in docstrings],
|
||||
props=props,
|
||||
tab_width=options._tab_width,
|
||||
)
|
||||
if docstring_signatures:
|
||||
values = {name: getattr(config, name) for name in _AutodocConfig.__slots__}
|
||||
values["autodoc_typehints"] = "none"
|
||||
kwargs["config"] = _AutodocConfig(**values)
|
||||
|
||||
return _orig_format_signatures(**kwargs)
|
||||
|
||||
|
||||
def setup(app: Sphinx):
|
||||
_signatures._format_signatures = _format_signatures
|
||||
_loader._format_signatures = _format_signatures
|
||||
|
||||
return {"parallel_read_safe": True}
|
||||
Reference in New Issue
Block a user