Skip to content

Commit 492230d

Browse files
tpellissierclaude
andcommitted
Address PR review: move create_lookup_field to client, remove extensions
- Move create_lookup_field from extensions/relationships.py to DataverseClient method - Remove extensions/ directory entirely (src and tests) - Use tight type definitions instead of Any for relationship methods - Remove .NET SDK references from docstrings - Empty src models/__init__.py, add docstring to tests models/__init__.py - Add M:N relationship query to example - Add 12 unit tests for create_lookup_field Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 402a718 commit 492230d

9 files changed

Lines changed: 336 additions & 412 deletions

File tree

examples/advanced/relationships.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
This example demonstrates:
88
- Creating one-to-many relationships using the core SDK API
9-
- Creating lookup fields using the convenience extension helper
9+
- Creating lookup fields using the convenience method
1010
- Creating many-to-many relationships
1111
- Querying and deleting relationships
1212
- Working with relationship metadata types
@@ -29,7 +29,6 @@
2929
CascadeConfiguration,
3030
AssociatedMenuConfiguration,
3131
)
32-
from PowerPlatform.Dataverse.extensions.relationships import create_lookup_field
3332

3433

3534
# Simple logging helper
@@ -245,19 +244,18 @@ def main():
245244
rel_id_1 = result["relationship_id"]
246245

247246
# ============================================================================
248-
# 5. CREATE LOOKUP FIELD (Extension Helper)
247+
# 5. CREATE LOOKUP FIELD (Convenience Method)
249248
# ============================================================================
250249
print("\n" + "=" * 80)
251-
print("5. Create Lookup Field (Extension Helper)")
250+
print("5. Create Lookup Field (Convenience Method)")
252251
print("=" * 80)
253252

254253
log_call("Creating lookup field on Employee referencing Contact as Manager")
255254

