Add column groups support - #345
Conversation
| feature; the client must be constructed with ``preview=True`` to use them. | ||
| :param column_groups: A dictionary mapping column names within `data` to the qualified title of the group the | ||
| column should be placed in. Column groups are a preview feature; the client must be constructed with | ||
| ``preview=True`` to use them. |
There was a problem hiding this comment.
Column groups are a preview feature; the client must be constructed with
preview=Trueto use them
We are aiming to remove api-preview flag very soon, perhaps we could avoid this sentence.
Make the block model column-group API reflect the service instead of adding a bare-title abstraction layer: - Callers key `data` by each column's exact upload heading (bare for ungrouped, qualified `group▸leaf` for grouped); the SDK uploads the table verbatim and never renames columns. - `column_groups` maps a new column's qualified heading → group, or an existing column's current qualified title → new group (`""` ungroups). - `update_columns` / `delete_columns` use the column's current stored title (qualified if grouped, bare otherwise). Removed the hidden latest-version fetch that translated bare → qualified titles. - Add opt-in `qualified_heading` and `qualify_headings` helpers (and a `Table.rename_columns` protocol method) to build qualified headings from bare-titled data, re-exported from the package root. - Update docstrings and rewrite/extend tests for the new contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rohancrookbain-seequent
left a comment
There was a problem hiding this comment.
Partially reviewed; will have a more complete look soon!
There was a problem hiding this comment.
Pull request overview
Adds column-group lifecycle and column assignment/move support to the evo.blockmodels high-level client, along with new public data models and helpers for qualified column headings (preview-gated via API-Preview: opt-in).
Changes:
- Adds group CRUD/update support via
BlockModelAPIClient.update_groups(...)and wiresgroupsinto the update payload. - Adds
column_groupssupport for new columns and for moving/ungrouping existing columns during data re-upload. - Introduces qualified-title utilities and Version group-lookup helpers, plus expanded test coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/evo-blockmodels/tests/test_update.py | Adds tests for grouped column creation, group moves/ungrouping via re-upload, and update_groups behavior. |
| packages/evo-blockmodels/tests/test_group_helpers.py | New tests covering Version group helper methods and extra-field validation and heading qualification helpers. |
| packages/evo-blockmodels/src/evo/blockmodels/data.py | Adds group input models, qualified-title helpers, and Version group bridge helpers. |
| packages/evo-blockmodels/src/evo/blockmodels/client.py | Implements group update endpoint support and column group assignment/move logic with validation and payload shaping. |
| packages/evo-blockmodels/src/evo/blockmodels/_types.py | Extends Table protocol with rename_columns(...) to support heading qualification helper. |
| packages/evo-blockmodels/src/evo/blockmodels/init.py | Re-exports qualified-title helpers and separator at package root. |
| packages/evo-blockmodels/pyproject.toml | Bumps package version to 0.7.0. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
There was a problem hiding this comment.
nitpick: Shouldn't this be at the end of the file?
There was a problem hiding this comment.
🟡 Changes recommended
Preview-only group functionality is documented but not consistently enforced in the client (and one test currently exercises group updates without preview opt-in), which can lead to runtime API failures and contract mismatches.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
packages/evo-blockmodels/src/evo/blockmodels/client.py:912
- Same as
_add_new_columns():_update_columns()can receive a non-emptycolumn_groupsmapping whenpreview=False, but will send the payload without the preview opt-in header. Guard early to match the documented preview contract and avoid hard-to-debug API errors.
if column_groups is None:
column_groups = {}
data_type_map = {name: data_type for name, data_type in zip(schema.names, schema.types)}
packages/evo-blockmodels/tests/test_update.py:967
- After switching to the preview client for ungrouping, this assertion should also include the preview opt-in header; otherwise the test will accept behavior that contradicts the preview-only contract for column groups.
method=RequestMethod.PATCH,
path=f"{self.base_path}/block-models/{BM_UUID}/blocks",
body=expected_update_body.model_dump(mode="json", exclude_unset=True),
headers=DEFAULT_EXPECTED_HEADERS,
- Files reviewed: 7/7 changed files
- Comments generated: 5
- Review effort level: Lite
| if column_groups is None: | ||
| column_groups = {} | ||
| new_column_names = {name for name in schema.names if name not in _GEOMETRY_COLUMNS} | ||
| unknown_unit_columns = set(units) - new_column_names | ||
| if unknown_unit_columns: | ||
| raise MissingColumnInTable(f"units reference columns that are not being added: {unknown_unit_columns}") | ||
| unknown_tag_columns = set(tags) - new_column_names | ||
| if unknown_tag_columns: | ||
| raise MissingColumnInTable(f"tags reference columns that are not being added: {unknown_tag_columns}") | ||
| unknown_group_columns = set(column_groups) - new_column_names | ||
| if unknown_group_columns: | ||
| raise MissingColumnInTable( | ||
| f"column_groups reference columns that are not being added: {unknown_group_columns}" | ||
| ) |
| A column's group is metadata, so it can be moved (or ungrouped) here without re-uploading its | ||
| data. Address the column by the title the service currently stores it under: its qualified title | ||
| (``group▸title``) if it is currently grouped, or its plain title if it is not. Set | ||
| ``ColumnMetadataUpdate(group=...)`` to the target group's qualified title (or ``""`` to ungroup). | ||
|
|
| if not new and not update and not delete: | ||
| raise ValueError("At least one of 'new', 'update' or 'delete' must be provided.") |
| job_response=JobResponse(job_status=JobStatus.COMPLETE, payload=UPDATED_VERSION), | ||
| ) | ||
| ) | ||
| await self.bms_client_without_cache.update_column_metadata( |
| if __name__ == "__main__": | ||
| unittest.main() | ||
|
|
Description
Adds full column-group support to the high-level
BlockModelAPIClientfor the block model service. Previously the SDK could only read groups off a version; callers had to drop down to the generated endpoint layer to create, assign, or modify them. This change exposes the complete group lifecycle — create, assign, read, update and delete — through the public client and data model.Column groups are a preview feature: the client must be constructed with
preview=Trueto use them, consistent with column tags. The design goal is for the SDK to mirror the service: where the service addresses columns by their qualified (group▸title) title, the caller supplies that same qualified title. The SDK does not invent a plain-title abstraction, rename your data, or perform hidden version lookups — this avoids ambiguity when the same title is reused across groups.Write path
BlockModelAPIClient.update_groups(bm_id, new=, update=, delete=, comment=). Create groups withGroupDefinition(title, parent group, missing-column policy, tags, hidden flag); rename, re-parent, change policy, replace tags or toggle visibility withGroupMetadataUpdate; delete by title. Any combination can be applied in a single call.column_groupsparameter onadd_new_columns,add_new_subblocked_columns,update_block_model_columnsandupdate_subblocked_columnsmaps a new column's qualified title (its key indata, e.g.Assays▸Cu) → the group it belongs to (Assays). The SDK derives the title by stripping the group prefix.update_column_metadatawithColumnMetadataUpdate(group=...). A column's group is metadata, so moving it needs no data upload. Address the column by its current qualified title and setgroupto the new group's qualified title (or""to ungroup).Column titles
The caller keys
databy each column's title: a plain title for an ungrouped column, or the qualifiedgroup▸titlefor a grouped one. The SDK uploads the table verbatim and never renames columns. Two opt-in helpers are provided to build these titles from plain-titled data:get_qualified_title(group, title)— builds a single qualified title.qualify_column_titles(data, {title: group})— renames a plain-titled table and returns(renamed_table, column_groups)ready to pass straight to the client.Read path
Version/ListingVersiongain bridge helpers so a caller who wrote a group by title can find it again on the returned version without hand-rolling a lookup:group_by_uuid,group_for_column,qualified_group_titleandgroup_by_qualified_title.Public API
evo.blockmodels.data:GroupDefinitionGroupMetadataUpdateMissingColumnPolicyQUALIFIED_TITLE_SEPARATORget_qualified_titleandqualify_column_titlesget_qualified_title,qualify_column_titlesandQUALIFIED_TITLE_SEPARATORare also re-exported from theevo.blockmodelspackage root.Notes
update_columns/delete_columns) are addressed by the exact title the service currently stores them under: the qualifiedgroup▸titleif grouped, or the plain title if not. There is no hidden lookup — the caller states the current reference.update_column_metadata;ColumnMetadataUpdateaccepts agroupfield (""ungroups). Thecolumn_groupsparameter on the column methods only groups new columns.create_block_model(none exist yet); the documented flow is create the model →update_groups(new=...)→ add columns withcolumn_groups.GroupDefinition,GroupMetadataUpdate,ColumnMetadataUpdate) reject unknown fields to catch typos early.get_qualified_title/qualify_column_titleshelpers, preview-header propagation, input validation, and all bridge helpers.Checklist