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
139 lines
4.5 KiB
Python
139 lines
4.5 KiB
Python
import inspect
|
|
from collections import deque
|
|
from collections.abc import AsyncIterator
|
|
from typing import Any, Callable
|
|
|
|
from mirage.accessor.base import Accessor
|
|
from mirage.cache.read_through import cache_aware_read
|
|
from mirage.types import PathSpec
|
|
from mirage.utils.stream import ensure_stream
|
|
|
|
|
|
async def head(
|
|
src: bytes | AsyncIterator[bytes],
|
|
*,
|
|
n: int | None = None,
|
|
c: int | None = None,
|
|
) -> AsyncIterator[bytes]:
|
|
if c is not None:
|
|
if c == 0:
|
|
return
|
|
if c > 0:
|
|
emitted = 0
|
|
async for chunk in ensure_stream(src):
|
|
remaining = c - emitted
|
|
if len(chunk) >= remaining:
|
|
if remaining > 0:
|
|
yield chunk[:remaining]
|
|
return
|
|
yield chunk
|
|
emitted += len(chunk)
|
|
return
|
|
keep = -c
|
|
buf = b""
|
|
async for chunk in ensure_stream(src):
|
|
buf += chunk
|
|
if len(buf) > keep:
|
|
yield buf[:-keep]
|
|
buf = buf[-keep:]
|
|
return
|
|
|
|
target = n if n is not None else 10
|
|
|
|
if target >= 0:
|
|
if target == 0:
|
|
return
|
|
emitted_lines = 0
|
|
buf = b""
|
|
async for chunk in ensure_stream(src):
|
|
buf += chunk
|
|
while b"\n" in buf and emitted_lines < target:
|
|
line, buf = buf.split(b"\n", 1)
|
|
yield line + b"\n"
|
|
emitted_lines += 1
|
|
if emitted_lines >= target:
|
|
return
|
|
if buf and emitted_lines < target:
|
|
yield buf
|
|
return
|
|
|
|
keep = -target
|
|
recent: deque[bytes] = deque(maxlen=keep)
|
|
buf = b""
|
|
async for chunk in ensure_stream(src):
|
|
buf += chunk
|
|
while b"\n" in buf:
|
|
line, buf = buf.split(b"\n", 1)
|
|
if len(recent) == keep:
|
|
yield recent[0] + b"\n"
|
|
recent.append(line)
|
|
|
|
|
|
def head_multi(
|
|
paths: list[PathSpec],
|
|
*,
|
|
read: Callable[..., Any],
|
|
accessor: Accessor | None = None,
|
|
index: object = None,
|
|
n: int | None = None,
|
|
c: int | None = None,
|
|
show_headers: bool = False,
|
|
) -> AsyncIterator[bytes]:
|
|
"""Run head over multiple already-resolved paths.
|
|
|
|
Globs are expanded by the caller, so ``paths`` is a flat list of concrete
|
|
entries. When ``show_headers`` is set a ``==> path <==`` banner is emitted
|
|
before each file (POSIX/GNU head with multiple files), separated by a blank
|
|
line between files. The per-file source is produced lazily by ``read`` so
|
|
only one file streams at a time.
|
|
|
|
This is a plain ``def`` returning the async generator: the cache-aware
|
|
wrap captures the active manager now, when the command calls
|
|
``head_multi`` inside the mount's cache-manager scope, not when the
|
|
returned stream is drained later (after that scope is gone). A warm read
|
|
then returns the cached bytes; only a cold read streams lazily from the
|
|
backend, preserving early-exit (``cat big | head -5``).
|
|
|
|
Args:
|
|
paths (list[PathSpec]): Resolved paths; only ``.virtual`` is read.
|
|
read (Callable[..., Any]): Reader called as ``read(accessor, path,
|
|
index)``; returns bytes, an awaitable of bytes, or an async byte
|
|
iterator.
|
|
accessor (Accessor | None): Backend accessor passed through to
|
|
``read``.
|
|
index (object): Index cache store passed through to ``read``.
|
|
n (int | None): Line count (negative = all-but-last-N, per head).
|
|
c (int | None): Byte count.
|
|
show_headers (bool): Emit ``==> path <==`` banners between files.
|
|
"""
|
|
return _head_multi(paths,
|
|
read=cache_aware_read(read),
|
|
accessor=accessor,
|
|
index=index,
|
|
n=n,
|
|
c=c,
|
|
show_headers=show_headers)
|
|
|
|
|
|
async def _head_multi(
|
|
paths: list[PathSpec],
|
|
*,
|
|
read: Callable[..., Any],
|
|
accessor: Accessor | None = None,
|
|
index: object = None,
|
|
n: int | None = None,
|
|
c: int | None = None,
|
|
show_headers: bool = False,
|
|
) -> AsyncIterator[bytes]:
|
|
for i, p in enumerate(paths):
|
|
if show_headers:
|
|
header = f"==> {p.raw_path} <==\n"
|
|
if i > 0:
|
|
header = "\n" + header
|
|
yield header.encode()
|
|
source = read(accessor, p, index)
|
|
if inspect.isawaitable(source):
|
|
source = await source
|
|
async for chunk in head(source, n=n, c=c):
|
|
yield chunk
|