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:
- Resolve and display the exact target.
- Validate that it is under the intended narrow root.
- Reject an empty path, filesystem root, home directory, or workspace root.
- Prefer a recoverable move or backup when practical.
- 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.