Source code for scitex_app.sdk._filesystem

#!/usr/bin/env python3
# Timestamp: 2026-03-13
# File: scitex_app/sdk/_filesystem.py

"""FileSystem backend — local-disk implementation of FilesBackend."""

from __future__ import annotations

import shutil
from pathlib import Path
from typing import List, Optional, Union


[docs] class FileSystemBackend: """Local filesystem implementation of FilesBackend. All paths are relative to a root directory. Path traversal outside root is prevented. Parameters ---------- root : str or Path Root directory for all file operations. """ def __init__(self, root: Union[str, Path]) -> None: self._root = Path(root).resolve() @property def root(self) -> Path: """Root directory.""" return self._root def _resolve(self, path: str) -> Path: """Resolve relative path, preventing traversal attacks.""" resolved = (self._root / path.lstrip("/")).resolve() if not str(resolved).startswith(str(self._root)): raise ValueError(f"Path traversal detected: {path!r}") return resolved
[docs] def read(self, path: str, *, binary: bool = False) -> Union[str, bytes]: """Read file content.""" target = self._resolve(path) if not target.exists(): raise FileNotFoundError(f"File not found: {path!r}") if binary: return target.read_bytes() return target.read_text(encoding="utf-8")
[docs] def write(self, path: str, content: Union[str, bytes]) -> None: """Write content to a file, creating parent dirs as needed.""" target = self._resolve(path) target.parent.mkdir(parents=True, exist_ok=True) if isinstance(content, bytes): target.write_bytes(content) else: target.write_text(content, encoding="utf-8")
[docs] def list( self, directory: str = "", *, extensions: Optional[List[str]] = None, ) -> List[str]: """List file paths in a directory.""" target = self._resolve(directory) if directory else self._root if not target.exists(): return [] results = [] for item in sorted(target.iterdir()): if item.is_file(): if extensions and item.suffix.lower() not in extensions: continue results.append(str(item.relative_to(self._root))) return results
[docs] def exists(self, path: str) -> bool: """Check if a file exists.""" return self._resolve(path).exists()
[docs] def delete(self, path: str) -> None: """Delete a file.""" target = self._resolve(path) if not target.exists(): raise FileNotFoundError(f"File not found: {path!r}") target.unlink()
[docs] def rename(self, old_path: str, new_path: str) -> None: """Rename/move a file.""" old = self._resolve(old_path) new = self._resolve(new_path) if not old.exists(): raise FileNotFoundError(f"File not found: {old_path!r}") if new.exists(): raise FileExistsError(f"File already exists: {new_path!r}") new.parent.mkdir(parents=True, exist_ok=True) old.rename(new)
[docs] def copy(self, src_path: str, dest_path: str) -> None: """Copy a file.""" src = self._resolve(src_path) dest = self._resolve(dest_path) if not src.exists(): raise FileNotFoundError(f"File not found: {src_path!r}") dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dest)
def __repr__(self) -> str: return f"FileSystemBackend({self._root})"
# EOF