bur-cli
BUR-5Pure helpers: filenames, retention, config
Description
The three dependency-free, unit-tested modules everything else builds on.
naming.py: build and parse<app>-<UTC-ISO-basic>.dump.age(and the paired…globals.sql.age). Anchor the parse on the timestamp pattern (-\d{8}T\d{6}Z\.…$), never a naive split on-. Hyphenated app names likealbum-reviewsmust round-trip.retention.py:plan_retention(names, keep) -> (keep, delete). Sort by parsed timestamp, keep the newest N, delete the rest. Keep-last-N only, with no daily/monthly tiers, so it collapses to a sort and a slice.config.py: load and validate the globalbur.tomlviatomllib:backup_root,retention_keep(default 10),age_recipients,r2_remote,r2_bucket,overdue_hours,postgres_image_match. Path from--configor$BUR_CONFIG.
Acceptance
-
parse(build(app, dt)) == (app, dt)including multi-hyphen names; malformed names raise a typed error; timestamps are UTC and sort lexically. - retention keeps exactly the newest N and never deletes the most recent; edges covered (empty, fewer than N, exactly N, ties).
- a valid config loads with defaults applied; missing or invalid fields error by name; empty
age_recipientsis rejected.
Sub-tickets
0No sub-tickets yet.
Links
No links.
Summary
naming.py: builds and parses the backup filenames, likeapp-20260603T174800Z.dump.ageand the matching.globals.sql.age. It reads the name off the timestamp pattern instead of splitting on-, so names with hyphens in them likealbum-reviewsstill come back out the same. Timestamps are UTC down to the second so the names sort by time. A bad name raisesInvalidBackupName.retention.py:plan_retention(names, keep)sorts newest first and gives you back what to keep and what to delete. If two names have the same timestamp it falls back to sorting on the name so it's always the same answer.config.py: readsbur.tomlwithtomllib, taking the path from the arg or$BUR_CONFIG. You have to setbackup_rootand at least oneage_recipients. The rest have defaults:retention_keep10,overdue_hours24,postgres_image_match"postgres", and the R2 fields empty. A missing, wrong-typed, or unknown field raisesConfigErrorand tells you which field. A bool won't pass where a number's expected.> uv run ruff check All checks passed! > uv run mypy Success: no issues found in 8 source files > uv run pytest .................................. [100%] 34 passed in 0.04sCode: https://github.com/jmmd2000/bur-cli/commit/87b57b51e97fbe5f74834abc8f93f4742d69edcd