Files
zooqueenandHanzo Dev 920f767417 style: auto-fix ruff lint and format for Python 3.12
Run ruff check --fix and ruff format across the repo to fix ~495
auto-fixable lint errors (mostly I001 import sorting) and reformat
225 files to match the py312 target.

Co-authored-by: Hanzo Dev <[email protected]>
2026-02-20 21:29:31 -08:00

256 lines
6.2 KiB
Python

"""Async path operations.
Provides non-blocking path operations using run_in_executor.
All operations are fully async and safe for use in event loops.
Note: Uses ThreadPoolExecutor for filesystem metadata operations (stat, exists,
glob, etc.) rather than aiofiles, as these are fast syscalls that don't benefit
significantly from true async I/O. The executor pattern prevents event loop
blocking while keeping the implementation simple and reliable.
For file content operations (read/write), use hanzo_async.files which wraps aiofiles.
"""
import os
import asyncio
from glob import glob as sync_glob
from typing import List, Union, Optional
from pathlib import Path
async def _run_in_executor(func, *args):
"""Run a sync function in executor."""
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, func, *args)
async def path_exists(path: Union[str, Path]) -> bool:
"""Check if path exists asynchronously.
Args:
path: Path to check
Returns:
True if path exists
"""
path = Path(path)
return await _run_in_executor(path.exists)
async def is_file(path: Union[str, Path]) -> bool:
"""Check if path is a file asynchronously.
Args:
path: Path to check
Returns:
True if path is a file
"""
path = Path(path)
return await _run_in_executor(path.is_file)
async def is_dir(path: Union[str, Path]) -> bool:
"""Check if path is a directory asynchronously.
Args:
path: Path to check
Returns:
True if path is a directory
"""
path = Path(path)
return await _run_in_executor(path.is_dir)
async def mkdir(
path: Union[str, Path],
parents: bool = False,
exist_ok: bool = False,
) -> None:
"""Create directory asynchronously.
Args:
path: Directory path to create
parents: Create parent directories (default: False)
exist_ok: Don't error if exists (default: False)
Raises:
FileExistsError: If directory exists and exist_ok=False
PermissionError: If permission denied
"""
path = Path(path)
def _mkdir():
path.mkdir(parents=parents, exist_ok=exist_ok)
await _run_in_executor(_mkdir)
async def rmdir(path: Union[str, Path]) -> None:
"""Remove empty directory asynchronously.
Args:
path: Directory to remove
Raises:
OSError: If directory not empty
FileNotFoundError: If directory doesn't exist
"""
path = Path(path)
await _run_in_executor(path.rmdir)
async def unlink(path: Union[str, Path], missing_ok: bool = False) -> None:
"""Remove file asynchronously.
Args:
path: File to remove
missing_ok: Don't error if file missing (default: False)
Raises:
FileNotFoundError: If file doesn't exist and missing_ok=False
"""
path = Path(path)
def _unlink():
path.unlink(missing_ok=missing_ok)
await _run_in_executor(_unlink)
async def stat(path: Union[str, Path]) -> os.stat_result:
"""Get file/directory stats asynchronously.
Args:
path: Path to stat
Returns:
os.stat_result with file info
Raises:
FileNotFoundError: If path doesn't exist
"""
path = Path(path)
return await _run_in_executor(path.stat)
async def listdir(path: Union[str, Path] = ".") -> List[str]:
"""List directory contents asynchronously.
Args:
path: Directory to list (default: current directory)
Returns:
List of entry names
Raises:
FileNotFoundError: If directory doesn't exist
NotADirectoryError: If path is not a directory
"""
return await _run_in_executor(os.listdir, str(path))
async def glob(
pattern: str,
root_dir: Optional[Union[str, Path]] = None,
recursive: bool = False,
) -> List[str]:
"""Find files matching pattern asynchronously.
Args:
pattern: Glob pattern (e.g., "*.py", "**/*.txt")
root_dir: Root directory for search (default: current directory)
recursive: Enable ** pattern (default: False)
Returns:
List of matching paths
"""
def _glob():
if root_dir:
old_cwd = os.getcwd()
os.chdir(str(root_dir))
try:
return sync_glob(pattern, recursive=recursive)
finally:
os.chdir(old_cwd)
else:
return sync_glob(pattern, recursive=recursive)
return await _run_in_executor(_glob)
async def rename(src: Union[str, Path], dst: Union[str, Path]) -> None:
"""Rename/move file or directory asynchronously.
Args:
src: Source path
dst: Destination path
Raises:
FileNotFoundError: If source doesn't exist
FileExistsError: If destination exists
"""
src = Path(src)
dst = Path(dst)
await _run_in_executor(src.rename, dst)
async def copy(src: Union[str, Path], dst: Union[str, Path]) -> None:
"""Copy file asynchronously.
Args:
src: Source file path
dst: Destination file path
Raises:
FileNotFoundError: If source doesn't exist
PermissionError: If permission denied
"""
import shutil
await _run_in_executor(shutil.copy2, str(src), str(dst))
async def copytree(
src: Union[str, Path],
dst: Union[str, Path],
dirs_exist_ok: bool = False,
) -> None:
"""Copy directory tree asynchronously.
Args:
src: Source directory path
dst: Destination directory path
dirs_exist_ok: Don't error if dst exists (default: False)
Raises:
FileNotFoundError: If source doesn't exist
FileExistsError: If destination exists and dirs_exist_ok=False
"""
import shutil
def _copytree():
shutil.copytree(str(src), str(dst), dirs_exist_ok=dirs_exist_ok)
await _run_in_executor(_copytree)
async def rmtree(path: Union[str, Path], ignore_errors: bool = False) -> None:
"""Remove directory tree asynchronously.
Args:
path: Directory to remove
ignore_errors: Ignore errors during removal (default: False)
Raises:
FileNotFoundError: If directory doesn't exist and ignore_errors=False
"""
import shutil
def _rmtree():
shutil.rmtree(str(path), ignore_errors=ignore_errors)
await _run_in_executor(_rmtree)