Skip to content

Code-smell quick wins: dedupe streaming helpers, dead-code cleanup, project retry - #20

Merged
Doug Guthrie (dpguthrie) merged 8 commits into
mainfrom
chore/streaming-quick-wins
Jun 8, 2026
Merged

Code-smell quick wins: dedupe streaming helpers, dead-code cleanup, project retry#20
Doug Guthrie (dpguthrie) merged 8 commits into
mainfrom
chore/streaming-quick-wins

Conversation

@dpguthrie

Copy link
Copy Markdown
Collaborator

First, low-risk batch from the code-smell audit. Strictly behavior-preserving (one bug fix called out below) — no existing test was modified; the suite went from 288 → 293 (only additions).

Changes

R2 + R3 — Hoist duplicated streaming helpers (braintrust_migrate/streaming_utils.py)

The three streaming migrators each carried byte-for-byte copies of _is_http_413, _approx_event_size_bytes, _count_attachment_refs, the 413 constant, and a near-identical _dump_oversize_event_summary (differing only by output dir, filename prefix, dest-id key, and log wording). Moved to single shared functions; each migrator now calls them. The dump function takes per-resource out_dir/filename_prefix/event_label/dest_id_field so the exact summary schema and log messages are preserved. Dropped the now-unused httpx import from datasets/experiments. Net −19 lines.

R12 — Dead code + bogus timestamp (resources/base.py)

  • Deleted _get_client_resource_attr and _handle_api_response_to_list (zero callers anywhere).
  • Bug fix: record_failure stored failed_at = str(Path(__file__).stat().st_mtime) — base.py's own mtime, a static value identical for every failure. Now datetime.now(UTC).isoformat().

R11 — Route project list/create through with_retry (client.py)

list_projects (per page) and create_project called raw_request directly, bypassing the adaptive retry every other call uses. Wrapped both. Happy-path identical; only transient-failure behavior improves.

Test deviation report (what the audit asked for)

  • Nothing needed modifying. All pre-existing tests pass unchanged — the strongest signal these are behavior-preserving.
  • Added tests/unit/test_streaming_helpers.py (5 tests) to pin the newly-shared helpers, which were previously only covered indirectly through the migration paths.
  • The one intentional behavior delta (failed_at) was uncovered by any test and is a clear fix.

Deliberately deferred

  • R5 (central streaming-config value object) — changing the headroom-ratio fallback default is a behavior delta, so it belongs in its own change.
  • R12's CLI part (run_dry_run/validate consolidation) — more CLI-test-sensitive; separate.
  • The structural refactors (R6 param-object for stream_btql_sorted_events_buffered, R4 shared base, R1 logs onto the shared loop) are the high-value/medium-risk core and will need test changes (monkeypatched writer names, EventsStreamState JSON keys, progress payloads) — tracked for a follow-up.

🤖 Generated with Claude Code

Doug Guthrie (dpguthrie) and others added 7 commits June 8, 2026 14:31
The three streaming migrators (logs, datasets, experiments) each carried
byte-for-byte copies of _is_http_413, _approx_event_size_bytes,
_count_attachment_refs, the HTTP 413 constant, and a near-identical
_dump_oversize_event_summary method (differing only by output dir, filename
prefix, dest-id key name, and log wording).

Move these to single shared functions in streaming_utils
(is_http_413, approx_event_size_bytes, count_attachment_refs,
dump_oversize_event_summary) and have all three migrators call them. The
dump function takes the per-resource out_dir / filename_prefix / event_label
/ dest_id_field so behavior (including the exact summary schema and log
messages) is preserved. Drops the now-unused httpx import from datasets and
experiments.

Behavior-preserving refactor: all existing tests pass unchanged. Adds
test_streaming_helpers.py to pin the shared helpers directly (previously
only covered indirectly through the migration paths).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove _get_client_resource_attr and _handle_api_response_to_list from
  ResourceMigrator; both had zero callers anywhere in the package.
- record_failure stored failed_at as str(Path(__file__).stat().st_mtime) —
  base.py's own mtime, a static value identical for every failure. Replace
  with datetime.now(UTC).isoformat() so the recorded time is real.

No test referenced these; full suite passes unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
list_projects (per-page) and create_project called raw_request directly,
bypassing the adaptive retry/backoff that every other API call in the tool
uses. Wrap both in with_retry so transient 429/5xx during project discovery
and creation are retried consistently.

Happy-path behavior is unchanged (with_retry calls the request once on
success); only transient-failure behavior improves. Project methods are
mocked wholesale in tests, so the suite passes unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three streaming migrators each re-read byte-batching config with an
identical try/except, and each computed self._insert_max_bytes from
insert_max_request_bytes * insert_request_headroom_ratio — a value that was
never read anywhere (dead), and whose fallback default (0.5) disagreed with
the canonical MigrationConfig default (0.75).

