Add Record and TableInfo typed return models#115
Add Record and TableInfo typed return models#115tpellissier-msft wants to merge 8 commits intomainfrom
Conversation
Introduces RelationshipInfo dataclass as the return type for all relationship operations (create_one_to_many_relationship, create_many_to_many_relationship, get_relationship, create_lookup_field). Factory methods: from_one_to_many(), from_many_to_many(), from_api_response() which detects 1:N vs N:N from @odata.type in API responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Record: wraps OData responses for records.get() and query.sql() with dict-like backward compat (result["key"] still works) - TableInfo: wraps table metadata for tables.create() and tables.get() with legacy key mapping (result["table_schema_name"] still works) - ColumnInfo: typed model for column metadata (from_api_response factory) - Updated all operation return types and existing tests - 188 tests passing (32 new model tests) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces typed dataclass return models (Record, TableInfo, ColumnInfo) for record and table metadata operations, replacing raw Dict[str, Any] returns while maintaining full backward compatibility through dict-like access patterns. The changes upgrade the SDK's type safety without breaking existing code that expects dictionary access.
Changes:
- New
Recordmodel wraps OData record responses with structured fields (id,table,data,etag) while supportingresult["key"]dict-like access via delegation toself.data - New
TableInfoandColumnInfomodels provide typed table metadata with legacy key mapping for backward compatibility (e.g.,result["table_schema_name"]maps toresult.schema_name) - Updated return types for
records.get(),query.sql(),tables.create(), andtables.get()to return typed models instead of raw dicts
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/PowerPlatform/Dataverse/models/record.py |
New Record dataclass with dict-like delegation, from_api_response factory that strips @OData.* keys |
src/PowerPlatform/Dataverse/models/table_info.py |
New TableInfo and ColumnInfo dataclasses with legacy key mapping and dual factory methods |
src/PowerPlatform/Dataverse/operations/records.py |
Updated get() return types to Record, wrapping raw OData responses in factory calls |
src/PowerPlatform/Dataverse/operations/query.py |
Updated sql() return type to List[Record], wrapping SQL results |
src/PowerPlatform/Dataverse/operations/tables.py |
Updated create() and get() return types to TableInfo with factory wrapping |
tests/unit/models/test_record.py |
Comprehensive tests for Record dict-like access and API response transformation |
tests/unit/models/test_table_info.py |
Tests for TableInfo/ColumnInfo dict-like access, legacy key mapping, and factories |
tests/unit/test_records_operations.py |
Updated to assert Record instances with dict-like access verification |
tests/unit/test_query_operations.py |
Updated to assert Record instances in SQL query results |
tests/unit/test_tables_operations.py |
Updated to assert TableInfo instances with backward compatibility checks |
tests/unit/test_client_deprecations.py |
Updated deprecated method tests to work with new Record/TableInfo returns |
tests/unit/test_client.py |
Updated legacy client tests to verify dict-like access on new models |
Comments suppressed due to low confidence (1)
src/PowerPlatform/Dataverse/operations/query.py:79
- The SQL query results use an empty string for the table parameter in Record.from_api_response, which means Record.table will be empty for SQL query results. This is inconsistent with other operations where table contains the actual table schema name. Consider extracting the table name from the SQL query using the existing _extract_logical_table helper from the OData layer, or at minimum document this behavior in the docstring for the sql method.
return [Record.from_api_response("", row) for row in rows]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…l duplicates Import ODATA_TYPE_ONE_TO_MANY_RELATIONSHIP and ODATA_TYPE_MANY_TO_MANY_RELATIONSHIP from common.constants to maintain consistency with the rest of the codebase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…microsoft/PowerPlatform-DataverseClient-Python into feature/record-tableinfo-models # Conflicts: # src/PowerPlatform/Dataverse/operations/tables.py # tests/unit/test_tables_operations.py
Resolve merge conflicts: - Remove redundant models/relationship_info.py (RelationshipInfo lives in models/relationship.py since PR #114) - Update imports in tables.py and tests to use models.relationship - Use direct dict access for required keys (main's convention) - Use main's detailed :rtype: docstrings - Fix test_relationship_info.py to match main's ValueError behavior for unrecognized @odata.type
3b8adbd to
639cba7
Compare
| """Return value for *key*, or *default* if not present.""" | ||
| return self.data.get(key, default) | ||
|
|
||
| def keys(self): |
There was a problem hiding this comment.
Nit: This method and the two methods below it, are missing return type annotations.
| display_name_obj = response_data.get("DisplayName", {}) | ||
| user_label = display_name_obj.get("UserLocalizedLabel") or {} | ||
| display_name = user_label.get("Label") | ||
|
|
||
| # Extract description from nested structure | ||
| desc_obj = response_data.get("Description", {}) | ||
| desc_label = desc_obj.get("UserLocalizedLabel") or {} | ||
| description = desc_label.get("Label") |
There was a problem hiding this comment.
Nit: These are used in both ColumnInfo and TableInfo. Would it make sense to have a shared helper function?
| @@ -7,6 +7,8 @@ | |||
|
|
|||
| from typing import Any, Dict, List, TYPE_CHECKING | |||
There was a problem hiding this comment.
Any and Dict are no longer used.
Summary
Introduces typed dataclass return models for record and table metadata operations, replacing raw
Dict[str, Any]returns while maintaining full backward compatibility via dict-like access.New models
Record— wraps OData record responses withid,table,data,etagfields.result["key"]still works via delegation toself.data. Factoryfrom_api_response()strips@odata.*keys and extracts etag.TableInfo— wraps table metadata with legacy key mapping (result["table_schema_name"]maps toresult.schema_name). Factoriesfrom_dict()andfrom_api_response().ColumnInfo— typed column metadata withfrom_api_response()for PascalCase API responses.Changed return types
records.get(table, id)Dict[str, Any]Recordrecords.get(table, filter=...)Iterable[List[Dict]]Iterable[List[Record]]query.sql(sql)List[Dict]List[Record]tables.create(...)Dict[str, Any]TableInfotables.get(...)Optional[Dict]Optional[TableInfo]Backward compatibility
result["key"]works on all models"key" in resultworksfor k in resultiterates keysresult.get("key", default)workstable_schema_name) map to new attributes (schema_name)Test plan
🤖 Generated with Claude Code