Add alternate key management for upsert support#126
Add alternate key management for upsert support#126tpellissier-msft wants to merge 12 commits intomainfrom
Conversation
Adds client.tables.create_alternate_key(), get_alternate_keys(), and delete_alternate_key() with AlternateKeyInfo typed return model. Enables programmatic setup of alternate keys required for upsert operations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates full workflow: create table, create alternate key, wait for index, upsert records, verify, clean up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds programmatic alternate key management to the SDK, enabling developers to create, list, and delete alternate keys required for upsert operations. The implementation introduces a new AlternateKeyInfo dataclass model and adds three operations to the client.tables namespace that follow the established patterns for metadata operations in the codebase.
Changes:
- Adds
AlternateKeyInfodataclass model withfrom_api_response()factory method for typed alternate key metadata - Adds three OData layer helper methods (
_create_alternate_key,_get_alternate_keys,_delete_alternate_key) with consistent table validation and error handling - Adds three public operations (
create_alternate_key,get_alternate_keys,delete_alternate_key) toTableOperationsnamespace with comprehensive docstrings
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/PowerPlatform/Dataverse/models/table_info.py | New dataclass AlternateKeyInfo with fields for metadata_id, schema_name, key_attributes, and status; includes factory method for API response mapping |
| src/PowerPlatform/Dataverse/data/_odata.py | Three private OData methods for alternate key CRUD operations with consistent table validation, error handling, and API endpoint construction |
| src/PowerPlatform/Dataverse/operations/tables.py | Three public methods in TableOperations namespace with comprehensive documentation and examples; follows established patterns for scoped OData access |
| tests/unit/models/test_alternate_key.py | Unit tests for AlternateKeyInfo defaults, factory method, and edge cases (full/minimal/partial responses, multi-column keys, independent mutable defaults) |
| tests/unit/test_tables_operations.py | Unit tests for all three table operations verifying OData layer calls, return types, and handling of single/multi-column keys and empty results |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The EntityKeyMetadata API requires a DisplayName with localized labels. Uses the key_name as the display label by default. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…me param - Use Label/LocalizedLabel models instead of inline JSON construction - Add optional display_name and language_code keyword params - Pass Label to OData layer instead of building payload internally - Update test assertions for new signature Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separates single PATCH upsert (step 4a) from UpsertMultiple bulk (step 4b) to make it clear which path is being exercised. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single PATCH upsert works correctly. UpsertMultiple bulk action returns 'An unexpected error occurred' on custom tables - this is a pre-existing issue in the upsert implementation to investigate separately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UpsertMultiple returns 400 Bad Request on custom tables while single PATCH upsert works correctly. This is a pre-existing issue in the _upsert_multiple implementation (PR #106) that needs investigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Alternate key values must only appear in @odata.id, not in the record body. Including them in both causes Dataverse to return 400 Bad Request. Confirmed via raw curl that removing the duplicate fields resolves it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UpsertMultiple now works correctly after the payload fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unnecessary dict() copy to match PR #127 - Example now covers all 4 upsert paths: single-create, multi-create, single-update, multi-update Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Newly created keys always start in Pending state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| self.assertIsInstance(result, AlternateKeyInfo) | ||
| self.assertEqual(result.metadata_id, "key-guid-1") | ||
| self.assertEqual(result.schema_name, "new_product_code_key") | ||
| self.assertEqual(result.key_attributes, ["new_productcode"]) |
There was a problem hiding this comment.
Would it be useful to also have an assertion for the status in the alternate key result?
| columns: List[str], | ||
| *, | ||
| display_name: Optional[str] = None, | ||
| language_code: int = 1033, |
There was a problem hiding this comment.
Nit: Not specifically for this PR but may be for future consideration - 1033 has been used as a default language code in a few places. Would it make sense to define a constant like 'DEFAULT_LANGUAGE_CODE' for it?
Summary
AlternateKeyInfodataclass model in newmodels/table_info.pyfor typed alternate key metadata_create_alternate_key,_get_alternate_keys,_delete_alternate_key) to_odata.pycreate_alternate_key,get_alternate_keys,delete_alternate_key) toTableOperationsintables.pyTest plan
AlternateKeyInfo.from_api_responsewith full, minimal, partial, and multi-column dataAlternateKeyInfocreate_alternate_keycalls OData layer correctly and returnsAlternateKeyInfocreate_alternate_keywith multi-column keysget_alternate_keysreturns list ofAlternateKeyInfofrom API responseget_alternate_keysreturns empty list when no keys existdelete_alternate_keycalls OData layer with correct argspython -m pytest tests/unit/ -v)python -m black🤖 Generated with Claude Code