- Delete the dead _insert_max_bytes computation from all three migrators
  (removes the drift entirely rather than "fixing" an unused default).
- Add StreamingConfig.resolve(source, dest) in streaming_utils as the single
  place that resolves the flush/fetch knobs, with module-constant defaults
  (STREAMING_FLUSH_MAX_ROWS/BYTES, STREAMING_MAX_EVENT_BYTES,
  STREAMING_EVENT_FETCH_GROUP_SIZE). The migrator ClassVars now alias those
  constants.

Test mechanism change (not a product-behavior change): the three
resume-after-insert-failure tests forced per-row flushing by monkeypatching
the SDK_FLUSH_MAX_ROWS ClassVar; they now set events_flush_max_rows=1 on a
MigrationConfig — the actual user-facing lever — which is both cleaner and
lets resolve use plain module-constant defaults. Full suite: 293 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…res (R8)

_get_source_user_email, _find_dest_user_id_by_email, and
_invite_user_to_dest_org were duplicated near-verbatim across the ACL and
group migrators (only the with_retry op-name strings differed), and each
swallowed every exception and cached None permanently — so a transient
auth/network/5xx failure became a permanent "no such user".

Extract a single UserResolver (braintrust_migrate/user_resolver.py) used by
both migrators. with_retry re-raises the original exception, so a 404
propagates as httpx.HTTPStatusError(404): the resolver now caches the
negative result only on a genuine 404, and on any other failure logs a
warning and does NOT cache, leaving a later attempt able to succeed.

The duplicated methods and their three per-migrator caches are removed; both
migrators delegate to the resolver. Op-name suffix keeps telemetry
distinguishable per caller. Unit suite (293) and the ACL/group user-mapping
integration flow tests pass; the 404-vs-transient change only affects the
error path (happy path is unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
streaming_pipeline (env MIGRATION_STREAMING_PIPELINE) was defined, parsed,
and constructed but never read anywhere in the product code — the streaming
loop is strictly sequential (fetch page, insert, fetch next). The README,
however, documented it as a working, default-on "pipelined event streaming"
feature ("prefetch the next BTQL page while inserting the current batch").

Remove the dead config field/env-parse/constructor arg, and correct the
README (env table, the Pipelined Event Streaming section, the parallelization
diagram, tuning tips, and example .env blocks) so it no longer claims a
feature that doesn't exist. Drop the streaming_pipeline assertions from the
unit config tests and the field from the live e2e scenario matrix (the two
scenarios remain distinct via their concurrency settings; renamed to
concurrent/sequential).

No runtime behavior change — the knob never affected anything. Implementing
real pipelined prefetch remains possible future work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dataset and experiment migrators each defined four progress-hook lambdas
(on_fetch/on_page/on_insert/on_done) that built byte-identical ~95-line dicts,
differing only by the resource label and the source/dest id-field keys.

Add build_stream_progress(phase, info, state, *, resource, id_fields) and
make_stream_progress_hooks(...) in streaming_utils, and have both migrators
pass a single descriptor instead of the four hand-written dicts. The emitted
payloads are unchanged (per-phase sourcing preserved: fetch/page/done pull
totals from the loop info; insert pulls committed totals from state; cursor
truncation and the resource/id-field keys are identical), so the CLI progress
display contract is preserved.

Scope: datasets + experiments (which share the buffered loop). Logs keeps its
own progress functions for now — its payload shape differs and it folds in
with R1 (logs onto the shared loop).

Adds build_stream_progress / make_stream_progress_hooks unit tests. Suite: 294.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- datasets.py: `_on_single_413` referenced an unbound `source_dataset_id`
  (only a comprehension/loop variable elsewhere), so a single dataset event
  hitting HTTP 413 raised NameError — aborting the migration and skipping the
  oversize diagnostic instead of writing it and propagating the 413. Bind
  `source_dataset_id = event.get("dataset_id")` in the closure, matching the
  experiments migrator. (Latent bug present since before this branch; fixed
  here since the refactor touches this code.)
- streaming_utils.py: drop the vestigial `_p=progress` default-arg in
  make_stream_progress_hooks' lambda — it's the loop-capture idiom, but there
  is no loop and `progress` is a stable captured param.

Adds test_dataset_oversize_413.py, which drives a single-event 413 through the
dataset streaming path (fails with NameError without the fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dpguthrie
Doug Guthrie (dpguthrie) merged commit 109839b into main Jun 8, 2026
1 check passed
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.

1 participant