pathlib provides object-oriented filesystem paths. from pathlib import Path. Current dir: Path.cwd(). Home: Path.home(). Join: p = Path("/tmp") / "data" / "file.txt". exists: p.exists(). is_file/is_dir: p.is_file(); p.is_dir(). name: p.name → “file.txt”. stem: p.stem → “file”. suffix: p.suffix → “.txt”. suffixes: p.suffixes → [“.tar”,“.gz”]. parent: p.parent → Path(“/tmp/data”). parts: p.parts. resolve: p.resolve() — absolute, no symlinks. read_text: text = p.read_text(encoding="utf-8"). write_text: p.write_text("content", encoding="utf-8"). read_bytes: p.read_bytes(). write_bytes: p.write_bytes(b"data"). open: with p.open("r") as f: .... mkdir: p.mkdir(parents=True, exist_ok=True). rmdir: p.rmdir(). unlink: p.unlink(missing_ok=True). rename: p.rename(Path("/tmp/new.txt")). replace: p.replace(dest) — overwrites. stat: st = p.stat(); st.st_size; st.st_mtime. glob: list(Path(".").glob("**/*.py")). rglob: list(p.rglob("*.csv")). iterdir: for f in p.iterdir(): .... with_suffix: p.with_suffix(".bak"). with_name: p.with_name("other.txt"). with_stem: p.with_stem("newname"). symlink_to: p.symlink_to(target). touch: p.touch(). expanduser: Path("~/docs").expanduser(). samefile: p.samefile(q). relative_to: p.relative_to(base). Claude Code generates pathlib file utilities, directory walkers, build scripts, and I/O helpers.
CLAUDE.md for pathlib
## pathlib Stack
- Stdlib: from pathlib import Path (Python 3.4+, all platforms)
- Build: p = Path("/base") / "subdir" / "file.ext"
- Read/write: p.read_text(encoding="utf-8") | p.write_text(content) | p.read_bytes()
- Inspect: p.exists() | p.is_file() | p.suffix | p.stem | p.parent | p.stat().st_size
- Find: list(p.rglob("*.py")) | for f in p.iterdir(): if f.is_file(): ...
- Mutate: p.mkdir(parents=True, exist_ok=True) | p.unlink(missing_ok=True) | p.rename(dst)
pathlib File System Pipeline
# app/fs.py — pathlib Path, I/O, glob, mkdir, copy, find, clean, atomic write
from __future__ import annotations
import hashlib
import os
import shutil
import stat
import time
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Generator, Iterator
# ─────────────────────────────────────────────────────────────────────────────
# 1. Path construction helpers
# ─────────────────────────────────────────────────────────────────────────────
def project_root(*markers: str) -> Path:
"""
Walk up from cwd until a directory containing any of the markers is found.
Useful for finding the project root regardless of where tests are run.
Example:
root = project_root("pyproject.toml", ".git")
config = root / "config" / "settings.toml"
"""
current = Path.cwd()
for directory in [current, *current.parents]:
if any((directory / m).exists() for m in markers):
return directory
return current # fallback
def ensure_dir(path: str | Path) -> Path:
"""
Create a directory (and parents) if it doesn't exist.
Example:
log_dir = ensure_dir("logs/2024/01")
(log_dir / "app.log").write_text("started")
"""
p = Path(path)
p.mkdir(parents=True, exist_ok=True)
return p
def temp_path(suffix: str = "", prefix: str = "tmp_") -> Path:
"""
Return a Path to a unique temp file (not yet created).
Example:
p = temp_path(".json")
p.write_text(json.dumps(data))
"""
import tempfile
_, name = tempfile.mkstemp(suffix=suffix, prefix=prefix)
os.unlink(name) # remove — caller will create the file
return Path(name)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Read / write helpers
# ─────────────────────────────────────────────────────────────────────────────
def read_text(path: str | Path, encoding: str = "utf-8", default: str | None = None) -> str:
"""
Read a text file; return default if missing (raises if default is None).
Example:
content = read_text("README.md")
config = read_text("local.cfg", default="")
"""
try:
return Path(path).read_text(encoding=encoding)
except FileNotFoundError:
if default is not None:
return default
raise
def write_text_safe(
path: str | Path,
content: str,
encoding: str = "utf-8",
make_parents: bool = True,
) -> Path:
"""
Write text to a file atomically (write to .tmp then rename).
Creates parent directories if make_parents=True.
Example:
write_text_safe("dist/index.html", rendered_html)
"""
p = Path(path)
if make_parents:
p.parent.mkdir(parents=True, exist_ok=True)
tmp = p.with_suffix(p.suffix + ".tmp")
tmp.write_text(content, encoding=encoding)
tmp.replace(p)
return p
def write_bytes_safe(path: str | Path, data: bytes, make_parents: bool = True) -> Path:
"""Atomic binary write."""
p = Path(path)
if make_parents:
p.parent.mkdir(parents=True, exist_ok=True)
tmp = p.with_suffix(p.suffix + ".tmp")
tmp.write_bytes(data)
tmp.replace(p)
return p
@contextmanager
def open_atomic(path: str | Path, mode: str = "w", encoding: str = "utf-8") -> Generator:
"""
Open a file handle that writes atomically (temp file → rename on close).
Example:
with open_atomic("output/report.csv") as f:
writer = csv.writer(f)
writer.writerows(data)
"""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
tmp = p.with_suffix(p.suffix + ".tmp")
is_binary = "b" in mode
kwargs = {} if is_binary else {"encoding": encoding}
try:
with open(tmp, mode, **kwargs) as f:
yield f
tmp.replace(p)
except Exception:
tmp.unlink(missing_ok=True)
raise
# ─────────────────────────────────────────────────────────────────────────────
# 3. File discovery
# ─────────────────────────────────────────────────────────────────────────────
def find_files(
root: str | Path,
pattern: str = "**/*",
exclude_dirs: set[str] | None = None,
min_size: int = 0,
newer_than: float | None = None,
) -> list[Path]:
"""
Find files matching a glob pattern with optional filters.
Example:
py_files = find_files("src", "**/*.py", exclude_dirs={".venv","__pycache__"})
recent = find_files(".", newer_than=time.time() - 86400)
"""
root = Path(root)
excluded = exclude_dirs or {".git", ".venv", "node_modules", "__pycache__", ".mypy_cache"}
results: list[Path] = []
for p in root.glob(pattern):
if not p.is_file():
continue
if any(part in excluded for part in p.parts):
continue
try:
st = p.stat()
if st.st_size < min_size:
continue
if newer_than and st.st_mtime < newer_than:
continue
except OSError:
continue
results.append(p)
return sorted(results)
def iter_files(
root: str | Path,
pattern: str = "*",
recursive: bool = True,
) -> Iterator[Path]:
"""
Lazily yield file paths.
Example:
for csv_file in iter_files("data", "*.csv"):
process(csv_file)
"""
root = Path(root)
glob_fn = root.rglob if recursive else root.glob
for p in glob_fn(pattern):
if p.is_file():
yield p
def walk_tree(root: str | Path) -> Iterator[tuple[Path, list[Path], list[Path]]]:
"""
Walk directory tree yielding (dirpath, subdirs, files) — like os.walk but with Paths.
Example:
for dirpath, subdirs, files in walk_tree("src"):
print(f"{dirpath}: {len(files)} files")
"""
root = Path(root)
dirs: list[Path] = []
files: list[Path] = []
for entry in root.iterdir():
(dirs if entry.is_dir() else files).append(entry)
yield root, dirs, files
for subdir in dirs:
yield from walk_tree(subdir)
# ─────────────────────────────────────────────────────────────────────────────
# 4. File operations
# ─────────────────────────────────────────────────────────────────────────────
def copy(src: str | Path, dst: str | Path, overwrite: bool = True) -> Path:
"""
Copy a file; create destination directory if needed.
Example:
copy("src/config.toml", "dist/config.toml")
"""
src, dst = Path(src), Path(dst)
if not overwrite and dst.exists():
return dst
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(str(src), str(dst))
return dst
def copy_tree(src: str | Path, dst: str | Path, overwrite: bool = True) -> Path:
"""
Recursively copy a directory tree.
Example:
copy_tree("templates/", "dist/templates/")
"""
src, dst = Path(src), Path(dst)
if dst.exists() and overwrite:
shutil.rmtree(dst)
shutil.copytree(str(src), str(dst))
return dst
def remove(path: str | Path, missing_ok: bool = True) -> None:
"""
Remove a file or directory tree.
Example:
remove("dist/", missing_ok=True)
remove("old_file.txt")
"""
p = Path(path)
if not p.exists():
if not missing_ok:
raise FileNotFoundError(p)
return
if p.is_dir():
shutil.rmtree(p)
else:
p.unlink()
def file_hash(path: str | Path, algorithm: str = "sha256", chunk_size: int = 65536) -> str:
"""
Compute file hash for integrity checking.
Example:
h = file_hash("dist/app.tar.gz")
assert h == expected_sha256
"""
hasher = hashlib.new(algorithm)
with open(path, "rb") as f:
while chunk := f.read(chunk_size):
hasher.update(chunk)
return hasher.hexdigest()
# ─────────────────────────────────────────────────────────────────────────────
# 5. Directory data
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class DirStats:
path: Path
file_count: int
dir_count: int
total_size: int
largest_file: Path | None
largest_size: int
def dir_stats(root: str | Path, pattern: str = "**/*") -> DirStats:
"""
Compute statistics for a directory.
Example:
stats = dir_stats("src")
print(f"{stats.file_count} files, {stats.total_size:,} bytes")
"""
root = Path(root)
files, dirs = 0, 0
total = 0
largest: Path | None = None
largest_size = 0
for p in root.glob(pattern):
if p.is_file():
files += 1
try:
sz = p.stat().st_size
total += sz
if sz > largest_size:
largest_size = sz
largest = p
except OSError:
pass
elif p.is_dir():
dirs += 1
return DirStats(
path=root,
file_count=files,
dir_count=dirs,
total_size=total,
largest_file=largest,
largest_size=largest_size,
)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile, json
print("=== pathlib demo ===")
with tempfile.TemporaryDirectory() as td:
root = Path(td)
# Build a test tree
(root / "src" / "pkg").mkdir(parents=True)
(root / "tests").mkdir()
(root / "data").mkdir()
(root / "src" / "pkg" / "main.py").write_text('print("hello")')
(root / "src" / "pkg" / "utils.py").write_text("# utils")
(root / "tests" / "test_main.py").write_text("def test_it(): pass")
(root / "data" / "sample.csv").write_text("a,b\n1,2\n3,4")
(root / "pyproject.toml").write_text('[project]\nname = "demo"')
print("\n--- find_files ---")
py_files = find_files(root, "**/*.py")
print(f" .py files: {[f.name for f in py_files]}")
print("\n--- write_text_safe (atomic) ---")
out = write_text_safe(root / "output" / "report.txt", "Report content\n")
print(f" written: {out.relative_to(root)}")
print(f" content: {out.read_text()!r}")
print("\n--- open_atomic context manager ---")
cfg = root / "config.json"
with open_atomic(cfg) as f:
json.dump({"debug": True}, f)
print(f" config: {cfg.read_text()}")
print("\n--- dir_stats ---")
stats = dir_stats(root)
print(f" files={stats.file_count}, dirs={stats.dir_count}, size={stats.total_size:,}B")
if stats.largest_file:
print(f" largest: {stats.largest_file.name} ({stats.largest_size}B)")
print("\n--- file_hash ---")
h = file_hash(root / "pyproject.toml")
print(f" sha256: {h[:16]}...")
print("\n--- path components ---")
p = root / "src" / "pkg" / "main.py"
print(f" name={p.name}, stem={p.stem}, suffix={p.suffix}")
print(f" parent={p.parent.relative_to(root)}")
print(f" with_suffix(.pyc)={p.with_suffix('.pyc').name}")
print("\n=== done ===")
For the os.path alternative — Python’s os.path module provides the classic string-based path manipulation API (os.path.join, os.path.exists, os.path.dirname); pathlib.Path provides an object-oriented API where paths are first-class objects supporting / operator concatenation, method chaining, and attribute access — use os.path only when maintaining old code or when path strings are needed for legacy APIs that don’t accept Path objects (most modern APIs accept both), pathlib.Path for all new code because it is more readable, less error-prone, and handles cross-platform separators automatically. For the shutil alternative — shutil provides file system operations that go beyond what pathlib’s Path offers: shutil.copy2 (copy with metadata), shutil.copytree (recursive directory copy), shutil.rmtree (recursive delete), shutil.move, shutil.make_archive; pathlib handles single-file reads, writes, renames, and globs — use pathlib for path manipulation and single-file I/O, shutil for bulk operations, tree copies, and archive creation (they are designed to complement each other). The Claude Skills 360 bundle includes pathlib skill sets covering project_root()/ensure_dir()/temp_path() construction, read_text()/write_text_safe()/write_bytes_safe()/open_atomic() I/O, find_files()/iter_files()/walk_tree() discovery, copy()/copy_tree()/remove()/file_hash() operations, and dir_stats() DirStats summary. Start with the free tier to try file system automation and pathlib pipeline code generation.