[Bugfix] Doc generator/stub generator miss module-level dynamic attributes (current_scene, scenes) #356

Closed
opened 2026-07-12 01:01:56 +00:00 by john · 0 comments
Owner

Summary

mcrfpy.current_scene and mcrfpy.scenes are real, working module attributes — confirmed live:

$ ./mcrogueface --headless -c "import mcrfpy; print('current_scene' in dir(mcrfpy)); print(hasattr(mcrfpy,'current_scene')); s=mcrfpy.Scene('x'); mcrfpy.current_scene=s; print(mcrfpy.current_scene)"
False
True
<Scene 'x'>

'current_scene' in dir(mcrfpy) is False but hasattr()/actual access works. They are completely absent from both docs/API_REFERENCE_DYNAMIC.md and stubs/mcrfpy.pyi — a zero-hit grep for current_scene in either generated artifact, despite it being the primary idiom used throughout CLAUDE.md, the tutorial series, and virtually every example script (mcrfpy.current_scene = mcrfpy.Scene("gameplay")).

default_font/default_texture (real static module attributes) ARE documented correctly — this is specific to the dynamic pair.

Root cause

src/McRFPy_API.cpp implements a custom module tp_getattro/tp_setattro for current_scene/scenes (issue #151, see McRFPy_API.cpp:129-190, api_get_current_scene/api_set_current_scene in PySceneObject.cpp:680-723). Because these are intercepted via custom attribute hooks rather than real module PyObject members, they never appear in dir(mcrfpy) — and both doc-generation tools (tools/generate_dynamic_docs.py for the Markdown/HTML/man reference, tools/generate_stubs_v2.py for stubs/mcrfpy.pyi) discover module-level symbols via dir() + getattr() introspection, so both silently skip them.

There is already a docstring for the concept at McRFPy_API.cpp:355 (MCRF_DESC("Module-level attribute access for dynamic properties (current_scene, scenes).")), but it's attached to the module-type slot, not to two discoverable per-attribute entries, so it doesn't get individually surfaced either.

Impact

Discovered while auditing/merging generated-vs-hand-written docs on the site (mcrogueface.github.io) — reference.md's "Module Attributes" section had to be hand-authored and hand-verified against a live binary because the generator produced nothing to draw from. This will re-drift the moment the generator is next run, since nothing about the gap is fixed at the source.

Suggested direction

Either:

  1. Hardcode a small manifest of known tp_getattro-intercepted module attributes (name + docstring + type) that both generate_dynamic_docs.py and generate_stubs_v2.py consult in addition to dir()-based introspection, sourced from the existing MCRF_DESC at McRFPy_API.cpp:355 split into two real per-attribute docstrings; or
  2. Register current_scene/scenes as real descriptor-backed module attributes (e.g. via a PyGetSetDef-style property on the module type) instead of tp_getattro string-matching, so they become naturally dir()-discoverable and the existing introspection pipeline picks them up for free.

(2) is probably the more durable fix and would also make hasattr/dir consistent for any future dynamic module attribute, but touches #151's existing mechanism — (1) is a smaller, generator-only patch if a C++ change isn't wanted right now.

Also worth double-checking: tools/generate_api_manifest.py (new, api/manifest.json) uses the same dir()-based introspection and will have the identical blind spot — whichever fix lands here should cover it too.

## Summary `mcrfpy.current_scene` and `mcrfpy.scenes` are real, working module attributes — confirmed live: ``` $ ./mcrogueface --headless -c "import mcrfpy; print('current_scene' in dir(mcrfpy)); print(hasattr(mcrfpy,'current_scene')); s=mcrfpy.Scene('x'); mcrfpy.current_scene=s; print(mcrfpy.current_scene)" False True <Scene 'x'> ``` `'current_scene' in dir(mcrfpy)` is `False` but `hasattr()`/actual access works. They are completely absent from both `docs/API_REFERENCE_DYNAMIC.md` and `stubs/mcrfpy.pyi` — a zero-hit grep for `current_scene` in either generated artifact, despite it being the primary idiom used throughout CLAUDE.md, the tutorial series, and virtually every example script (`mcrfpy.current_scene = mcrfpy.Scene("gameplay")`). `default_font`/`default_texture` (real static module attributes) ARE documented correctly — this is specific to the dynamic pair. ## Root cause `src/McRFPy_API.cpp` implements a custom module `tp_getattro`/`tp_setattro` for `current_scene`/`scenes` (issue #151, see `McRFPy_API.cpp:129-190`, `api_get_current_scene`/`api_set_current_scene` in `PySceneObject.cpp:680-723`). Because these are intercepted via custom attribute hooks rather than real module `PyObject` members, they never appear in `dir(mcrfpy)` — and both doc-generation tools (`tools/generate_dynamic_docs.py` for the Markdown/HTML/man reference, `tools/generate_stubs_v2.py` for `stubs/mcrfpy.pyi`) discover module-level symbols via `dir()` + `getattr()` introspection, so both silently skip them. There *is* already a docstring for the concept at `McRFPy_API.cpp:355` (`MCRF_DESC("Module-level attribute access for dynamic properties (current_scene, scenes).")`), but it's attached to the module-type slot, not to two discoverable per-attribute entries, so it doesn't get individually surfaced either. ## Impact Discovered while auditing/merging generated-vs-hand-written docs on the site (mcrogueface.github.io) — `reference.md`'s "Module Attributes" section had to be hand-authored and hand-verified against a live binary because the generator produced nothing to draw from. This will re-drift the moment the generator is next run, since nothing about the gap is fixed at the source. ## Suggested direction Either: 1. Hardcode a small manifest of known `tp_getattro`-intercepted module attributes (name + docstring + type) that both `generate_dynamic_docs.py` and `generate_stubs_v2.py` consult in addition to `dir()`-based introspection, sourced from the existing `MCRF_DESC` at `McRFPy_API.cpp:355` split into two real per-attribute docstrings; or 2. Register `current_scene`/`scenes` as real descriptor-backed module attributes (e.g. via a `PyGetSetDef`-style property on the module type) instead of `tp_getattro` string-matching, so they become naturally `dir()`-discoverable and the existing introspection pipeline picks them up for free. (2) is probably the more durable fix and would also make `hasattr`/`dir` consistent for any future dynamic module attribute, but touches `#151`'s existing mechanism — (1) is a smaller, generator-only patch if a C++ change isn't wanted right now. Also worth double-checking: `tools/generate_api_manifest.py` (new, `api/manifest.json`) uses the same `dir()`-based introspection and will have the identical blind spot — whichever fix lands here should cover it too.
john closed this issue 2026-07-14 12:01:03 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
john/McRogueFace#356
No description provided.