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

205 lines
8.2 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. =========
from collections.abc import AsyncIterator, Callable
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic_bind.adapter import CommandIO
from mirage.io.stream import materialize
from mirage.io.types import ByteSource, IOResult
from mirage.types import FileType, PathSpec
from mirage.utils.errors import FS_ERRORS, eisdir, fs_error_line
from mirage.utils.path import norm, parent
async def resolve_or_empty(ops: CommandIO, accessor: Accessor,
paths: list[PathSpec],
index: IndexCacheStore | None) -> list[PathSpec]:
if paths and ops.is_mounted(accessor):
return await ops.resolve_glob(accessor, paths, index)
return []
async def _is_implicit_dir(ops: CommandIO, accessor: Accessor, path: PathSpec,
index: IndexCacheStore | None) -> bool:
"""Whether a path that failed stat with ENOENT is an implicit directory.
Keyed backends (RAM/Redis/S3) have no directory entries: stat of a
prefix that only exists through deeper keys raises ENOENT. The operand's
own readdir cannot serve as the probe: synthetic hierarchies fabricate
children for any name (postgres answers ``tables/views`` for a missing
schema) and database backends raise driver errors for missing tables.
The parent listing is authoritative instead: the operand is an implicit
directory only if its parent's readdir lists it. When the operand is
the mount root there is no parent to list, so its own readdir decides
(root listings are real in every backend). Any probe failure is a
negative probe (the original ENOENT stands), never an error to surface,
which is why the except is deliberately broad.
Args:
ops (CommandIO): Backend I/O bundle providing ``readdir``.
accessor (Accessor): Backend accessor.
path (PathSpec): The operand whose stat raised ENOENT.
index (IndexCacheStore | None): Index cache store for ``readdir``.
"""
target = norm(path.virtual)
key = path.resource_path.strip("/")
if not key:
try:
entries = await ops.readdir(accessor, path, index)
except Exception:
return False
return bool(entries)
parent_key = key.rsplit("/", 1)[0] if "/" in key else ""
parent_virtual = parent(target)
parent_path = PathSpec(virtual=parent_virtual,
directory=parent_virtual,
resource_path=parent_key)
try:
entries = await ops.readdir(accessor, parent_path, index)
except Exception:
return False
return any(norm(entry) == target for entry in entries)
async def split_readable(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
index: IndexCacheStore | None,
cmd_name: str,
) -> tuple[list[PathSpec], bytes]:
"""Partition operands into readable paths and GNU stderr lines.
Read-family commands (cat/head/tail/wc) process remaining operands
after one fails, per GNU coreutils: each failed operand becomes one
``<cmd>: <path>: <strerror>`` line and the command exits 1 while still
emitting output for the operands that resolved. Each path is stat'ed
eagerly so a lazy output stream never aborts mid-drain on a missing
operand. A directory operand is refused with GNU's ``Is a directory``:
explicit directories via the stat type, implicit keyed-backend
directories via a readdir probe on ENOENT (#457).
Args:
ops (CommandIO): Backend I/O bundle providing ``stat``/``readdir``.
accessor (Accessor): Backend accessor.
paths (list[PathSpec]): Glob-resolved operands in command order.
index (IndexCacheStore | None): Index cache store for ``stat``.
cmd_name (str): Command name for the stderr prefix.
Returns:
tuple[list[PathSpec], bytes]: Readable operands in order, and the
concatenated stderr lines for the failed ones (``b""`` if none).
"""
readable: list[PathSpec] = []
err = b""
for p in paths:
try:
st = await ops.stat(accessor, p, index)
except FileNotFoundError as exc:
failure: OSError = exc
if await _is_implicit_dir(ops, accessor, p, index):
failure = eisdir(p)
err += fs_error_line(cmd_name, p, failure).encode()
continue
except FS_ERRORS as exc:
err += fs_error_line(cmd_name, p, exc).encode()
continue
if getattr(st, "type", None) == FileType.DIRECTORY:
err += fs_error_line(cmd_name, p, eisdir(p)).encode()
continue
readable.append(p)
return readable, err
async def _read_refusing_dirs(ops: CommandIO, index: IndexCacheStore | None,
accessor: Accessor,
path: PathSpec) -> AsyncIterator[bytes]:
try:
st = await ops.stat(accessor, path, index)
except FileNotFoundError:
if await _is_implicit_dir(ops, accessor, path, index):
raise eisdir(path) from None
raise
if getattr(st, "type", None) == FileType.DIRECTORY:
raise eisdir(path)
async for chunk in ops.read_stream(accessor, path, index):
yield chunk
def dir_refusing_read(ops: CommandIO,
index: IndexCacheStore | None) -> Callable:
"""Read-stream callable that reports directory operands as EISDIR.
For generics that read per operand and format FS errors inline (wc):
the raw backend read raises ENOENT for an implicit keyed-backend
directory, so the injected reader refines the error the same way
``split_readable`` does before the generic formats the line (#457).
Args:
ops (CommandIO): Backend I/O bundle providing ``stat``/``readdir``
and ``read_stream``.
index (IndexCacheStore | None): Index cache store bound into reads.
"""
return partial(_read_refusing_dirs, ops, index)
async def resolve_readable(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
index: IndexCacheStore | None,
cmd_name: str,
) -> tuple[list[PathSpec], bytes]:
"""Resolve globs, then drop unreadable operands via ``split_readable``.
The read-family builder entry: an empty result with empty stderr means
stdin mode (no path operands were given), while an empty result with
stderr lines means every operand failed and the builder must not fall
back to stdin.
Args:
ops (CommandIO): Backend I/O bundle.
accessor (Accessor): Backend accessor.
paths (list[PathSpec]): Raw path operands (may hold globs).
index (IndexCacheStore | None): Index cache store.
cmd_name (str): Command name for the stderr prefix.
"""
resolved = await resolve_or_empty(ops, accessor, paths, index)
if not resolved:
return [], b""
return await split_readable(ops, accessor, resolved, index, cmd_name)
async def merge_split_errors(
result: tuple[ByteSource | None, IOResult],
err: bytes,
) -> tuple[ByteSource | None, IOResult]:
"""Attach ``split_readable`` stderr lines to a generic's result.
Args:
result (tuple[ByteSource | None, IOResult]): The generic's return.
err (bytes): Stderr lines for the operands dropped by the split;
when non-empty the command exits 1, per GNU.
"""
if not err:
return result
out, io = result
existing = await materialize(io.stderr) if io.stderr else b""
io.stderr = existing + err
io.exit_code = 1
return out, io