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

155 lines
5.3 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 dataclasses import dataclass
from mirage.resource.base import BaseResource
from mirage.types import MountMode
from mirage.utils.path import resolve_symlinks
from mirage.workspace.mount.mount import MountEntry
from mirage.workspace.mount.registry import MountRegistry
@dataclass(frozen=True, slots=True)
class LinkEntry:
target: str
mtime: float
class Namespace:
"""Addressing authority: maps virtual paths to their mounts.
Owns the mount registry and the symlink table (and, in a later phase, the
attribute overlay). Pure addressing: it resolves a virtual path to its
mount and backend-relative path, following symlinks and crossing mounts.
It holds no cache and performs no backend I/O. Op execution and caching
live in the Dispatcher, which calls this layer to locate the mount.
Symlinks are stored verbatim as typed: the target string is kept exactly as
the user wrote it (relative targets are resolved lazily against the link's
own parent at resolution time), so ``readlink`` is GNU-faithful.
"""
def __init__(self, registry: MountRegistry) -> None:
self._registry = registry
self._symlinks: dict[str, LinkEntry] = {}
@property
def registry(self) -> MountRegistry:
return self._registry
@property
def symlinks(self) -> dict[str, LinkEntry]:
return self._symlinks
def replace_symlinks(self, entries: dict[str, LinkEntry]) -> None:
self._symlinks = dict(entries)
def symlink_targets(self) -> dict[str, str]:
return {link: entry.target for link, entry in self._symlinks.items()}
def is_link(self, path: str) -> bool:
return path in self._symlinks
def readlink(self, path: str) -> str | None:
entry = self._symlinks.get(path)
return entry.target if entry is not None else None
def symlink(self, link: str, target: str, mtime: float) -> None:
self._symlinks[link] = LinkEntry(target=target, mtime=mtime)
def unlink(self, path: str) -> bool:
if path in self._symlinks:
del self._symlinks[path]
return True
return False
def rename(self, src: str, dst: str) -> bool:
entry = self._symlinks.pop(src, None)
if entry is None:
return False
self._symlinks[dst] = entry
return True
def follow(self, path: str) -> str:
"""Return ``path`` with all symlink prefixes resolved.
Identity when the table is empty or nothing matches.
Args:
path (str): absolute virtual path.
Raises:
CycleError: when resolution exceeds the hop limit (ELOOP).
"""
if not self._symlinks:
return path
return resolve_symlinks(path, self.symlink_targets())
def links_under(self, directory: str) -> dict[str, str]:
"""Links living directly under a directory.
Args:
directory (str): absolute virtual directory path.
Returns:
dict[str, str]: link basename to target, for entries whose
parent is exactly ``directory``.
"""
base = directory.rstrip("/") + "/"
out: dict[str, str] = {}
for link, entry in self._symlinks.items():
if link.startswith(base) and "/" not in link[len(base):]:
out[link[len(base):]] = entry.target
return out
def purge_under(self, directory: str) -> int:
"""Drop every link entry under a directory (``rm -r`` semantics).
Args:
directory (str): absolute virtual directory path being removed.
Returns:
int: number of entries dropped.
"""
base = directory.rstrip("/") + "/"
doomed = [link for link in self._symlinks if link.startswith(base)]
for link in doomed:
del self._symlinks[link]
return len(doomed)
def resolve(self,
path: str,
*,
follow: bool = True) -> tuple[BaseResource, str, MountMode]:
"""Map a virtual path to ``(resource, resource_path, mode)``.
Args:
path (str): virtual path to resolve.
follow (bool): follow symlinks (the symlink table) before mapping
the path to its mount.
Raises:
CycleError: when symlink resolution exceeds the hop limit (ELOOP).
"""
if follow and self._symlinks:
path = resolve_symlinks(path, self.symlink_targets())
return self._registry.resolve(path)
def mount_for(self, path: str) -> MountEntry:
return self._registry.mount_for(path)
def is_mount_root(self, path: str) -> bool:
return self._registry.is_mount_root(path)