Add relationship metadata API for creating table relationships#88
Add relationship metadata API for creating table relationships#88tpellissier-msft merged 12 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive relationship metadata API support to the Dataverse SDK, enabling programmatic creation and management of table relationships through the Web API. The implementation follows a clean architecture with metadata models, a relationship operations mixin, and convenience helpers.
Changes:
- New metadata model types (LocalizedLabel, Label, CascadeConfiguration, AssociatedMenuConfiguration, LookupAttributeMetadata, OneToManyRelationshipMetadata, ManyToManyRelationshipMetadata) with serialization support
- Relationship CRUD operations via mixin pattern (_RelationshipOperationsMixin) integrated into _ODataClient
- Public API methods on DataverseClient for creating/deleting/querying relationships
- Convenience extension helper (create_lookup_field) for simplified lookup scenarios
- Comprehensive unit tests (60 total, 25 new for metadata types)
- Full working example demonstrating all relationship operations
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/PowerPlatform/Dataverse/models/metadata.py | New metadata model types with to_dict() serialization for Web API |
| src/PowerPlatform/Dataverse/models/init.py | Exports new metadata types |
| tests/unit/models/test_metadata.py | Comprehensive unit tests for all metadata classes |
| tests/unit/models/init.py | Test package initialization |
| src/PowerPlatform/Dataverse/data/_relationships.py | Relationship operations mixin with create/read/delete methods |
| src/PowerPlatform/Dataverse/data/_odata.py | Integration of relationship mixin into _ODataClient |
| src/PowerPlatform/Dataverse/client.py | Public API methods for relationship operations |
| src/PowerPlatform/Dataverse/extensions/relationships.py | Convenience helper for common lookup field scenarios |
| src/PowerPlatform/Dataverse/extensions/init.py | Exports create_lookup_field helper |
| examples/advanced/relationships.py | Complete working example demonstrating all features |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
we should probably update README too |
Good call, updated README. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
884755f to
b07d2e9
Compare
- Add metadata dataclasses (LocalizedLabel, Label, CascadeConfiguration, AssociatedMenuConfiguration, LookupAttributeMetadata, OneToManyRelationshipMetadata, ManyToManyRelationshipMetadata) - Add _RelationshipOperationsMixin with create/get/delete operations - Add create_lookup_field() convenience method to DataverseClient - Add comprehensive unit tests for all new functionality - Add relationships.py example demonstrating the API Co-Authored-By: Claude Opus 4.5 <[email protected]>
b07d2e9 to
c248495
Compare
- Move OData type constants to common/constants.py - Add input/output examples to metadata to_dict() docstrings - Remove .NET SDK references from _relationships.py docstrings - Add __all__ to models/__init__.py Co-Authored-By: Claude Opus 4.5 <[email protected]>
c248495 to
c9c97ad
Compare
- Revert emoji formatting changes in examples (file_upload, walkthrough, functional_testing, installation_example) - Revert pyproject.toml changes (keep claude skill installer) - Update README with only relationship-related changes Co-Authored-By: Claude Opus 4.5 <[email protected]>
Aligns with _RelationshipOperationsMixin naming convention. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Move cascade behavior string values ("Cascade", "NoCascade", "RemoveLink",
"Restrict") to constants.py per PR review feedback. Update CascadeConfiguration
to use these constants for its default values.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Remove the AssociatedMenuConfiguration class and related parameters from relationship metadata types. This is a niche UI customization that most users don't need, and can still be achieved via additional_properties. Changes: - Remove AssociatedMenuConfiguration class from metadata.py - Remove associated_menu_configuration from OneToManyRelationshipMetadata - Remove entity1/2_associated_menu_configuration from ManyToManyRelationshipMetadata - Update example and tests accordingly Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Rename solution_unique_name parameter to solution (shorter, consistent) - Add keyword-only separator (*) for optional parameters - Update tests to use new parameter style Co-Authored-By: Claude Opus 4.5 <[email protected]>
# Conflicts: # src/PowerPlatform/Dataverse/client.py
…trings
Use attribute docstrings (""" after assignment) for cascade behavior constants
so Pylance/Pyright surfaces descriptions in IDE hover tooltips. Replace all
hardcoded cascade string literals with named constants in client.py and the
relationships example.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Resolve import conflict in client.py: keep both metadata imports and operation namespace imports. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
b8ce576 to
d8aa1a3
Compare
…onvention - Use MS docs descriptions for cascade constants; keep NoCascade explicit - Restrict and RemoveLink docstrings now reference delete specifically - Document internal vs public _ prefix naming convention in README and both SKILL file copies Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
d8aa1a3 to
de6018c
Compare
… in own modules Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
## Summary Add end-to-end relationship tests that validate the full relationship API lifecycle against a live Dataverse environment. This is the primary pre-GA validation for Tim's relationship PRs (#88, #105, #114). ## Changes ### New: `tests/e2e/test_relationships_e2e.py` 11 curated e2e tests covering: | Test Class | Tests | Coverage | |---|---|---| | `TestOneToManyCore` | 1 | Full 1:N lifecycle: create, get, delete, field assertions | | `TestLookupField` | 1 | Convenience `create_lookup_field` to system table | | `TestManyToMany` | 2 | N:N lifecycle + nonexistent returns None | | `TestDataThroughRelationships` | 4 | `@odata.bind`, `$expand`, `$filter` on lookup, update binding | | `TestCascadeBehaviors` | 2 | Restrict blocks delete; Cascade deletes children | | `TestTypeDetection` | 1 | `get_relationship` distinguishes 1:N vs N:N | ### Updated: `examples/basic/functional_testing.py` - Added relationship testing section covering 1:N core API, convenience API, N:N, get, and delete - Added relationship imports and retry helpers ### Updated: `pyproject.toml` - Added `[tool.pytest.ini_options]` with `testpaths = ["tests/unit"]` - Default `pytest` runs only unit tests; e2e tests require explicit invocation ## How to run e2e tests ```bash # Set your Dataverse org URL export DATAVERSE_URL=https://yourorg.crm.dynamics.com # Run relationship e2e tests pytest tests/e2e/ -v -s ``` The tests authenticate via `InteractiveBrowserCredential` and create/delete temporary tables (prefixed `test_E2E*`). ## E2E Test Results (from `.scratch/` comprehensive suite) Ran 30 tests against `https://aurorabapenv71aff.crm10.dynamics.com`: - 25/30 passed on first run - 5 failures were test bugs (not SDK bugs), all fixed: - Metadata propagation timing (increased retries) - Navigation property name casing (`$expand` needs server-assigned nav prop) - `IsValidForAdvancedFind` requires `BooleanManagedProperty` complex type ## Finding: SDK inconsistency to address before GA `create_one_to_many_relationship()` returns `lookup_schema_name` as the user-provided SchemaName, but `$expand` requires the server-assigned `ReferencingEntityNavigationPropertyName` (which may differ in casing). The e2e tests work around this by calling `get_relationship()` after create to get the correct nav prop name. This should be harmonized before GA. ## Checklist - [x] 398 unit tests pass - [x] 11 e2e tests collected by pytest - [x] Default `pytest` excludes e2e (runs unit only) - [x] Code formatted with black - [x] Branch rebased on origin/main --------- Co-authored-by: Saurabh Badenkal <[email protected]>
Summary
DataverseClientfor creating and managing table relationshipsChanges
New API Methods on DataverseClient
create_one_to_many_relationship(lookup, relationship)- Create 1:N relationships with lookup fieldscreate_many_to_many_relationship(relationship)- Create N:N relationships with intersect tablesget_relationship(schema_name)- Query relationship metadata by schema namedelete_relationship(relationship_id)- Delete relationships by metadata IDNew Metadata Types (
models/metadata.py)LookupAttributeMetadata- Lookup field configurationOneToManyRelationshipMetadata- 1:N relationship definitionManyToManyRelationshipMetadata- N:N relationship definitionCascadeConfiguration- Cascade behavior settingsAssociatedMenuConfiguration- Navigation menu optionsLabel/LocalizedLabel- Multi-language label supportExtension Helper (
extensions/relationships.py)create_lookup_field()- Simplified helper for common lookup scenariosArchitecture
_relationships.pymixin to keep_odata.pyfocusedadditional_propertiesfor extensibilityTest plan