Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

181 lines
5.8 KiB
Python

# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import logging
import uuid
from typing import Any
from qdrant_client import models
from qdrant_client.http.exceptions import UnexpectedResponse
from mirage.accessor.qdrant import QdrantAccessor
logger = logging.getLogger(__name__)
SCROLL_BATCH = 256
def _coerce(value: str) -> Any:
if value.lstrip("-").isdigit():
as_int = int(value)
if str(as_int) == value:
return as_int
return value
def _filter(filters: dict[str, str]) -> models.Filter | None:
if not filters:
return None
return models.Filter(must=[
models.FieldCondition(key=column,
match=models.MatchValue(value=_coerce(value)))
for column, value in filters.items()
])
def _point_to_row(point: Any, id_field: str) -> dict:
payload = point.payload if isinstance(point.payload, dict) else {}
row = dict(payload)
row[id_field] = point.id
return row
def _candidate_ids(row_id: str) -> list[Any]:
if row_id.lstrip("-").isdigit():
return [int(row_id)]
try:
uuid.UUID(row_id)
except ValueError:
return []
return [row_id]
async def _scroll_raw(client: Any, collection: str, flt: models.Filter | None,
limit: int) -> list[Any]:
points: list[Any] = []
offset: Any = None
while len(points) < limit:
batch, offset = await client.scroll(
collection_name=collection,
scroll_filter=flt,
limit=min(SCROLL_BATCH, limit - len(points)),
offset=offset,
with_payload=True,
with_vectors=False,
)
points.extend(batch)
if offset is None:
break
return points[:limit]
def _is_index_required(exc: UnexpectedResponse) -> bool:
content = exc.content
text = content.decode() if isinstance(content, bytes) else str(content)
return exc.status_code == 400 and "index required" in text.lower()
async def _ensure_indexes(client: Any, accessor: QdrantAccessor,
collection: str) -> None:
if collection in accessor._indexes_ensured:
return
for field in accessor.config.group_by:
await client.create_payload_index(
collection_name=collection,
field_name=field,
field_schema="keyword",
)
accessor._indexes_ensured.add(collection)
async def _scroll_all(accessor: QdrantAccessor, collection: str,
filters: dict[str, str], limit: int) -> list[Any]:
client = await accessor.client()
if not filters:
return await _scroll_raw(client, collection, None, limit)
flt = _filter(filters)
try:
return await _scroll_raw(client, collection, flt, limit)
except UnexpectedResponse as exc:
if not _is_index_required(exc):
raise
await _ensure_indexes(client, accessor, collection)
return await _scroll_raw(client, collection, flt, limit)
async def list_tables(accessor: QdrantAccessor) -> list[str]:
client = await accessor.client()
result = await client.get_collections()
return sorted(item.name for item in result.collections)
async def table_exists(accessor: QdrantAccessor, name: str) -> bool:
client = await accessor.client()
return await client.collection_exists(name)
async def distinct_values(accessor: QdrantAccessor, table: str, column: str,
filters: dict[str, str], limit: int) -> list[str]:
points = await _scroll_all(accessor, table, filters, limit)
values = {
str(payload[column])
for point in points
if (payload := point.payload or {}).get(column) is not None
}
return sorted(values)
async def rows_matching(accessor: QdrantAccessor, table: str,
filters: dict[str, str], limit: int) -> list[dict]:
points = await _scroll_all(accessor, table, filters, limit)
return [_point_to_row(point, accessor.config.id_field) for point in points]
async def row_record(accessor: QdrantAccessor, table: str, id_field: str,
row_id: str) -> dict | None:
ids = _candidate_ids(row_id)
if not ids:
return None
client = await accessor.client()
found = await client.retrieve(collection_name=table,
ids=ids,
with_payload=True,
with_vectors=False)
if not found:
return None
return _point_to_row(found[0], id_field)
async def search_rows(accessor: QdrantAccessor, table: str, query_text: str,
limit: int) -> list[dict]:
key = (table, query_text, limit)
cached = accessor.cached_search(key)
if cached is not None:
return cached
client = await accessor.client()
response = await client.query_points(
collection_name=table,
query=models.Document(text=query_text,
model=accessor.config.embedding_model),
limit=limit,
with_payload=True,
)
rows: list[dict] = []
for point in response.points:
row = _point_to_row(point, accessor.config.id_field)
row["_score"] = point.score
rows.append(row)
accessor.store_search(key, rows)
return rows