Skip to content

Add RelationshipInfo typed return model#114

Merged
tpellissier-msft merged 11 commits intomainfrom
feature/relationship-info-model
Feb 25, 2026
Merged

Add RelationshipInfo typed return model#114
tpellissier-msft merged 11 commits intomainfrom
feature/relationship-info-model

Conversation

@tpellissier-msft
Copy link
Copy Markdown
Contributor

Summary

  • Introduces RelationshipInfo dataclass as the return type for all relationship operations
  • Replaces raw Dict[str, Any] returns with typed model providing attribute access
  • Factory methods: from_one_to_many(), from_many_to_many(), from_api_response() (auto-detects type from @odata.type)
  • No backward compat needed — relationship methods are net-new (not yet in a stable release)

Changed methods

  • tables.create_one_to_many_relationship() → returns RelationshipInfo
  • tables.create_many_to_many_relationship() → returns RelationshipInfo
  • tables.get_relationship() → returns Optional[RelationshipInfo]
  • tables.create_lookup_field() → returns RelationshipInfo

Test plan

  • 166 unit tests passing (10 new model tests + updated operation tests)
  • Black formatting clean

🤖 Generated with Claude Code

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) <[email protected]>
Copilot AI review requested due to automatic review settings February 23, 2026 20:56
@tpellissier-msft tpellissier-msft requested a review from a team as a code owner February 23, 2026 20:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a typed RelationshipInfo dataclass as the return type for relationship operations, replacing raw dictionaries with a structured model that provides attribute access and better IDE support. The changes align with modern Python API design patterns by offering factory methods for creating instances from different sources.

Changes:

  • Added RelationshipInfo dataclass with factory methods for one-to-many, many-to-many, and API response parsing
  • Updated four relationship operations in TableOperations to return RelationshipInfo instead of Dict[str, Any]
  • Added comprehensive test coverage with 10 new model tests and updated operation tests

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/PowerPlatform/Dataverse/models/relationship_info.py New typed model with dataclass pattern and three factory methods for different relationship types
src/PowerPlatform/Dataverse/operations/tables.py Updated return types and implementations for create_one_to_many_relationship, create_many_to_many_relationship, get_relationship, and create_lookup_field to return RelationshipInfo
tests/unit/models/test_relationship_info.py New comprehensive test suite covering all factory methods and edge cases
tests/unit/test_tables_operations.py Updated operation tests to assert RelationshipInfo instances and validate attribute access

💡 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) <[email protected]>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 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.

@zhaodongwang-msft
Copy link
Copy Markdown
Collaborator

since we have the RelationshipInfo now, _create_one_to_many_relationship etc. can probably use that instead of creating a new dict

tpellissier and others added 4 commits February 23, 2026 20:47
…ds in docstrings

- from_api_response raises ValueError for unrecognized @odata.type
  (Dataverse only supports OneToMany and ManyToMany)
- from_api_response delegates to from_one_to_many/from_many_to_many
  instead of constructing cls() directly
- Docstrings on create methods now list all returned fields explicitly

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Move relationship input models (CascadeConfiguration, LookupAttributeMetadata,
OneToManyRelationshipMetadata, ManyToManyRelationshipMetadata) from metadata.py
into relationship.py alongside RelationshipInfo output model.

Extract Label/LocalizedLabel into labels.py.

Delete metadata.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Missing keys from our own OData layer would be a bug; fail loudly
with KeyError instead of silently defaulting to empty string.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@tpellissier-msft
Copy link
Copy Markdown
Contributor Author

since we have the RelationshipInfo now, _create_one_to_many_relationship etc. can probably use that instead of creating a new dict

It would probably work fine, but I might prefer keeping the model types (public, fixed contract) uncoupled to the odata layer (private methods, we can reimplement as we please). How do you feel about that aspect?

tpellissier and others added 2 commits February 24, 2026 11:48
…al design doc

- Use direct key access for required API fields (ReferencedEntity, etc.)
  instead of .get() with empty string defaults
- Keep .get() only for ReferencingEntityNavigationPropertyName which may
  be absent in the API response
- Remove docs/design/relationships_integration.md (old design doc
  accidentally included in reorg commit)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@tpellissier-msft tpellissier-msft merged commit 8c0fa7e into main Feb 25, 2026
9 checks passed
@tpellissier-msft tpellissier-msft deleted the feature/relationship-info-model branch February 25, 2026 06:01
tpellissier-msft pushed a commit that referenced this pull request Mar 3, 2026
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
tpellissier-msft pushed a commit that referenced this pull request Mar 3, 2026
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
saurabhrb added a commit that referenced this pull request Mar 18, 2026
## 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants