Skip to content

Commit 6b8353b

Browse files
tpellissier-msfttpellissierclaude
authored
Add relationship metadata API for creating table relationships (microsoft#88)
* Add relationship metadata API for creating table relationships - 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 <noreply@anthropic.com> * Address PR review feedback - 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 <noreply@anthropic.com> * Revert unrelated changes to minimize PR diff - 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 <noreply@anthropic.com> * Rename _ODataFileUpload to _FileUploadMixin for consistency Aligns with _RelationshipOperationsMixin naming convention. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add cascade behavior constants to constants.py 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 <noreply@anthropic.com> * Remove AssociatedMenuConfiguration for minimal API surface 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 <noreply@anthropic.com> * Align relationship API with SDK redesign patterns - 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 <noreply@anthropic.com> * Add attribute docstrings to cascade constants and replace hardcoded strings 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) <noreply@anthropic.com> * Address review comments: fix cascade docstrings and document naming convention - 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) <noreply@anthropic.com> * Clarify public API convention: methods in namespaces, types/constants in own modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: tpellissier <tpellissier@microsoft.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4996de3 commit 6b8353b

16 files changed

Lines changed: 2139 additions & 14 deletions

File tree

.claude/skills/dataverse-sdk-dev/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ This skill provides guidance for developers working on the PowerPlatform Dataver
1313

1414
### API Design
1515

16-
1. **Public API in operation namespaces** - New public methods go in the appropriate namespace module under `src/PowerPlatform/Dataverse/operations/` (`records.py`, `query.py`, `tables.py`). The `client.py` file exposes these via namespace properties (`client.records`, `client.query`, `client.tables`)
16+
1. **Public methods in operation namespaces** - New public methods go in the appropriate namespace module under `src/PowerPlatform/Dataverse/operations/` (`records.py`, `query.py`, `tables.py`). The `client.py` file exposes these via namespace properties (`client.records`, `client.query`, `client.tables`). Public types and constants live in their own modules (e.g., `models/metadata.py`, `common/constants.py`)
1717
2. **Every public method needs README example** - Public API methods must have examples in README.md
1818
3. **Reuse existing APIs** - Always check if an existing method can be used before making direct Web API calls
1919
4. **Update documentation** when adding features - Keep README and SKILL files (both copies) in sync
2020
5. **Consider backwards compatibility** - Avoid breaking changes
21+
6. **Internal vs public naming** - Modules, files, and functions not meant to be part of the public API must use a `_` prefix (e.g., `_odata.py`, `_relationships.py`). Files without the prefix (e.g., `constants.py`, `metadata.py`) are public and importable by SDK consumers
2122

2223
### Code Style
2324

