From e7e21021a10dee47b1779a15d2bd0f00c90cc253 Mon Sep 17 00:00:00 2001 From: Sam Minot Date: Mon, 21 Sep 2026 15:47:59 -0700 Subject: [PATCH] Fix sheets row cell values to round-trip scalars RowInsertValues, RowUpdateValues and SheetQueryResponse.rows were generated with a second level of additional_properties, so a scalar cell value could not be represented in either direction: parsing a row of scalars raised ValueError, and serializing one raised AttributeError. This made get_sheet_data, query_sheet_data, query_namespace_data, insert_sheet_data and update_sheet_data unusable against any sheet holding data. The map values and row cells are free-form JSON, matching the Map and List> the API actually serves, so these models now carry a single level of additional_properties like Sample.metadata. The source spec still declares these as `type: object`; until that is corrected the next schema sync will reintroduce the nested models. Fixes #29 Co-Authored-By: Claude Opus 5 --- cirro_api_client/v1/models/__init__.py | 6 --- .../v1/models/row_insert_values.py | 25 +++------- .../row_insert_values_additional_property.py | 47 ------------------- .../v1/models/row_update_values.py | 25 +++------- .../row_update_values_additional_property.py | 47 ------------------- .../v1/models/sheet_query_response.py | 20 ++------ .../models/sheet_query_response_rows_item.py | 47 ------------------- config.yml | 2 - tests/test_client.py | 39 +++++++++++++++ 9 files changed, 56 insertions(+), 202 deletions(-) delete mode 100644 cirro_api_client/v1/models/row_insert_values_additional_property.py delete mode 100644 cirro_api_client/v1/models/row_update_values_additional_property.py delete mode 100644 cirro_api_client/v1/models/sheet_query_response_rows_item.py diff --git a/cirro_api_client/v1/models/__init__.py b/cirro_api_client/v1/models/__init__.py index 924a823..4cae86d 100644 --- a/cirro_api_client/v1/models/__init__.py +++ b/cirro_api_client/v1/models/__init__.py @@ -188,10 +188,8 @@ from .resources_info import ResourcesInfo from .row_insert import RowInsert from .row_insert_values import RowInsertValues -from .row_insert_values_additional_property import RowInsertValuesAdditionalProperty from .row_update import RowUpdate from .row_update_values import RowUpdateValues -from .row_update_values_additional_property import RowUpdateValuesAdditionalProperty from .run_analysis_request import RunAnalysisRequest from .run_analysis_request_params import RunAnalysisRequestParams from .run_analysis_request_source_sample_files_map import RunAnalysisRequestSourceSampleFilesMap @@ -223,7 +221,6 @@ from .sheet_job_type import SheetJobType from .sheet_query_request import SheetQueryRequest from .sheet_query_response import SheetQueryResponse -from .sheet_query_response_rows_item import SheetQueryResponseRowsItem from .sheet_sort import SheetSort from .sheet_type import SheetType from .sheet_update_response import SheetUpdateResponse @@ -454,10 +451,8 @@ "ResourcesInfo", "RowInsert", "RowInsertValues", - "RowInsertValuesAdditionalProperty", "RowUpdate", "RowUpdateValues", - "RowUpdateValuesAdditionalProperty", "RunAnalysisRequest", "RunAnalysisRequestParams", "RunAnalysisRequestSourceSampleFilesMap", @@ -489,7 +484,6 @@ "SheetJobType", "SheetQueryRequest", "SheetQueryResponse", - "SheetQueryResponseRowsItem", "SheetSort", "SheetType", "SheetUpdateResponse", diff --git a/cirro_api_client/v1/models/row_insert_values.py b/cirro_api_client/v1/models/row_insert_values.py index 6c668c7..43bb08c 100644 --- a/cirro_api_client/v1/models/row_insert_values.py +++ b/cirro_api_client/v1/models/row_insert_values.py @@ -1,15 +1,11 @@ from __future__ import annotations from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field -if TYPE_CHECKING: - from ..models.row_insert_values_additional_property import RowInsertValuesAdditionalProperty - - T = TypeVar("T", bound="RowInsertValues") @@ -22,40 +18,31 @@ class RowInsertValues: """ - additional_properties: dict[str, RowInsertValuesAdditionalProperty] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: field_dict: dict[str, Any] = {} - for prop_name, prop in self.additional_properties.items(): - field_dict[prop_name] = prop.to_dict() + field_dict.update(self.additional_properties) return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.row_insert_values_additional_property import RowInsertValuesAdditionalProperty - d = dict(src_dict) row_insert_values = cls() - additional_properties = {} - for prop_name, prop_dict in d.items(): - additional_property = RowInsertValuesAdditionalProperty.from_dict(prop_dict) - - additional_properties[prop_name] = additional_property - - row_insert_values.additional_properties = additional_properties + row_insert_values.additional_properties = d return row_insert_values @property def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) - def __getitem__(self, key: str) -> RowInsertValuesAdditionalProperty: + def __getitem__(self, key: str) -> Any: return self.additional_properties[key] - def __setitem__(self, key: str, value: RowInsertValuesAdditionalProperty) -> None: + def __setitem__(self, key: str, value: Any) -> None: self.additional_properties[key] = value def __delitem__(self, key: str) -> None: diff --git a/cirro_api_client/v1/models/row_insert_values_additional_property.py b/cirro_api_client/v1/models/row_insert_values_additional_property.py deleted file mode 100644 index 1d08764..0000000 --- a/cirro_api_client/v1/models/row_insert_values_additional_property.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -T = TypeVar("T", bound="RowInsertValuesAdditionalProperty") - - -@_attrs_define -class RowInsertValuesAdditionalProperty: - """ """ - - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - row_insert_values_additional_property = cls() - - row_insert_values_additional_property.additional_properties = d - return row_insert_values_additional_property - - @property - def additional_keys(self) -> list[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/cirro_api_client/v1/models/row_update_values.py b/cirro_api_client/v1/models/row_update_values.py index 0819242..dccf972 100644 --- a/cirro_api_client/v1/models/row_update_values.py +++ b/cirro_api_client/v1/models/row_update_values.py @@ -1,15 +1,11 @@ from __future__ import annotations from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field -if TYPE_CHECKING: - from ..models.row_update_values_additional_property import RowUpdateValuesAdditionalProperty - - T = TypeVar("T", bound="RowUpdateValues") @@ -23,40 +19,31 @@ class RowUpdateValues: """ - additional_properties: dict[str, RowUpdateValuesAdditionalProperty] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: field_dict: dict[str, Any] = {} - for prop_name, prop in self.additional_properties.items(): - field_dict[prop_name] = prop.to_dict() + field_dict.update(self.additional_properties) return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.row_update_values_additional_property import RowUpdateValuesAdditionalProperty - d = dict(src_dict) row_update_values = cls() - additional_properties = {} - for prop_name, prop_dict in d.items(): - additional_property = RowUpdateValuesAdditionalProperty.from_dict(prop_dict) - - additional_properties[prop_name] = additional_property - - row_update_values.additional_properties = additional_properties + row_update_values.additional_properties = d return row_update_values @property def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) - def __getitem__(self, key: str) -> RowUpdateValuesAdditionalProperty: + def __getitem__(self, key: str) -> Any: return self.additional_properties[key] - def __setitem__(self, key: str, value: RowUpdateValuesAdditionalProperty) -> None: + def __setitem__(self, key: str, value: Any) -> None: self.additional_properties[key] = value def __delitem__(self, key: str) -> None: diff --git a/cirro_api_client/v1/models/row_update_values_additional_property.py b/cirro_api_client/v1/models/row_update_values_additional_property.py deleted file mode 100644 index ad10052..0000000 --- a/cirro_api_client/v1/models/row_update_values_additional_property.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -T = TypeVar("T", bound="RowUpdateValuesAdditionalProperty") - - -@_attrs_define -class RowUpdateValuesAdditionalProperty: - """ """ - - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - row_update_values_additional_property = cls() - - row_update_values_additional_property.additional_properties = d - return row_update_values_additional_property - - @property - def additional_keys(self) -> list[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/cirro_api_client/v1/models/sheet_query_response.py b/cirro_api_client/v1/models/sheet_query_response.py index 50ebcf8..cc59ea3 100644 --- a/cirro_api_client/v1/models/sheet_query_response.py +++ b/cirro_api_client/v1/models/sheet_query_response.py @@ -1,14 +1,13 @@ from __future__ import annotations from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field if TYPE_CHECKING: from ..models.query_column import QueryColumn - from ..models.sheet_query_response_rows_item import SheetQueryResponseRowsItem T = TypeVar("T", bound="SheetQueryResponse") @@ -22,12 +21,12 @@ class SheetQueryResponse: Attributes: columns (list[QueryColumn]): column definitions, starting with `_row_id` - rows (list[list[SheetQueryResponseRowsItem]]): row data, each list aligned with `columns` + rows (list[list[Any]]): row data, each list aligned with `columns` total_row_count (int): number of total rows in the result set """ columns: list[QueryColumn] - rows: list[list[SheetQueryResponseRowsItem]] + rows: list[list[Any]] total_row_count: int additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -39,10 +38,7 @@ def to_dict(self) -> dict[str, Any]: rows = [] for rows_item_data in self.rows: - rows_item = [] - for rows_item_item_data in rows_item_data: - rows_item_item = rows_item_item_data.to_dict() - rows_item.append(rows_item_item) + rows_item = rows_item_data rows.append(rows_item) @@ -63,7 +59,6 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.query_column import QueryColumn - from ..models.sheet_query_response_rows_item import SheetQueryResponseRowsItem d = dict(src_dict) columns = [] @@ -76,12 +71,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: rows = [] _rows = d.pop("rows") for rows_item_data in _rows: - rows_item = [] - _rows_item = rows_item_data - for rows_item_item_data in _rows_item: - rows_item_item = SheetQueryResponseRowsItem.from_dict(rows_item_item_data) - - rows_item.append(rows_item_item) + rows_item = cast(list[Any], rows_item_data) rows.append(rows_item) diff --git a/cirro_api_client/v1/models/sheet_query_response_rows_item.py b/cirro_api_client/v1/models/sheet_query_response_rows_item.py deleted file mode 100644 index 12dae57..0000000 --- a/cirro_api_client/v1/models/sheet_query_response_rows_item.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any, TypeVar - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -T = TypeVar("T", bound="SheetQueryResponseRowsItem") - - -@_attrs_define -class SheetQueryResponseRowsItem: - """ """ - - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - sheet_query_response_rows_item = cls() - - sheet_query_response_rows_item.additional_properties = d - return sheet_query_response_rows_item - - @property - def additional_keys(self) -> list[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/config.yml b/config.yml index 013826b..e1f143c 100644 --- a/config.yml +++ b/config.yml @@ -31,7 +31,5 @@ class_overrides: class_name: WorkspaceComputeConfigEnvironmentVariables ViewFilterValuesType0Item: class_name: ViewFilterValues - SheetQueryResponseRowsItemItem: - class_name: SheetQueryResponseRowsItem DashboardDashboardDataType0: class_name: DashboardData diff --git a/tests/test_client.py b/tests/test_client.py index 5ed8976..1b5031d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -55,3 +55,42 @@ def test_import_api_methods(self): self.assertIsNotNone(response) self.assertEqual(len(response.data), 1) self.assertIsInstance(response.data[0], User) + + +class TestSheetModels(unittest.TestCase): + """Sheet cell values are free-form JSON scalars, not nested objects (issue #29).""" + + def test_query_response_parses_scalar_rows(self): + from cirro_api_client.v1.models import SheetQueryResponse + + response = SheetQueryResponse.from_dict( + { + "columns": [ + {"name": "_row_id", "dataType": "LONG"}, + {"name": "icd_code", "dataType": "STRING"}, + {"name": "n", "dataType": "INTEGER"}, + ], + "rows": [[42, "G65", 3], [43, None, 0.5]], + "totalRowCount": 2, + } + ) + + self.assertEqual(response.rows, [[42, "G65", 3], [43, None, 0.5]]) + self.assertEqual(response.to_dict()["rows"], [[42, "G65", 3], [43, None, 0.5]]) + + def test_row_insert_serializes_scalar_values(self): + from cirro_api_client.v1.models import RowInsert, RowInsertValues + + values = RowInsertValues() + values["icd_code"] = "G65" + values["n"] = 3 + + self.assertEqual(RowInsert(values=values).to_dict(), {"values": {"icd_code": "G65", "n": 3}}) + + def test_row_update_serializes_scalar_values(self): + from cirro_api_client.v1.models import RowUpdate, RowUpdateValues + + values = RowUpdateValues() + values["icd_code"] = "G65" + + self.assertEqual(RowUpdate(row_id=42, values=values).to_dict(), {"rowId": 42, "values": {"icd_code": "G65"}})