From 325d4423e1c6ab73da477a3345a49e14ab04bb6b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 15:21:14 +0000 Subject: [PATCH] feat: expose manifest_from_mapping and demo_function API on docgen package Add manifest_from_mapping() as the supported way to build demo manifests from dicts for CLI/VHS or declarative Playwright flows without TS/pytest paths. Re-export demo-function symbols from docgen.__init__ with version aligned to pyproject. Include tests for programmatic CLI manifests and package imports. Co-authored-by: John Menke --- src/docgen/__init__.py | 55 +++++++++++++++++++++++++++++++++-- src/docgen/demo_function.py | 24 +++++++++++++++ tests/test_demo_function.py | 17 +++++++++++ tests/test_package_exports.py | 8 +++++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/test_package_exports.py diff --git a/src/docgen/__init__.py b/src/docgen/__init__.py index 4857811..1c88f97 100644 --- a/src/docgen/__init__.py +++ b/src/docgen/__init__.py @@ -1,3 +1,54 @@ -"""docgen — reusable demo generation pipeline.""" +"""docgen — reusable demo generation pipeline. -__version__ = "0.1.0" +The package root re-exports the per-function demo API (`demo_function`) so +consumers can use ``from docgen import load_manifest, render`` without reaching +into submodules. For CLI / VHS demos (no Playwright tests), build a manifest with +`manifest_from_mapping` and pass it to `render`. +""" + +from docgen.demo_function import ( + CACHED_ARTIFACTS, + HARD_CAP_SECONDS, + Action, + EXIT_INVALID, + EXIT_NEUTRAL_SKIP, + EXIT_OK, + EXIT_TOOLING_MISSING, + Manifest, + ManifestError, + NarrationResult, + PlaceholderManifest, + RenderResult, + SUPPORTED_ACTION_KINDS, + ToolingMissingError, + generate_capture_script, + load_manifest, + manifest_from_mapping, + render, + run_cli, +) + +__version__ = "0.2.0" + +__all__ = [ + "__version__", + "Action", + "CACHED_ARTIFACTS", + "EXIT_INVALID", + "EXIT_NEUTRAL_SKIP", + "EXIT_OK", + "EXIT_TOOLING_MISSING", + "HARD_CAP_SECONDS", + "Manifest", + "ManifestError", + "NarrationResult", + "PlaceholderManifest", + "RenderResult", + "SUPPORTED_ACTION_KINDS", + "ToolingMissingError", + "generate_capture_script", + "load_manifest", + "manifest_from_mapping", + "render", + "run_cli", +] diff --git a/src/docgen/demo_function.py b/src/docgen/demo_function.py index 14a02c2..8f64763 100644 --- a/src/docgen/demo_function.py +++ b/src/docgen/demo_function.py @@ -592,6 +592,29 @@ def _load_yaml_sidecar(path: Path) -> Manifest: return _coerce(raw, source_path=path) +def manifest_from_mapping( + raw: dict[str, Any], + *, + source_path: Path | None = None, +) -> Manifest: + """Build a validated `Manifest` from the same mapping shape as a ``*.docgen.yaml``. + + For programmatic use when you are not loading YAML from disk and not using a + Playwright ``.ts`` spec or ``path.py::test_name``. Typical cases: + + - **CLI / VHS** — ``demonstration.kind: cli`` and ``demonstration.tape`` pointing + at a ``.tape`` file (no Playwright involved). + - **Declarative browser** — ``demonstration.kind: playwright`` with ``url`` and + ``actions`` (docgen drives Playwright from Python; no Node ``@playwright/test``). + + ``source_path`` resolves relative paths inside the manifest (fixtures, tape, + optional ``spec``/``cwd``) the same way as a sidecar file next to your assets. + + Raises `ManifestError` if the mapping violates the schema. + """ + return _coerce(raw, source_path=source_path) + + def _load_pytest_marker(path: Path, test_name: str) -> Manifest: """Read `@pytest.mark.docgen(...)` decorator on `test_name` via `ast`. @@ -1676,6 +1699,7 @@ def run_cli( "RenderResult", "NarrationResult", "load_manifest", + "manifest_from_mapping", "render", "run_cli", "generate_capture_script", diff --git a/tests/test_demo_function.py b/tests/test_demo_function.py index ff13d29..9302c54 100644 --- a/tests/test_demo_function.py +++ b/tests/test_demo_function.py @@ -25,6 +25,7 @@ _render_action, generate_capture_script, load_manifest, + manifest_from_mapping, run_cli, ) @@ -172,6 +173,22 @@ def test_cli_kind_requires_tape() -> None: _coerce(raw) +def test_manifest_from_mapping_cli(tmp_path: Path) -> None: + """Programmatic manifest for VHS (no Playwright test files).""" + tape = tmp_path / "demo.tape" + tape.write_text("Output out.mp4\n", encoding="utf-8") + sidecar = tmp_path / "side.docgen.yaml" + raw = { + "identifier": "cli:demo", + "intent": "Shows the CLI.", + "demonstration": {"kind": "cli", "tape": "demo.tape"}, + "output_budget": {"duration_seconds": 15, "resolution": "1280x720"}, + } + m = manifest_from_mapping(raw, source_path=sidecar) + assert m.kind == "cli" + assert m.cli_tape == tape.resolve() + + def test_playwright_spec_requires_grep(tmp_path: Path) -> None: spec = tmp_path / "t.spec.ts" spec.write_text("//", encoding="utf-8") diff --git a/tests/test_package_exports.py b/tests/test_package_exports.py new file mode 100644 index 0000000..59865f5 --- /dev/null +++ b/tests/test_package_exports.py @@ -0,0 +1,8 @@ +"""Smoke tests for `docgen` package-level re-exports.""" + +from docgen import manifest_from_mapping, render + + +def test_manifest_from_mapping_exported_at_package_root() -> None: + assert callable(manifest_from_mapping) + assert callable(render)