Skip to content

feat: append mode for MetricWriter.open#54

Draft
msto wants to merge 9 commits into
mainfrom
feat/metric-writer-append-mode
Draft

feat: append mode for MetricWriter.open#54
msto wants to merge 9 commits into
mainfrom
feat/metric-writer-append-mode

Conversation

@msto

@msto msto commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #47

This PR adds a mode argument to MetricWriter.open() so callers can append metric rows to an existing delimited file without duplicating the header (e.g. resumable steps, sharded writers, incremental logging, any other append-or-create context).

open(...) call File on disk Result
mode="w" (default) any truncate + header — unchanged
mode="a" missing / empty header + rows (append-or-create)
mode="a" non-empty, first row matches header rows appended, no header
mode="a" non-empty, first row validates as data rows appended, no header
mode="a" non-empty, first row neither matches header nor validates as data ValueError on entry

@msto msto self-assigned this May 29, 2026
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 067270cd-0423-457d-817f-09c7087e653b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@msto
msto force-pushed the docs/readme-refresh branch from cd8d7b6 to 3c69720 Compare May 29, 2026 20:59
Base automatically changed from docs/readme-refresh to main June 8, 2026 20:00
@msto
msto force-pushed the feat/metric-writer-append-mode branch from e63ef72 to 4bd59b7 Compare June 9, 2026 12:11
msto and others added 8 commits June 24, 2026 19:44
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add mode="a" to open(): on a missing/empty file the header is written
(append-or-create); on a non-empty file the existing header is validated
against the metric class's fields and skipped. mode defaults to "w", so
existing callers are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift the read/validate/decide logic into _append_writes_header and
collapse open()'s nested conditional into a single branch on mode.
No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hand-rolled `readline()` + one-shot `csv.reader` + empty-string
check in `_read_existing_header` with `csv.DictReader.fieldnames` — the
symmetric counterpart to the `DictWriter` the writer emits with. It parses the
first row identically to how it was written and natively returns `None` for an
empty file, so the header read needs no manual empty handling.

Also note the `utf-8` (writer) vs `utf-8-sig` (reader) default asymmetry in the
`open()` docstring. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`MetricWriter.open(mode="a")` promises append-or-create: an empty file gets a
fresh header. For a zero-byte `.bz2`/`.xz` file this raised EOFError instead —
the header-validation read handed the empty file to xopen, and the bz2/xz
decompressors reject a truncated stream (plaintext and gzip tolerate it). Check
the file size before decompressing: a zero-byte file of any format has no
header, so we skip the read and write one. Genuine corruption in a non-empty
file still surfaces rather than being silently swallowed.

Cover append-to-empty across all four formats (the previously-broken case), and
pin the documented trailing-newline concatenation behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the try/except stat in _read_existing_header with an explicit
exists()/st_size check, narrow its return type to list[str] | None so
the header comparison is list-to-list, and rename _append_writes_header
to _append_needs_header — it reports whether a header is needed rather
than writing one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@msto
msto force-pushed the feat/metric-writer-append-mode branch from 9339458 to dfbe49f Compare June 24, 2026 23:56
`MetricWriter.open(path, mode="a")` previously raised unless the first row of
an existing file exactly matched the metric's header. It now also accepts a
headerless data file: if the first row validates as a record of the metric
class (via `model_validate`, the same path `MetricReader` uses to parse a row),
rows are appended without injecting a header into the middle of the file.

A `ValueError` is raised only when the first row is neither the expected header
nor a valid record. A row with the wrong number of columns cannot be a record,
so it is rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Append mode for MetricWriter (skip header by default when appending)

1 participant