arborist-viz/arborist_viz/storage.py
russell@unturf.com 0cd96ab1d7
Initial commit: arborist VIZ — Merkle command center
Pyramid web app for inspecting arborist Merkle trees over sharded SQLite:
document roots, inclusion proofs, an interactive 3D Merkle lattice
(six.js / WebGL with graceful fallback), audit-chain linkage, and a live
SSE event stream.

Generated artifacts (env/, vendored static/six/ bundles, demo databases)
are gitignored and rebuilt via the Makefile (venv, six, init-db, serve).
2026-05-28 09:22:59 -04:00

65 lines
2 KiB
Python

"""Thin shim over ``arborist.read``.
VIZ used to do raw SQL against arborist's tables (documents, chunks,
merkle_nodes, audit_events, providence_cache) and reach into
``arborist.merkle`` / ``arborist.compress`` / ``arborist.wikitext``
directly. That violated arborist's own contract: per
``arborist.embed``, "import from here, not from arborist's internal
modules, so internal refactors don't break embedders."
We now go through :mod:`arborist.read` — the read-only counterpart to
``arborist.embed``. VIZ knows nothing about arborist's schema,
compression scheme, sharding plan, qa.db separation, or projection
versioning. All of that lives where it belongs.
This module is reduced to a process-cached :class:`arborist.read.Shards`
handle keyed by the configured shard paths.
"""
from __future__ import annotations
import logging
from typing import Optional
try:
from arborist.read import Shards, open_shards
HAVE_ARBORIST = True
except Exception: # pragma: no cover — fixture mode only
HAVE_ARBORIST = False
Shards = None # type: ignore
open_shards = None # type: ignore
log = logging.getLogger(__name__)
# Cache of Shards handles keyed by the tuple of configured paths so the
# Pyramid app reuses connections across requests.
_HANDLES: dict[tuple[str, ...], "Shards"] = {}
def get_shards(paths: list[str]) -> Optional["Shards"]:
"""Return the cached :class:`arborist.read.Shards` for these paths.
Returns ``None`` when arborist isn't importable or no paths are
configured — callers should render the "no shard" fallback state.
"""
if not HAVE_ARBORIST or not paths:
return None
key = tuple(paths)
h = _HANDLES.get(key)
if h is None:
h = open_shards(paths)
_HANDLES[key] = h
log.info("arborist.read: opened %d shard(s)", len(h.paths))
return h
def close_all() -> None:
"""Close every cached handle. Used by tests / shutdown hooks."""
for h in _HANDLES.values():
try:
h.close()
except Exception:
pass
_HANDLES.clear()