chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from mirage.cache.index import IndexCacheStore, IndexEntry
|
||||
from mirage.core.dify._client import list_all_documents
|
||||
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
|
||||
|
||||
# list_all_documents already filters to visible documents.
|
||||
documents = await list_all_documents(accessor.config)
|
||||
dir_entries = build_dir_entries(
|
||||
documents,
|
||||
prefix,
|
||||
accessor.config.slug_metadata_name,
|
||||
)
|
||||
for directory in sorted(dir_entries):
|
||||
await index.set_dir(
|
||||
directory, sorted(dir_entries[directory],
|
||||
key=lambda item: item[0]))
|
||||
|
||||
|
||||
def build_dir_entries(
|
||||
documents: list[dict[str, Any]],
|
||||
prefix: str,
|
||||
slug_metadata_name: str = "slug",
|
||||
) -> dict[str, list[tuple[str, IndexEntry]]]:
|
||||
files: dict[str, dict[str, Any]] = {}
|
||||
raw_slugs: dict[str, str] = {}
|
||||
has_slugs: dict[str, bool] = {}
|
||||
for document in documents:
|
||||
slug, has_slug = extract_slug(document, slug_metadata_name)
|
||||
path = normalize_slug(slug)
|
||||
if path in files:
|
||||
value = path.strip("/")
|
||||
raise ValueError(
|
||||
f"Duplicate {slug_metadata_name} '{value}': documents "
|
||||
f"'{files[path].get('id')}' and '{document.get('id')}' share "
|
||||
"the same path.")
|
||||
files[path] = document
|
||||
raw_slugs[path] = str(slug)
|
||||
has_slugs[path] = has_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, document in sorted(files.items()):
|
||||
entry = IndexEntry(
|
||||
id=str(document["id"]),
|
||||
name=gnu_basename(path),
|
||||
resource_type="file",
|
||||
size=extract_document_size(document),
|
||||
remote_time=timestamp_to_iso(document.get("created_at")),
|
||||
extra={
|
||||
"slug": path.strip("/"),
|
||||
"slug_metadata_name": slug_metadata_name,
|
||||
"raw_slug": raw_slugs[path],
|
||||
"has_slug": has_slugs[path],
|
||||
"tokens": document.get("tokens"),
|
||||
"indexing_status": document.get("indexing_status"),
|
||||
"data_source_type": document.get("data_source_type"),
|
||||
},
|
||||
)
|
||||
dir_entries[virtual_path(parent(path), prefix)].append(
|
||||
(entry.name, entry))
|
||||
return dir_entries
|
||||
|
||||
|
||||
def extract_slug(document: dict[str, Any],
|
||||
slug_metadata_name: str = "slug") -> tuple[str, bool]:
|
||||
metadata = document.get("doc_metadata")
|
||||
if isinstance(metadata, list):
|
||||
for item in metadata:
|
||||
if (isinstance(item, dict)
|
||||
and item.get("name") == slug_metadata_name):
|
||||
value = item.get("value")
|
||||
if value is not None:
|
||||
return str(value), True
|
||||
if (isinstance(metadata, dict)
|
||||
and metadata.get(slug_metadata_name) is not None):
|
||||
return str(metadata[slug_metadata_name]), True
|
||||
return str(document["name"]), False
|
||||
|
||||
|
||||
def normalize_slug(value: str) -> str:
|
||||
parts = [part for part in value.strip("/").split("/") if part]
|
||||
if not parts:
|
||||
raise ValueError("Invalid empty Dify document slug.")
|
||||
invalid = {".", ".."}
|
||||
for part in parts:
|
||||
if part in invalid:
|
||||
raise ValueError(f"Invalid Dify document slug 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: document "
|
||||
f"'{files[ancestor].get('id')}' uses file path "
|
||||
f"'{ancestor.strip('/')}' but document "
|
||||
f"'{files[path].get('id')}' requires it as a directory "
|
||||
"prefix.")
|
||||
|
||||
|
||||
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 extract_document_size(document: dict[str, Any]) -> int | None:
|
||||
candidates = (
|
||||
document.get("data_source_detail_dict"),
|
||||
document.get("data_source_info"),
|
||||
)
|
||||
for candidate in candidates:
|
||||
if isinstance(candidate, dict):
|
||||
upload_file = candidate.get("upload_file")
|
||||
if isinstance(upload_file, dict):
|
||||
size = upload_file.get("size")
|
||||
if isinstance(size, int):
|
||||
return size
|
||||
return None
|
||||
|
||||
|
||||
def timestamp_to_iso(value: object) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, (int, float)):
|
||||
return datetime.fromtimestamp(value, timezone.utc).isoformat()
|
||||
return str(value)
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user