Skip to content

#16 PR A: bucket query client and bucket_conversions - #72

Merged
craigmcchesney merged 3 commits into
mainfrom
feat/16-bucket-query-client
Oct 1, 2026
Merged

craigmcchesney merged 3 commits into
mainfrom
feat/16-bucket-query-client

Conversation

@craigmcchesney

Copy link
Copy Markdown
Collaborator

Refs #16. This is PR A of the two-PR plan in plan/tickets/16/plan.md (merged in #71): the client, the conversions, and unit tests. PR B will add the live tests and the cookbook, and will close #16.

What's in it

query_client.py

  • New methods query_buckets(), iter_query_buckets(), and iter_query_buckets_stream(). They take the same QueryParams as the three sample methods and keep their exact contracts (D1).
  • A sample_status_filter is refused with a ValueError before any RPC (D2).
  • useSerializedColumns is forced False. excludeColumnMetadata is passed through (D3).
  • QueryBucketsApiResult exposes .data_buckets and .next_page_token, plus .to_dataframes(). It deliberately has no .to_dataframe().
  • Both stream senders now go through a private _iter_stream() (D9). The sample stream's yielded error texts are unchanged, and the existing tests pass untouched.
  • Docstrings updated: limit counts buckets on this path, and the "future release" and seam notes are rewritten.

bucket_conversions.py (new, D4–D8, D10)

  • bucket_column() and bucket_to_data_frame() view a bucket as a one-column common.DataFrame.
  • bucket_timestamps() and bucket_values() read its axis and values.
  • trim_bucket():
    • exact and half-open, using bisect_left on both bounds, so repeated timestamps are handled;
    • reuses _slice_frame(), so a SamplingClock keeps its clock form;
    • raises on a decreasing axis, a serialized bucket, or begin >= end.
  • buckets_by_pv() groups buckets by PV.
  • [analysis] extra: buckets_to_dataframes() returns dict[pv, DataFrame], and query_buckets_to_dataframes() adds max_buckets.
  • Read-side checks only: nothing calls validate_data_frame() or timestamp_count(). Before slicing, trim_bucket() runs the read path's alignment check, so an array column with absent dims raises instead of being cut in the wrong-sized blocks.

Docs:

One addition beyond the plan, for review

The plan's D7 lists enum_ids as the structural attr carried even under exclude_column_metadata. The per-PV frames also carry dimensions, image_descriptors, and schema_ids in attrs, keyed by PV. Without them, an array, image, or struct column's object cells can't be interpreted, and the consistency check already guarantees they are uniform across a PV's buckets. Dropping them would repeat the #6 "carry it, then lose it" defect. This is easy to remove if you'd rather keep strictly to the plan.

trim_bucket() finds both bounds with bisect_left for both axis forms, a SamplingClock included, rather than computing them arithmetically for a clock. The plan allowed either (it said "or"), and the results are identical.

Verification

  • pytest tests/unit/: 960 passed. This includes 53 new tests in test_bucket_conversions.py and the new bucket classes in test_query_client.py.
  • ruff check ., ruff format --check ., mypy src/, the cookbook snippet checker, and the release-notes checker are all clean.
  • Typed-stub check (T9): generated .pyi stubs from dp-grpc e775244 with grpcio-tools==1.84.0 and mypy-protobuf==5.1.0, using generate-python-stubs.yml's flags and import fixup, and ran mypy src/ against them. It caught one issue: WhichOneof() is typed str | None. That is fixed, and the check now passes.
  • Not run live. That is PR B.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CajTMkjkkzeoWXLSgMpn5k

Wrap queryBuckets / queryBucketsStream on QueryClient (query_buckets,
iter_query_buckets, iter_query_buckets_stream) over the existing
QueryParams, refusing sample_status_filter before any RPC.  Both stream
senders now share _iter_stream().

Add bucket_conversions: a bucket viewed as a one-column DataFrame, exact
half-open trim_bucket() reusing _slice_frame() with read-side checks only,
grouping by PV, and per-PV pandas frames with attrs rebuilt after concat.

Refs #16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CajTMkjkkzeoWXLSgMpn5k
Copilot AI balanced review requested due to automatic review settings October 1, 2026 16:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation matches the planned PR A scope and has thorough unit coverage for request handling, conversion behavior, and failure cases.

Review effort: Balanced
Findings: None

What changed in this PR

Adds bucket-oriented v2 query support and conversions for whole stored DataBucket records.

Changes:

  • Adds unary, paged, and streaming bucket-query APIs.
  • Adds pure-Python and pandas bucket conversions with exact trimming.
  • Adds comprehensive unit tests and documentation.
File Description
src/​dp_python_lib/​client/​query_client.py Implements bucket-query APIs and shared stream handling.
src/​dp_python_lib/​client/​bucket_conversions.py Adds bucket reading, trimming, grouping, and pandas assembly.
src/​dp_python_lib/​client/​data_frame_conversions.py Updates bucket-query documentation.
src/​dp_python_lib/​client/​__init__.py Exports QueryBucketsApiResult.
tests/​unit/​test_query_client.py Tests bucket requests, paging, streaming, and errors.
tests/​unit/​test_bucket_conversions.py Tests all bucket conversion paths and edge cases.
tests/​unit/​test_data_frame_conversions.py Updates conversion test documentation.
doc/​release-notes/​NEXT.md Documents the new public functionality.
CLAUDE.md Records bucket-query architecture and testing details.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

craigmcchesney and others added 2 commits October 1, 2026 11:24
… review)

buckets_to_dataframes() refused a SerializedDataColumn bucket before
trimming, across every PV, so one serialized bucket anywhere in a result
-- even wholly outside time_range -- blocked the conversion of every
other PV.  With time_range, buckets with no sample in the range are now
dropped first (they would be trimmed away anyway), and only an
overlapping serialized bucket is refused.  Without time_range nothing
changes: every bucket is kept, so a serialized one still raises.

Also documents that a legacy DataColumn bypasses the consistency check
(it has no column-level type), so its buckets widen through pandas
rather than raising, and pins that with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CajTMkjkkzeoWXLSgMpn5k
…16 PR A review)

- A bucket carrying no ColumnMetadata (as after a query that set
  excludeColumnMetadata, which a page's to_dataframes() cannot know) is
  now reported as column_metadata=None in attrs["buckets"], not as
  column_metadata_dict()'s empty containers, which read as metadata the
  server returned.  The per-PV summary is set only when every bucket
  carries metadata and it all agrees.
- attrs["buckets"] records each bucket's stored column_name, since the
  frame's column is always renamed to the PV.
- trim_bucket() and time_range= errors name the caller's own parameter
  instead of a "time_range" trim_bucket() was never passed.
- The kind/structure mismatch error renders readably, e.g.
  "EnumColumn (enumId 'a:v1')", instead of a raw tuple.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CajTMkjkkzeoWXLSgMpn5k
@craigmcchesney
craigmcchesney merged commit 637fefa into main Oct 1, 2026
6 checks passed
@craigmcchesney
craigmcchesney deleted the feat/16-bucket-query-client branch October 1, 2026 17:34
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.

interface to v2 bucket-oriented query API (queryBuckets / queryBucketsStream)

2 participants