Python Atlas
Standard library & async

Paths and files

Compose paths, manage resources, and update files safely with pathlib.

pathlib.Path represents a filesystem path, not necessarily an existing file. It avoids manual separator handling and composes naturally across Windows and POSIX systems.

Build and inspect paths

from pathlib import Path

root = Path.home() / ".example"
config = root / "settings.toml"

print(config.name)       # settings.toml
print(config.stem)       # settings
print(config.suffix)     # .toml
print(config.parent)

Path.resolve() makes an absolute path and resolves symlinks. That may touch the filesystem and change security semantics; do not use it merely to display a path. Path.expanduser() expands ~, while environment variables require explicit handling.

Read and write text

Always state the encoding for durable text formats:

from pathlib import Path

path = Path("notes.txt")
path.write_text("naïve\n", encoding="utf-8")
text = path.read_text(encoding="utf-8")

For large files, stream rather than loading everything:

from pathlib import Path

def nonempty_lines(path: Path):
    with path.open("r", encoding="utf-8", newline="") as handle:
        for line in handle:
            stripped = line.strip()
            if stripped:
                yield stripped

A context manager closes the file even if parsing fails. newline="" preserves universal-newline information where formats such as CSV need it.

Binary data and buffering

Use "rb"/"wb" or read_bytes()/write_bytes() for bytes. Streaming is essential for large payloads:

from pathlib import Path

def copy_chunks(source: Path, destination: Path) -> None:
    with source.open("rb") as reader, destination.open("wb") as writer:
        while chunk := reader.read(1024 * 1024):
            writer.write(chunk)

For ordinary copies that should preserve metadata, prefer shutil.copy2().

Directory traversal

from pathlib import Path

for path in Path("data").glob("**/*.json"):
    if path.is_file():
        print(path)

glob() is convenient; os.scandir() can be faster for carefully tuned traversal. Directory contents can change at any time. A successful exists() check does not guarantee the next operation will succeed, so perform the operation and handle its exception.

Creating and deleting

cache = Path("var/cache")
cache.mkdir(parents=True, exist_ok=True)

temporary = cache / "stale.tmp"
temporary.unlink(missing_ok=True)

Path.rmdir() removes only empty directories. Use shutil.rmtree() deliberately for recursive deletion. Never derive a deletion target from unvalidated input.

Atomic replacement

Writing directly to a configuration file can leave partial content after a crash. Write a temporary file in the same directory, flush it, and replace the target:

import os
from pathlib import Path

def replace_text(path: Path, content: str) -> None:
    temporary = path.with_suffix(path.suffix + ".tmp")
    with temporary.open("w", encoding="utf-8", newline="\n") as handle:
        handle.write(content)
        handle.flush()
        os.fsync(handle.fileno())
    os.replace(temporary, path)

Replacement is atomic when source and destination are on the same filesystem. This does not by itself coordinate multiple writers; use an application-level lock or single-writer design.

Safe path containment

Joining a user value can escape a root with .. or an absolute path. Resolve both paths and check containment before access:

from pathlib import Path

def contained_path(root: Path, user_value: str) -> Path:
    resolved_root = root.resolve()
    candidate = (resolved_root / user_value).resolve()
    if not candidate.is_relative_to(resolved_root):
        raise ValueError("path escapes the allowed root")
    return candidate

Symlink races require stronger operating-system-specific techniques in hostile environments.

Common mistakes and practice

  • Omitting encodings or assuming path case rules are identical across platforms.
  • Using Path truthiness to test existence (Path objects are always truthy).
  • Checking then acting instead of handling FileNotFoundError, FileExistsError, or PermissionError.
  • Confusing a filename suffix with trustworthy content validation.

Practice: build a streaming merger for UTF-8 .log files. Skip symlinks, write through a temporary file, preserve deterministic filename order, and leave the old output intact if any input cannot be decoded.

LEARNING RECORD

Finish this lesson

Mark it complete when you can explain the main decision without looking.

KNOWLEDGE CHECK

Paths and files

1Which procedure best protects an existing configuration file from a crash during an update?
2A server joins an upload root with a user-supplied relative path. What check addresses both ".." traversal and symlink resolution in ordinary environments?