#16 PR A: bucket query client and bucket_conversions - #72
Merged
Merged
Conversation
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
There was a problem hiding this comment.
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.
… 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pyquery_buckets(),iter_query_buckets(), anditer_query_buckets_stream(). They take the sameQueryParamsas the three sample methods and keep their exact contracts (D1).sample_status_filteris refused with aValueErrorbefore any RPC (D2).useSerializedColumnsis forcedFalse.excludeColumnMetadatais passed through (D3).QueryBucketsApiResultexposes.data_bucketsand.next_page_token, plus.to_dataframes(). It deliberately has no.to_dataframe()._iter_stream()(D9). The sample stream's yielded error texts are unchanged, and the existing tests pass untouched.limitcounts buckets on this path, and the "future release" and seam notes are rewritten.bucket_conversions.py(new, D4–D8, D10)bucket_column()andbucket_to_data_frame()view a bucket as a one-columncommon.DataFrame.bucket_timestamps()andbucket_values()read its axis and values.trim_bucket():bisect_lefton both bounds, so repeated timestamps are handled;_slice_frame(), so aSamplingClockkeeps its clock form;begin >= end.buckets_by_pv()groups buckets by PV.[analysis]extra:buckets_to_dataframes()returnsdict[pv, DataFrame], andquery_buckets_to_dataframes()addsmax_buckets.validate_data_frame()ortimestamp_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:
NEXT.mdsection.CLAUDE.mdKey Files entries.data_frame_conversionsand its tests.One addition beyond the plan, for review
The plan's D7 lists
enum_idsas the structural attr carried even underexclude_column_metadata. The per-PV frames also carrydimensions,image_descriptors, andschema_idsinattrs, 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 withbisect_leftfor both axis forms, aSamplingClockincluded, 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 intest_bucket_conversions.pyand the new bucket classes intest_query_client.py.ruff check .,ruff format --check .,mypy src/, the cookbook snippet checker, and the release-notes checker are all clean..pyistubs from dp-grpce775244withgrpcio-tools==1.84.0andmypy-protobuf==5.1.0, usinggenerate-python-stubs.yml's flags and import fixup, and ranmypy src/against them. It caught one issue:WhichOneof()is typedstr | None. That is fixed, and the check now passes.🤖 Generated with Claude Code
https://claude.ai/code/session_01CajTMkjkkzeoWXLSgMpn5k