README.md

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ A Python client library for Microsoft Dataverse that provides a unified interfac
2525
- [Bulk operations](#bulk-operations)
2626
- [Query data](#query-data)
2727
- [Table management](#table-management)
28+
- [Relationship management](#relationship-management)
2829
- [File operations](#file-operations)
2930
- [Next steps](#next-steps)
3031
- [Troubleshooting](#troubleshooting)
@@ -36,6 +37,7 @@ A Python client library for Microsoft Dataverse that provides a unified interfac
3637
- **⚡ True Bulk Operations**: Automatically uses Dataverse's native `CreateMultiple`, `UpdateMultiple`, and `BulkDelete` Web API operations for maximum performance and transactional integrity
3738
- **📊 SQL Queries**: Execute read-only SQL queries via the Dataverse Web API `?sql=` parameter
3839
- **🏗️ Table Management**: Create, inspect, and delete custom tables and columns programmatically
40+
- **🔗 Relationship Management**: Create one-to-many and many-to-many relationships between tables with full metadata control
3941
- **📎 File Operations**: Upload files to Dataverse file columns with automatic chunking for large files
4042
- **🔐 Azure Identity**: Built-in authentication using Azure Identity credential providers with comprehensive support
4143
- **🛡️ Error Handling**: Structured exception hierarchy with detailed error context and retry guidance
@@ -255,9 +257,71 @@ client.tables.remove_columns("new_Product", ["new_Category"])
255257
client.tables.delete("new_Product")
256258
```
257259

258-
> **Important**: All custom column names must include the customization prefix value (e.g., `"new_"`).
260+
> **Important**: All custom column names must include the customization prefix value (e.g., `"new_"`).
259261
> This ensures explicit, predictable naming and aligns with Dataverse metadata requirements.
260262
263+
### Relationship management
264+
265+
Create relationships between tables using the relationship API. For a complete working example, see [examples/advanced/relationships.py](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/relationships.py).
266+
267+
```python
268+
from PowerPlatform.Dataverse.models.metadata import (
269+
LookupAttributeMetadata,
270+
OneToManyRelationshipMetadata,
271+
ManyToManyRelationshipMetadata,
272+
Label,
273+
LocalizedLabel,
274+
)
275+
276+
# Create a one-to-many relationship: Department (1) -> Employee (N)
277+
# This adds a "Department" lookup field to the Employee table
278+
lookup = LookupAttributeMetadata(
279+
schema_name="new_DepartmentId",
280+
display_name=Label(localized_labels=[LocalizedLabel(label="Department", language_code=1033)]),
281+
)
282+
283+
relationship = OneToManyRelationshipMetadata(
284+
schema_name="new_Department_Employee",
285+
referenced_entity="new_department", # Parent table (the "one" side)
286+
referencing_entity="new_employee", # Child table (the "many" side)
287+
referenced_attribute="new_departmentid",
288+
)
289+
290+
result = client.create_one_to_many_relationship(lookup, relationship)
291+
print(f"Created lookup field: {result['lookup_schema_name']}")
292+
293+
# Create a many-to-many relationship: Employee (N) <-> Project (N)
294+
# Employees work on multiple projects; projects have multiple team members
295+
m2m_relationship = ManyToManyRelationshipMetadata(
296+
schema_name="new_employee_project",
297+
entity1_logical_name="new_employee",
298+
entity2_logical_name="new_project",
299+
)
300+
301+
result = client.create_many_to_many_relationship(m2m_relationship)
302+
print(f"Created M:N relationship: {result['relationship_schema_name']}")
303+
304+
# Query relationship metadata
305+
rel = client.get_relationship("new_Department_Employee")
306+
if rel:
307+
print(f"Found: {rel['SchemaName']}")
308+
309+
# Delete a relationship
310+
client.delete_relationship(result['relationship_id'])
311+
```
312+
313+
For simpler scenarios, use the convenience method:
314+
315+
```python
316+
# Quick way to create a lookup field with sensible defaults
317+
result = client.create_lookup_field(
318+
referencing_table="contact", # Child table gets the lookup field
319+
lookup_field_name="new_AccountId",
320+
referenced_table="account", # Parent table being referenced
321+
display_name="Account",
322+
)
323+
```
324+
261325
### File operations
262326

263327
```python
@@ -281,7 +345,8 @@ Explore our comprehensive examples in the [`examples/`](https://github.com/micro
281345
- **[Functional Testing](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/basic/functional_testing.py)** - Test core functionality in your environment
282346

283347
**🚀 Advanced Usage:**
284-
- **[Complete Walkthrough](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/walkthrough.py)** - Full feature demonstration with production patterns
348+
- **[Complete Walkthrough](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/walkthrough.py)** - Full feature demonstration with production patterns
349+
- **[Relationship Management](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/relationships.py)** - Create and manage table relationships
285350
- **[File Upload](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/file_upload.py)** - Upload files to Dataverse file columns
286351

287352
📖 See the [examples README](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/README.md) for detailed guidance and learning progression.
@@ -343,8 +408,7 @@ For optimal performance in production environments:
343408
### Limitations
344409

345410
- SQL queries are **read-only** and support a limited subset of SQL syntax
346-
- Create Table supports a limited number of column types. Lookup columns are not yet supported.
347-
- Creating relationships between tables is not yet supported.
411+
- Create Table supports a limited number of column types (string, int, decimal, bool, datetime, picklist)
348412
- File uploads are limited by Dataverse file size restrictions (default 128MB per file)
349413

350414
## Contributing
@@ -365,10 +429,11 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
365429

366430
When contributing new features to this SDK, please follow these guidelines:
367431

368-
1. **Public API in operation namespaces** - New public methods go in the appropriate namespace module under [operations/](src/PowerPlatform/Dataverse/operations/)
432+
1. **Public methods in operation namespaces** - New public methods go in the appropriate namespace module under [operations/](src/PowerPlatform/Dataverse/operations/). Public types and constants live in their own modules (e.g., `models/metadata.py`, `common/constants.py`)
369433
2. **Add README example for public methods** - Add usage examples to this README for public API methods
370434
3. **Document public APIs** - Include Sphinx-style docstrings with parameter descriptions and examples for all public methods
371435
4. **Update documentation** when adding features - Keep README and SKILL files (note that each skill has 2 copies) in sync
436+
5. **Internal vs public naming** - Modules, files, and functions not meant to be part of the public API must use a `_` prefix (e.g., `_odata.py`, `_relationships.py`). Files without the prefix (e.g., `constants.py`, `metadata.py`) are public and importable by SDK consumers
372437

373438
## Trademarks
374439

0 commit comments

Comments
 (0)