Skip to content

Commit 0235780

Browse files
committed
docs(refactor[package-reference]): drop docutils role monkey-patch
why: The `collect_extension_surface()` and `_seed_python_objects_map()` helpers each monkey-patched `docutils.parsers.rst.roles.register_local_role` / `register_canonical_role` so that `sphinx-autodoc-argparse`'s `register_roles()` — which registered its five `cli-*` roles via the docutils global — would be discoverable by the RecorderApp. Now that argparse registers via `Sphinx.add_role`, the RecorderApp captures those calls through its own `__getattr__`, and the monkey-patch has nothing left to catch. Removing it drops three `t.cast("t.Any", …)` casts, two try/finally blocks that mutated process-global docutils state, and an entire `"docutils role"` post-processing branch. what: - Delete the `registered_roles` / `_record_local` / role-patch try-finally in `collect_extension_surface()` (lines 317–336) and the trailing "docutils role" emission loop (lines 443–451) - Delete the `docutils_roles` / `_capture` / role-patch try-finally in `_seed_python_objects_map()` and the trailing `docutils_roles`-based `raw_objs` append (lines 781–823) - Drop the `from docutils.parsers.rst import roles` import (no other references remain in this file) - Update the module-level architecture docstring to describe the `RecorderApp.__getattr__` approach instead of the removed monkey-patch - Verified via `just build-docs`: the argparse package reference page still lists all five `cli-*` roles (27 occurrences in the rendered HTML, captured through the "add_role" branch)
1 parent 1cbc355 commit 0235780

1 file changed

Lines changed: 6 additions & 55 deletions

File tree

docs/_ext/package_reference.py

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
its name, version, description, classifiers, and GitHub URL.
1212
1313
2. **Surface extraction** (``collect_extension_surface()``) — imports the
14-
module and monkey-patches ``app.add_*`` methods on a lightweight mock
15-
``Sphinx`` object to intercept calls that ``setup()`` makes. Each
16-
registered item (config value, directive, role, lexer, theme) is captured
17-
into a ``SurfaceDict``.
14+
module and passes a lightweight ``RecorderApp`` to its ``setup()``.
15+
``RecorderApp.__getattr__`` captures every ``app.add_*`` call so each
16+
registered item (config value, directive, role, lexer, theme) flows
17+
into a ``SurfaceDict`` without monkey-patching docutils globals.
1818
1919
3. **Rendering** (``package_reference_markdown()``) — converts the collected
2020
surface into a Markdown fragment (config snippet + tables), which the
@@ -64,7 +64,6 @@
6464
import sys
6565
import typing as t
6666

67-
from docutils.parsers.rst import roles
6867
from sphinx.util.docutils import SphinxDirective
6968

7069
if t.TYPE_CHECKING:
@@ -314,26 +313,8 @@ def collect_extension_surface(module_name: str) -> SurfaceDict:
314313
themes=[],
315314
)
316315
app = RecorderApp()
317-
registered_roles: list[tuple[str, object]] = []
318-
original_local = roles.register_local_role
319-
original_canonical = roles.register_canonical_role
320-
321-
def _record_local(name: str, role: object) -> None:
322-
registered_roles.append((name, role))
323-
324-
# Temporarily replace the two docutils global role-registration functions so
325-
# that any role registered by setup(app) is captured in registered_roles.
326-
# The try/finally guarantees restoration even if setup() raises.
327-
# Limitation: this mutates process-global state and is not safe for
328-
# parallel Sphinx builds (sphinx -j N); single-threaded builds only.
329-
try:
330-
roles.register_local_role = t.cast("t.Any", _record_local)
331-
roles.register_canonical_role = t.cast("t.Any", _record_local)
332-
setup = t.cast("t.Callable[[object], object]", getattr(module, "setup"))
333-
setup(app)
334-
finally:
335-
roles.register_local_role = original_local
336-
roles.register_canonical_role = original_canonical
316+
setup = t.cast("t.Callable[[object], object]", getattr(module, "setup"))
317+
setup(app)
337318

338319
config_values: list[dict[str, str]] = []
339320
directives: list[dict[str, str]] = []
@@ -440,16 +421,6 @@ def _record_local(name: str, role: object) -> None:
440421
},
441422
)
442423

443-
for role_name, role_fn in registered_roles:
444-
role_items.append(
445-
{
446-
"name": role_name,
447-
"kind": "docutils role",
448-
"callable": object_path(role_fn),
449-
"summary": summarize(getattr(role_fn, "__doc__", None)),
450-
},
451-
)
452-
453424
return {
454425
"module": module_name,
455426
"config_values": unique_by_name(config_values),
@@ -806,26 +777,10 @@ def _register_extension_objects(
806777
continue
807778

808779
recorder = RecorderApp()
809-
docutils_roles: list[tuple[str, object]] = []
810-
original_local = roles.register_local_role
811-
original_canonical = roles.register_canonical_role
812-
813-
def _capture(
814-
role_name: str,
815-
role_fn: object,
816-
_roles: list[tuple[str, object]] = docutils_roles,
817-
) -> None:
818-
_roles.append((role_name, role_fn))
819-
820780
try:
821-
roles.register_local_role = t.cast("t.Any", _capture)
822-
roles.register_canonical_role = t.cast("t.Any", _capture)
823781
setup_fn(recorder)
824782
except Exception:
825783
continue
826-
finally:
827-
roles.register_local_role = original_local
828-
roles.register_canonical_role = original_canonical
829784

830785
raw_objs: list[tuple[object, str]] = [] # (obj, objtype)
831786
for call_name, args, _kwargs in recorder.calls:
@@ -845,10 +800,6 @@ def _capture(
845800
)
846801
elif call_name == "add_lexer" and len(args) >= 2:
847802
raw_objs.append((args[1], "class"))
848-
for _role_name, role_fn in docutils_roles:
849-
raw_objs.append(
850-
(role_fn, "function" if not inspect.isclass(role_fn) else "class"),
851-
)
852803

853804
for obj, objtype in raw_objs:
854805
mod = getattr(obj, "__module__", None) or type(obj).__module__

0 commit comments

Comments
 (0)