bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
162 lines
5.2 KiB
Python
162 lines
5.2 KiB
Python
import base64
|
|
import gzip
|
|
import json
|
|
from typing import Any
|
|
|
|
from mirage.cache.index import IndexCacheStore, IndexEntry
|
|
from mirage.core.chroma._client import fetch_path_tree
|
|
from mirage.utils.path import gnu_basename, parent
|
|
|
|
|
|
async def ensure_tree(accessor,
|
|
index: IndexCacheStore,
|
|
prefix: str = "") -> None:
|
|
root_key = mount_root(prefix)
|
|
listing = await index.list_dir(root_key)
|
|
if listing.entries is not None:
|
|
return
|
|
|
|
path_tree = parse_path_tree(await fetch_path_tree(accessor))
|
|
dir_entries = build_dir_entries(path_tree, prefix)
|
|
for directory in sorted(dir_entries):
|
|
await index.set_dir(
|
|
directory, sorted(dir_entries[directory],
|
|
key=lambda item: item[0]))
|
|
|
|
|
|
def parse_path_tree(raw: str) -> dict[str, dict[str, Any]]:
|
|
try:
|
|
parsed = json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
try:
|
|
decoded = gzip.decompress(base64.b64decode(raw)).decode()
|
|
parsed = json.loads(decoded)
|
|
except Exception as exc:
|
|
raise ValueError("Invalid Chroma path tree document") from exc
|
|
if not isinstance(parsed, dict):
|
|
raise ValueError("Chroma path tree must be a JSON object")
|
|
result: dict[str, dict[str, Any]] = {}
|
|
for key, value in parsed.items():
|
|
if not isinstance(key, str):
|
|
raise ValueError("Chroma path tree keys must be strings")
|
|
result[key] = value if isinstance(value, dict) else {}
|
|
return result
|
|
|
|
|
|
def build_dir_entries(
|
|
path_tree: dict[str, dict[str, Any]],
|
|
prefix: str,
|
|
) -> dict[str, list[tuple[str, IndexEntry]]]:
|
|
files: dict[str, dict[str, Any]] = {}
|
|
raw_slugs: dict[str, str] = {}
|
|
for raw_slug, metadata in path_tree.items():
|
|
path = normalize_slug(raw_slug)
|
|
if path in files:
|
|
value = path.strip("/")
|
|
raise ValueError(f"Duplicate Chroma path '{value}'")
|
|
files[path] = metadata
|
|
raw_slugs[path] = raw_slug
|
|
|
|
raise_on_collisions(files)
|
|
directories = collect_directories(set(files))
|
|
dir_entries: dict[str, list[tuple[str, IndexEntry]]] = {
|
|
virtual_path(directory, prefix): []
|
|
for directory in directories
|
|
}
|
|
|
|
for directory in sorted(directories):
|
|
if directory == "/":
|
|
continue
|
|
entry = IndexEntry(id=directory.strip("/"),
|
|
name=gnu_basename(directory),
|
|
resource_type="folder")
|
|
dir_entries[virtual_path(parent(directory), prefix)].append(
|
|
(entry.name, entry))
|
|
|
|
for path, metadata in sorted(files.items()):
|
|
slug = path.strip("/")
|
|
size = metadata_int_or_none(metadata, "size")
|
|
updated_at = metadata_or_none(metadata, "updated_at")
|
|
entry = IndexEntry(
|
|
id=slug,
|
|
name=gnu_basename(path),
|
|
resource_type="file",
|
|
size=size,
|
|
remote_time=updated_at or "",
|
|
extra={
|
|
"slug": slug,
|
|
"size": size,
|
|
"created_at": metadata_or_none(metadata, "created_at"),
|
|
"updated_at": updated_at,
|
|
},
|
|
)
|
|
dir_entries[virtual_path(parent(path), prefix)].append(
|
|
(entry.name, entry))
|
|
return dir_entries
|
|
|
|
|
|
def normalize_slug(value: str) -> str:
|
|
parts = [part for part in value.strip("/").split("/") if part]
|
|
if not parts:
|
|
raise ValueError("Invalid empty Chroma path")
|
|
invalid = {".", ".."}
|
|
for part in parts:
|
|
if part in invalid:
|
|
raise ValueError(f"Invalid Chroma path segment: {part!r}")
|
|
return "/" + "/".join(parts)
|
|
|
|
|
|
def raise_on_collisions(files: dict[str, dict[str, Any]]) -> None:
|
|
paths = set(files)
|
|
for path in sorted(paths):
|
|
parts = path.strip("/").split("/")
|
|
for index in range(1, len(parts)):
|
|
ancestor = "/" + "/".join(parts[:index])
|
|
if ancestor in paths:
|
|
raise ValueError(
|
|
"Path collision: Chroma path "
|
|
f"'{ancestor.strip('/')}' is both a file and a directory "
|
|
f"prefix for '{path.strip()}'.")
|
|
|
|
|
|
def collect_directories(paths: set[str]) -> set[str]:
|
|
directories = {"/"}
|
|
for path in paths:
|
|
parts = path.strip("/").split("/")
|
|
for index in range(1, len(parts)):
|
|
directories.add("/" + "/".join(parts[:index]))
|
|
return directories
|
|
|
|
|
|
def metadata_or_none(metadata: dict[str, Any], key: str) -> str | None:
|
|
value = metadata.get(key)
|
|
if value is None:
|
|
return None
|
|
return str(value)
|
|
|
|
|
|
def metadata_int_or_none(metadata: dict[str, Any], key: str) -> int | None:
|
|
value = metadata.get(key)
|
|
if isinstance(value, bool) or value is None:
|
|
return None
|
|
if isinstance(value, int):
|
|
return value
|
|
if isinstance(value, float):
|
|
return int(value)
|
|
if isinstance(value, str) and value.isdigit():
|
|
return int(value)
|
|
return None
|
|
|
|
|
|
def mount_root(prefix: str) -> str:
|
|
return prefix.rstrip("/") or "/"
|
|
|
|
|
|
def virtual_path(path: str, prefix: str) -> str:
|
|
root = mount_root(prefix)
|
|
if path == "/":
|
|
return root
|
|
if root == "/":
|
|
return path
|
|
return root + path
|