256-
# Use the convenience helper for simpler scenarios
255+
# Use the convenience method for simpler scenarios
257256
# An Employee has a Manager (who is a Contact in the system)
258257
result2 = backoff(
259-
lambda: create_lookup_field(
260-
client,
258+
lambda: client.create_lookup_field(
261259
referencing_table=emp_table["table_logical_name"],
262260
lookup_field_name="new_ManagerId",
263261
referenced_table="contact",
@@ -268,7 +266,7 @@ def main():
268266
)
269267
)
270268

271-
print(f"[OK] Created lookup using helper: {result2['lookup_schema_name']}")
269+
print(f"[OK] Created lookup using convenience method: {result2['lookup_schema_name']}")
272270
print(f" Relationship: {result2['relationship_schema_name']}")
273271

274272
rel_id_2 = result2["relationship_id"]
@@ -317,7 +315,7 @@ def main():
317315
print("7. Query Relationship Metadata")
318316
print("=" * 80)
319317

320-
log_call("Retrieving relationship by schema name")
318+
log_call("Retrieving 1:N relationship by schema name")
321319

322320
rel_metadata = client.get_relationship("new_Department_Employee")
323321
if rel_metadata:
@@ -328,6 +326,17 @@ def main():
328326
else:
329327
print(" Relationship not found")
330328

329+
log_call("Retrieving M:N relationship by schema name")
330+
331+
m2m_metadata = client.get_relationship("new_employee_project")
332+
if m2m_metadata:
333+
print(f"[OK] Found relationship: {m2m_metadata.get('SchemaName')}")
334+
print(f" Type: {m2m_metadata.get('@odata.type')}")
335+
print(f" Entity 1: {m2m_metadata.get('Entity1LogicalName')}")
336+
print(f" Entity 2: {m2m_metadata.get('Entity2LogicalName')}")
337+
else:
338+
print(" Relationship not found")
339+
331340
# ============================================================================
332341
# 8. CLEANUP
333342
# ============================================================================

src/PowerPlatform/Dataverse/client.py

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
from .core._auth import _AuthManager
1212
from .core.config import DataverseConfig
1313
from .data._odata import _ODataClient
14+
from .models.metadata import (
15+
LookupAttributeMetadata,
16+
OneToManyRelationshipMetadata,
17+
ManyToManyRelationshipMetadata,
18+
Label,
19+
LocalizedLabel,
20+
CascadeConfiguration,
21+
)
1422

1523

1624
class DataverseClient:
@@ -701,16 +709,15 @@ def flush_cache(self, kind) -> int:
701709
# Relationship operations
702710
def create_one_to_many_relationship(
703711
self,
704-
lookup: Any,
705-
relationship: Any,
712+
lookup: LookupAttributeMetadata,
713+
relationship: OneToManyRelationshipMetadata,
706714
solution_unique_name: Optional[str] = None,
707715
) -> Dict[str, Any]:
708716
"""
709717
Create a one-to-many relationship between tables.
710718
711719
This operation creates both the relationship and the lookup attribute
712-
on the referencing table. It mirrors the CreateOneToManyRequest from
713-
the .NET SDK.
720+
on the referencing table.
714721
715722
:param lookup: Metadata defining the lookup attribute.
716723
:type lookup: ~PowerPlatform.Dataverse.models.metadata.LookupAttributeMetadata
@@ -768,15 +775,14 @@ def create_one_to_many_relationship(
768775

769776
def create_many_to_many_relationship(
770777
self,
771-
relationship: Any,
778+
relationship: ManyToManyRelationshipMetadata,
772779
solution_unique_name: Optional[str] = None,
773780
) -> Dict[str, Any]:
774781
"""
775782
Create a many-to-many relationship between tables.
776783
777784
This operation creates a many-to-many relationship and an intersect table
778-
to manage the relationship. It mirrors the CreateManyToManyRequest from
779-
the .NET SDK.
785+
to manage the relationship.
780786
781787
:param relationship: Metadata defining the many-to-many relationship.
782788
:type relationship: ~PowerPlatform.Dataverse.models.metadata.ManyToManyRelationshipMetadata
@@ -854,5 +860,92 @@ def get_relationship(self, schema_name: str) -> Optional[Dict[str, Any]]:
854860
with self._scoped_odata() as od:
855861
return od._get_relationship(schema_name)
856862

863+
def create_lookup_field(
864+
self,
865+
referencing_table: str,
866+
lookup_field_name: str,
867+
referenced_table: str,
868+
display_name: Optional[str] = None,
869+
description: Optional[str] = None,
870+
required: bool = False,
871+
cascade_delete: str = "RemoveLink",
872+
solution_unique_name: Optional[str] = None,
873+
language_code: int = 1033,
874+
) -> Dict[str, Any]:
875+
"""
876+
Create a simple lookup field relationship.
877+
878+
This is a convenience method that wraps :meth:`create_one_to_many_relationship`
879+
for the common case of adding a lookup field to an existing table.
880+
881+
:param referencing_table: Logical name of the table that will have the lookup field (child table).
882+
:type referencing_table: :class:`str`
883+
:param lookup_field_name: Schema name for the lookup field (e.g., ``"new_AccountId"``).
884+
:type lookup_field_name: :class:`str`
885+
:param referenced_table: Logical name of the table being referenced (parent table).
886+
:type referenced_table: :class:`str`
887+
:param display_name: Display name for the lookup field. Defaults to the referenced table name.
888+
:type display_name: :class:`str` or None
889+
:param description: Optional description for the lookup field.
890+
:type description: :class:`str` or None
891+
:param required: Whether the lookup is required. Defaults to ``False``.
892+
:type required: :class:`bool`
893+
:param cascade_delete: Delete behavior (``"RemoveLink"``, ``"Cascade"``, ``"Restrict"``).
894+
Defaults to ``"RemoveLink"``.
895+
:type cascade_delete: :class:`str`
896+
:param solution_unique_name: Optional solution to add the relationship to.
897+
:type solution_unique_name: :class:`str` or None
898+
:param language_code: Language code for labels. Defaults to 1033 (English).
899+
:type language_code: :class:`int`
900+
901+
:return: Dictionary with ``relationship_id``, ``lookup_schema_name``, and related metadata.
902+
:rtype: :class:`dict`
903+
904+
:raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the Web API request fails.
905+
906+
Example:
907+
Create a simple lookup field::
908+
909+
result = client.create_lookup_field(
910+
referencing_table="new_order",
911+
lookup_field_name="new_AccountId",
912+
referenced_table="account",
913+
display_name="Account",
914+
required=True,
915+
cascade_delete="RemoveLink"
916+
)
917+
918+
print(f"Created lookup: {result['lookup_schema_name']}")
919+
"""
920+
# Build the label
921+
localized_labels = [LocalizedLabel(label=display_name or referenced_table, language_code=language_code)]
922+
923+
# Build the lookup attribute
924+
lookup = LookupAttributeMetadata(
925+
schema_name=lookup_field_name,
926+
display_name=Label(localized_labels=localized_labels),
927+
required_level="ApplicationRequired" if required else "None",
928+
)
929+
930+
# Add description if provided
931+
if description:
932+
lookup.description = Label(
933+
localized_labels=[LocalizedLabel(label=description, language_code=language_code)]
934+
)
935+
936+
# Generate a relationship name
937+
relationship_name = f"{referenced_table}_{referencing_table}_{lookup_field_name}"
938+
939+
# Build the relationship metadata
940+
relationship = OneToManyRelationshipMetadata(
941+
schema_name=relationship_name,
942+
referenced_entity=referenced_table,
943+
referencing_entity=referencing_table,
944+
referenced_attribute=f"{referenced_table}id",
945+
cascade_configuration=CascadeConfiguration(delete=cascade_delete),
946+
)
947+
948+
return self.create_one_to_many_relationship(lookup, relationship, solution_unique_name)
949+
857950

858951
__all__ = ["DataverseClient"]

src/PowerPlatform/Dataverse/extensions/__init__.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/PowerPlatform/Dataverse/extensions/relationships.py

Lines changed: 0 additions & 119 deletions
This file was deleted.
Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,2 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT license.
3-
4-
"""
5-
Data models and type definitions for the Dataverse SDK.
6-
"""
7-
8-
from .metadata import (
9-
LocalizedLabel,
10-
Label,
11-
CascadeConfiguration,
12-
AssociatedMenuConfiguration,
13-
LookupAttributeMetadata,
14-
OneToManyRelationshipMetadata,
15-
ManyToManyRelationshipMetadata,
16-
)
17-
18-
__all__ = [
19-
"LocalizedLabel",
20-
"Label",
21-
"CascadeConfiguration",
22-
"AssociatedMenuConfiguration",
23-
"LookupAttributeMetadata",
24-
"OneToManyRelationshipMetadata",
25-
"ManyToManyRelationshipMetadata",
26-
]

tests/unit/extensions/__init__.py

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)