pathlib — and the receipt that catches silent data swaps
Reading AI-generated Python, you'll see two completely different ways
of handling files. The old one strings together os.path.join(...),
os.path.exists(...), and an open/with block. The new one is one
object:
from pathlib import Path
log_path = Path("data") / "logs" / "today.log"
if log_path.exists():
text = log_path.read_text()
A Path is a value that represents a filesystem location — it
doesn't matter whether the file exists yet. The / operator joins
segments correctly on every platform (no "a" + "/" + "b" string
mashing, which breaks on Windows). .read_text() opens, reads, and
closes in one call; .write_text("...") is its mirror (it
overwrites — no append, no warning); .read_bytes() gives you the
raw bytes for binary files. The with block still matters when
you're streaming line by line — but for "load the whole thing," the
Path methods are the move.
Inspecting a path without touching disk
Four attributes AI code leans on constantly:
p.name— the last segment:"q1.csv"p.suffix— the dot-extension:".csv"p.stem— the name minus the extension:"q1"p.parent— everything before the last/
None of these touch the filesystem; they're string surgery on the
path itself. p.exists(), by contrast, actually checks disk.
The manifest — a receipt for your dataset
Now the part AI never writes on its own. Here's a failure mode that
produces no error message anywhere: a teammate regenerates eval.csv
with a "small cleanup", your evaluation script happily reads the new
file under the old name, and your model's accuracy jumps four points.
Two weeks later someone asks which rows you evaluated on, and nobody
knows. The file changed and nothing noticed. Files don't version
themselves.
The fix is old, boring, and beautiful: a manifest — a small
record, kept next to the data, that says this exact file, with this
many rows and this checksum, is what we mean by eval.csv. Three
fields do most of the work:
file— which file this entry describes (path.name).rows— how many records. For a CSV that's the newline count minus one, because the first line is the header, not data. For JSONL — one JSON object per line, the format fine-tuning data and eval logs ship in — there's no header: newline count is the record count.sha256— a checksum of the raw bytes, via the stdlib'shashlib.hashlib.sha256(data)returns a hash object;.hexdigest()turns it into a hex fingerprint string you can store and compare. Change one character anywhere in the file and the fingerprint changes completely — the first 8-12 characters are plenty for catching swaps.
The editor on the right builds one real entry: read_bytes() gets
the contents, sha256(...).hexdigest()[:12] fingerprints them, and
the entry itself is dumped as JSON — the config-shaped blob
json.dumps (and its file-writing sibling json.dump) exists for.
Where AI specifically gets this wrong
One: old-style path mashing. os.path.join("a", "b") and
f"{folder}/{file}" both work until they don't. Every one is a
Path("a") / "b" waiting to happen. Replace them.
Two: hashing the wrong thing. hashlib.sha256(str(path).encode())
hashes the filename, not the contents. The check passes forever, no
matter what's in the file — a guardrail made of paint. Hash
read_bytes(), always.
Three: writing the manifest and never reading it. Cursor will
happily generate build_manifest.py and stop. A manifest you never
verify against is a diary, not an alarm. The verify step — re-measure,
compare, complain — is the entire point, and it's the checkpoint at
the end of this lesson.
Run the editor. One file in, one receipt out — twelve hex characters that will tattle on any future edit.