Skip to main content

File Paths and Filesystem Operations

A path is a location description, not proof that a file exists. Prefer pathlib.Path for ordinary application code because it keeps path construction and filesystem operations in one cross-platform interface.

from pathlib import Path

root = Path("data")
report = root / "reports" / "summary.csv"

if report.is_file():
size = report.stat().st_size

Relative paths are interpreted against the process's current working directory, which is chosen by the caller and may differ from the script's directory. Make that base an explicit configuration or argument.

For a resource intentionally located beside a standalone script:

script_dir = Path(__file__).resolve().parent
template = script_dir / "templates" / "report.txt"

For installed package data, use importlib.resources; a package may not exist as ordinary adjacent files.

Expansion and resolution

configured = Path("~/exports").expanduser()
absolute = configured.resolve()

expanduser() handles the home marker. resolve() makes a path absolute, normalizes it, and resolves symbolic links. These operations have different semantics and should not be treated as interchangeable string cleanup.

Do not use string-prefix checks to prove containment: /safe-backup begins with /safe but is not inside it. Resolve the intended root and target, then use path relationships such as relative_to. Even that check is only one part of a security boundary because links and filesystem state can change between checking and using a path.

Discovery

markdown_files = sorted(root.rglob("*.md"))

Glob result order is not a portable contract; sort when deterministic processing matters. Directory traversal can encounter permission failures, broken links, cycles introduced by link-following, and files that disappear during the scan.

Operations

Path.mkdir, rename, replace, unlink, and rmdir cover common single-path operations. Use shutil.copy2, copytree, move, and rmtree for copying, moving, or removing trees. Their metadata and cross-filesystem behavior differ, so consult the operation's contract instead of assuming a rename or copy is atomic.

Before deleting or overwriting:

  1. Resolve and display the exact target.
  2. Validate that it is under the intended narrow root.
  3. Reject an empty path, filesystem root, home directory, or workspace root.
  4. Prefer a recoverable move or backup when practical.
  5. Expect time-of-check/time-of-use races when other processes can change it.

An existence check followed by an operation is not a guarantee. Prefer the operation itself and handle its specific exception. Avoid except OSError unless all of its causes truly share the same recovery policy.

Source