Skip to content

inventory

mkdocs_terok.inventory

Sibling-decoupled objects.inv builder for terok-* repos.

Each terok repo's docs build references symbols from its siblings via mkdocstrings inventories: URLs. When every repo's strict build needs every other repo's inventory, a brand-new repo (or a sibling whose Pages deploy is mid-rebuild) breaks the cycle for everybody.

This module produces a objects.inv for the current repo without needing any sibling's inventory to exist: it loads the project's properdocs.yml, drops every inventory URL pointing at another terok-* artifact, then runs a non-strict properdocs build into a scratch directory and copies the resulting site/objects.inv to the caller's chosen output path.

The output is published independently of the docs site (via the terok-ai/docs-inventories Contents-API bucket — see .github/workflows/publish-inventory.yml), so the strict docs build of every repo can fetch a fresh sibling inventory regardless of where the sibling's own docs deploy is in its lifecycle.

build_inventory(*, config, output)

Generate objects.inv for the project at config.

Loads config as text, removes sibling-terok inventory list entries, writes the patched config alongside the original, runs properdocs build --no-strict into a scratch site/ directory, then copies site/objects.inv to output.

Raises:

Type Description
CalledProcessError

the underlying properdocs build failed. The exception's returncode carries the exit code.

FileNotFoundError

the build completed but no objects.inv was produced (a ProperDocs configuration bug rather than a build failure).

Source code in src/mkdocs_terok/inventory.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def build_inventory(*, config: Path, output: Path) -> None:
    """Generate ``objects.inv`` for the project at *config*.

    Loads *config* as text, removes sibling-terok inventory list entries,
    writes the patched config alongside the original, runs ``properdocs
    build --no-strict`` into a scratch ``site/`` directory, then copies
    ``site/objects.inv`` to *output*.

    Raises:
        subprocess.CalledProcessError: the underlying ``properdocs build``
            failed.  The exception's ``returncode`` carries the exit code.
        FileNotFoundError: the build completed but no ``objects.inv`` was
            produced (a ProperDocs configuration bug rather than a build
            failure).
    """
    patched_text = _strip_sibling_inventory_lines(config.read_text())

    # ProperDocs resolves ``docs_dir`` and other relative paths against the
    # config file's location, so the patched copy must live next to the
    # original — a remote tmpdir would break ``docs/``, asset paths, etc.
    # ``NamedTemporaryFile(delete=False)`` plus explicit unlink avoids the
    # close-time delete that would race the subprocess.
    config_dir = config.parent.resolve()
    with tempfile.NamedTemporaryFile(
        mode="w",
        prefix=".inventory-",
        suffix=".yml",
        dir=config_dir,
        delete=False,
    ) as patched_file:
        patched_file.write(patched_text)
        patched_path = Path(patched_file.name)

    # The terok plugin honors INVENTORY_ONLY_ENV to skip generators that
    # don't feed objects.inv (test_map needs pytest, code_metrics needs
    # scc/vulture, …) — so a ``poetry install --only main,docs`` env can
    # still produce an inventory.
    env = {**os.environ, INVENTORY_ONLY_ENV: "1"}

    try:
        with tempfile.TemporaryDirectory(prefix="mkdocs-terok-inventory-") as tmp:
            site_dir = Path(tmp) / "site"
            subprocess.run(
                [
                    "properdocs",
                    "build",
                    "--no-strict",
                    "--config-file",
                    str(patched_path),
                    "--site-dir",
                    str(site_dir),
                ],
                check=True,
                env=env,
            )
            produced = site_dir / "objects.inv"
            if not produced.is_file():
                raise FileNotFoundError(f"objects.inv was not produced at {produced}")
            output.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(produced, output)
    finally:
        patched_path.unlink(missing_ok=True)