[DRAFT] DOC Add API example backlinks - #2282
Conversation
|
@microsoft-github-policy-service agree |
| return paths | ||
|
|
||
|
|
||
| def _build_example_index( |
There was a problem hiding this comment.
This is a lot of new responsibility for gen_api_md.py. The module docstring says its job is "Reads the JSON files produced by pydoc2json.py and generates clean MyST markdown pages" — this PR adds a second, unrelated subsystem: TOC YAML parsing, .ipynb JSON parsing, Jupytext percent-format splitting, Markdown fence extraction, frontmatter/H1 title extraction, and Python import/AST resolution. That's ~250 lines with their own failure modes, and it roughly doubles the file's non-rendering logic.
Suggest extracting everything from _unique_public_symbol through _build_example_index into build_scripts/example_index.py and importing it, the same way validate_docs is already imported as a sibling. gen_api_md.py then keeps one job: JSON → markdown. Fine as a follow-up if you'd rather not grow this PR.
| parts = ["**Examples:**\n"] | ||
| for example in examples: | ||
| title = example.title.replace("[", r"\[").replace("]", r"\]") | ||
| parts.append(f"- [{title}](../{example.path})") |
There was a problem hiding this comment.
The ../ prefix hardcodes the assumption that API pages sit exactly one directory below the doc root. True today (API_MD_DIR = Path("doc/api")), but the coupling is invisible from here and would break silently — MyST only surfaces unresolved links under --strict, which the docs workflow deliberately doesn't use. Worth deriving the prefix from API_MD_DIR relative to the doc root instead.
| example_index = _build_example_index( | ||
| doc_root=Path("doc"), | ||
| toc_path=Path("doc/myst.yml"), | ||
| symbol_index=symbol_index, | ||
| ) |
There was a problem hiding this comment.
Three independent silent-skip paths stack up behind this call: SyntaxError → skip chunk, JSONDecodeError/OSError → skip notebook, not path.is_file() → skip TOC entry. If notebook generation order changes in CI, or a TOC refactor moves pages under a newly-excluded prefix, the index quietly empties and the build still passes green.
main() already prints per-module counts below; a matching one-liner here — e.g. Indexed examples: {len(example_index)} symbols across {n} pages — would make a regression visible in CI logs. Cheap insurance.
| module=module, | ||
| class_name=name, | ||
| symbol_index=symbol_index, | ||
| examples_by_anchor=examples_by_anchor, |
There was a problem hiding this comment.
This parameter is dead on the method path. _unique_public_symbol filters to kind in ("class", "function"), so _build_example_index can only key on class and function anchors — method anchors from _method_anchor are structurally distinct and will never match. I confirmed empirically: 0 method-level **Examples:** blocks across all generated pages.
Either drop it here so the contract is honest, or make it real. Method-level backlinks aren't derivable from the current design anyway (attack.execute_async(...) roots on a local variable, not an import binding), so removing the plumbing seems like the cleaner call.
| short_entries = symbol_index.get(qualified_name.rsplit(".", 1)[-1], []) | ||
| module_matches = [entry for entry in short_entries if qualified_name.startswith(f"{entry.module}.")] | ||
| return _unique_public_symbol(module_matches) or _unique_public_symbol(short_entries) |
There was a problem hiding this comment.
Not a bug — I probed all 669 generated (symbol, page) pairs and found zero where the symbol name is absent from the page source, so no false positives on the current corpus. But this fallback and the segment[:1].isupper() branch in _resolve_attribute_symbol both discard the declared module and rely on API-wide name uniqueness. That leniency is exactly what makes re-export imports (from pyrit.prompt_target.openai.openai_chat_target import ...) resolve, so it should stay — it just reads like an oversight. A one-line comment stating the invariant (same assumption _resolve_symbol already makes) would save the next reader a detour.
Description
Closes #654.
PyRIT's documentation has moved from the Sphinx-based setup described in the original issue to Jupyter Book 2/MyST and the custom
gen_api_md.pygenerator. This updates that generator to build API-to-guide backlinks automatically:doc/myst.ymlJupytext
.pycompanions are used as the analysis source while the generated links continue to point to their.ipynbpages.The analysis is intentionally conservative: star/dynamic imports and instance-method type inference are not guessed. Invalid illustrative snippets are skipped so they cannot break API generation.
Tests and Documentation
python -m pytest tests/unit/build_scripts/test_gen_api_md.py -q --confcutdir=tests/unit/build_scripts— 39 passedruff check build_scripts/gen_api_md.py tests/unit/build_scripts/test_gen_api_md.pyruff format --check build_scripts/gen_api_md.py tests/unit/build_scripts/test_gen_api_md.pyty check build_scripts/gen_api_md.py tests/unit/build_scripts/test_gen_api_md.pypydoc2json.pyandgen_api_md.pymyst.ymlreferencesNo documentation source notebooks changed, so Jupytext execution was not applicable.
Disclosure: I used Codex as a coding assistant while implementing and validating this change. I reviewed the patch and generated output and will own any follow-up changes from review.