Skip to content

Commit 5c964fc

Browse files
author
Max Wang
committed
add claude skill for the sdk
1 parent c1ce5f0 commit 5c964fc

File tree

7 files changed

+829
-6
lines changed

7 files changed

+829
-6
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
---
2+
name: powerplatform-dataverseclient-python
3+
description: Guidance for using the PowerPlatform Dataverse Client Python SDK. Use when calling the SDK like creating CRUD operations, SQL queries, table metadata management, and upload files.
4+
---
5+
6+
# PowerPlatform Dataverse SDK Guide
7+
8+
## Overview
9+
10+
Use the PowerPlatform Dataverse Client Python SDK to interact with Microsoft Dataverse.
11+
12+
## Key Concepts
13+
14+
### Schema Names vs Display Names
15+
- Standard tables: lowercase (e.g., `"account"`, `"contact"`)
16+
- Custom tables: include customization prefix (e.g., `"new_Product"`, `"cr123_Invoice"`)
17+
- Custom columns: include customization prefix (e.g., `"new_Price"`, `"cr123_Status"`)
18+
- ALWAYS use **schema names** (logical names), NOT display names
19+
20+
### Bulk Operations
21+
The SDK supports Dataverse's native bulk operations: Pass lists to `create()`, `update()` for automatic bulk processing, for `delete()`, set `use_bulk_delete` when passing lists to use bulk operation
22+
23+
### Paging
24+
- Control page size with `page_size` parameter
25+
- Use `top` parameter to limit total records returned
26+
27+
## Common Operations
28+
29+
### Import
30+
```python
31+
from azure.identity import (
32+
InteractiveBrowserCredential,
33+
ClientSecretCredential,
34+
ClientCertificateCredential,
35+
AzureCliCredential
36+
)
37+
from PowerPlatform.Dataverse.client import DataverseClient
38+
```
39+
40+
### Client Initialization
41+
```python
42+
# Development options
43+
credential = InteractiveBrowserCredential()
44+
credential = AzureCliCredential()
45+
46+
# Production options
47+
credential = ClientSecretCredential(tenant_id, client_id, client_secret)
48+
credential = ClientCertificateCredential(tenant_id, client_id, cert_path)
49+
50+
# Create client (no trailing slash on URL!)
51+
client = DataverseClient("https://yourorg.crm.dynamics.com", credential)
52+
```
53+
54+
### CRUD Operations
55+
56+
#### Create Records
57+
```python
58+
# Single record
59+
account_ids = client.create("account", {"name": "Contoso Ltd", "telephone1": "555-0100"})
60+
account_id = account_ids[0]
61+
62+
# Bulk create (uses CreateMultiple API automatically)
63+
contacts = [
64+
{"firstname": "John", "lastname": "Doe"},
65+
{"firstname": "Jane", "lastname": "Smith"}
66+
]
67+
contact_ids = client.create("contact", contacts)
68+
```
69+
70+
#### Read Records
71+
```python
72+
# Get single record by ID
73+
account = client.get("account", account_id, select=["name", "telephone1"])
74+
75+
# Query with filter
76+
pages = client.get(
77+
"account",
78+
select=["accountid", "name"], # select is case-insensitive (automatically lowercased)
79+
filter="statecode eq 0", # filter must use lowercase logical names (not transformed)
80+
top=100
81+
)
82+
for page in pages:
83+
for record in page:
84+
print(record["name"])
85+
86+
# Query with navigation property expansion (case-sensitive!)
87+
pages = client.get(
88+
"account",
89+
select=["name"],
90+
expand=["primarycontactid"], # Navigation properties are case-sensitive!
91+
filter="statecode eq 0" # Column names must be lowercase logical names
92+
)
93+
for page in pages:
94+
for account in page:
95+
contact = account.get("primarycontactid", {})
96+
print(f"{account['name']} - {contact.get('fullname', 'N/A')}")
97+
```
98+
99+
#### Update Records
100+
```python
101+
# Single update
102+
client.update("account", account_id, {"telephone1": "555-0200"})
103+
104+
# Bulk update (broadcast same change to multiple records)
105+
client.update("account", [id1, id2, id3], {"industry": "Technology"})
106+
```
107+
108+
#### Delete Records
109+
```python
110+
# Single delete
111+
client.delete("account", account_id)
112+
113+
# Bulk delete (uses BulkDelete API)
114+
client.delete("account", [id1, id2, id3], use_bulk_delete=True)
115+
```
116+
117+
### SQL Queries
118+
119+
SQL queries are **read-only** and support limited SQL syntax. A single SELECT statement with optional WHERE, TOP (integer literal), ORDER BY (column names only), and a simple table alias after FROM is supported. But JOIN and subqueries may not be. Refer to the Dataverse documentation for the current feature set.
120+
121+
```python
122+
# Basic SQL query
123+
results = client.query_sql(
124+
"SELECT TOP 10 accountid, name FROM account WHERE statecode = 0"
125+
)
126+
for record in results:
127+
print(record["name"])
128+
```
129+
130+
### Table Management
131+
132+
#### Create Custom Tables
133+
```python
134+
# Create table with columns (include customization prefix!)
135+
table_info = client.create_table(
136+
table_schema_name="new_Product",
137+
columns={
138+
"new_Code": "string",
139+
"new_Price": "decimal",
140+
"new_Active": "bool",
141+
"new_Quantity": "int"
142+
}
143+
)
144+
145+
# With solution assignment and custom primary column
146+
table_info = client.create_table(
147+
table_schema_name="new_Product",
148+
columns={"new_Code": "string", "new_Price": "decimal"},
149+
solution_unique_name="MyPublisher",
150+
primary_column_schema_name="new_ProductCode"
151+
)
152+
```
153+
154+
#### Supported Column Types
155+
Types on the same line map to the same exact format under the hood
156+
- `"string"` or `"text"` - Single line of text
157+
- `"int"` or `"integer"` - Whole number
158+
- `"decimal"` or `"money"` - Decimal number
159+
- `"float"` or `"double"` - Floating point number
160+
- `"bool"` or `"boolean"` - Yes/No
161+
- `"datetime"` or `"date"` - Date
162+
- Enum subclass - Local option set (picklist)
163+
164+
#### Manage Columns
165+
```python
166+
# Add columns to existing table (must include customization prefix!)
167+
client.create_columns("new_Product", {
168+
"new_Category": "string",
169+
"new_InStock": "bool"
170+
})
171+
172+
# Remove columns
173+
client.delete_columns("new_Product", ["new_Category"])
174+
```
175+
176+
#### Inspect Tables
177+
```python
178+
# Get single table information
179+
table_info = client.get_table_info("new_Product")
180+
print(f"Logical name: {table_info['table_logical_name']}")
181+
print(f"Entity set: {table_info['entity_set_name']}")
182+
183+
# List all tables
184+
tables = client.list_tables()
185+
for table in tables:
186+
print(table)
187+
```
188+
189+
#### Delete Tables
190+
```python
191+
# Delete custom table
192+
client.delete_table("new_Product")
193+
```
194+
195+
### File Operations
196+
197+
```python
198+
# Upload file to a file column
199+
client.upload_file(
200+
table_schema_name="account",
201+
record_id=account_id,
202+
file_name_attribute="new_document",
203+
path="/path/to/document.pdf"
204+
)
205+
```
206+
207+
## Error Handling
208+
209+
The SDK provides structured exceptions with detailed error information:
210+
211+
```python
212+
from PowerPlatform.Dataverse.core.errors import (
213+
DataverseError,
214+
HttpError,
215+
ValidationError,
216+
MetadataError,
217+
SQLParseError
218+
)
219+
from PowerPlatform.Dataverse.client import DataverseClient
220+
221+
try:
222+
client.get("account", "invalid-id")
223+
except HttpError as e:
224+
print(f"HTTP {e.status_code}: {e.message}")
225+
print(f"Error code: {e.code}")
226+
print(f"Subcode: {e.subcode}")
227+
if e.is_transient:
228+
print("This error may be retryable")
229+
except ValidationError as e:
230+
print(f"Validation error: {e.message}")
231+
```
232+
233+
### Common Error Patterns
234+
235+
**Authentication failures:**
236+
- Check environment URL format (no trailing slash)
237+
- Verify credentials have Dataverse permissions
238+
- Ensure app registration is properly configured
239+
240+
**404 Not Found:**
241+
- Verify table schema name is correct (lowercase for standard tables)
242+
- Check record ID exists
243+
- Ensure using schema names, not display names
244+
- Cache issue could happen, so retry might help, especially for metadata creation
245+
246+
**400 Bad Request:**
247+
- Check filter/expand parameters use correct case
248+
- Verify column names exist and are spelled correctly
249+
- Ensure custom columns include customization prefix
250+
251+
## Best Practices
252+
253+
### Performance Optimization
254+
255+
1. **Use bulk operations** - Pass lists to create/update/delete for automatic optimization
256+
2. **Specify select fields** - Limit returned columns to reduce payload size
257+
3. **Control page size** - Use `top` and `page_size` parameters appropriately
258+
4. **Reuse client instances** - Don't create new clients for each operation
259+
5. **Use production credentials** - ClientSecretCredential or ClientCertificateCredential for unattended operations
260+
6. **Error handling** - Implement retry logic for transient errors (`e.is_transient`)
261+
7. **Always include customization prefix** for custom tables/columns
262+
8. **Use lowercase** - Generally using lower cased input won't go wrong, exception would be custom tables/columns naming
263+
9. **Test in non-production environments** first
264+
265+
## Additional Resources
266+
267+
Load these resources as needed during development:
268+
269+
- [API Reference](https://learn.microsoft.com/python/api/dataverse-sdk-docs-python/dataverse-overview)
270+
- [Product Documentation](https://learn.microsoft.com/power-apps/developer/data-platform/sdk-python/)
271+
- [Dataverse Web API](https://learn.microsoft.com/power-apps/developer/data-platform/webapi/)
272+
- [Azure Identity](https://learn.microsoft.com/python/api/overview/azure/identity-readme)
273+
274+
## Key Reminders
275+
276+
1. **Schema names are required** - Never use display names
277+
2. **Custom tables need prefixes** - Include customization prefix (e.g., "new_")
278+
3. **Filter is case-sensitive** - Use lowercase logical names
279+
4. **Bulk operations are encouraged** - Pass lists for optimization
280+
5. **No trailing slashes in URLs** - Format: `https://org.crm.dynamics.com`
281+
6. **Structured errors** - Check `is_transient` for retry logic

README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,21 @@ Install the PowerPlatform Dataverse Client using [pip](https://pypi.org/project/
5757
pip install PowerPlatform-Dataverse-Client
5858
```
5959

60-
For development from source:
60+
(Optional) Install Claude Skill globally with the Client:
61+
62+
```bash
63+
pip install PowerPlatform-Dataverse-Client && dataverse-install-claude-skill
64+
```
65+
66+
This installs a Claude Skill that enables Claude Code to:
67+
- Apply SDK best practices automatically
68+
- Provide context-aware code suggestions
69+
- Help with error handling and troubleshooting
70+
- Guide you through common patterns
71+
72+
The skill works with both the Claude Code CLI and VSCode extension. Once installed, Claude will automatically use it when working with Dataverse operations. For more information on Claude Skill see https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview. See skill definition here .claude\skills\dataverse-sdk\SKILL.md.
73+
74+
For development from source (Claude Skill auto loaded):
6175

6276
```bash
6377
git clone https://github.com/microsoft/PowerPlatform-DataverseClient-Python.git
@@ -225,6 +239,16 @@ table_info = client.create_table(
225239
primary_column_schema_name="new_ProductName" # Optional: custom primary column (default is "{customization prefix value}_Name")
226240
)
227241

242+
# Get table information
243+
info = client.get_table_info("new_Product")
244+
print(f"Logical name: {info['table_logical_name']}")
245+
print(f"Entity set: {info['entity_set_name']}")
246+
247+
# List all tables
248+
tables = client.list_tables()
249+
for table in tables:
250+
print(table)
251+
228252
# Add columns to existing table (columns must include customization prefix value)
229253
client.create_columns("new_Product", {"new_Category": "string"})
230254

@@ -302,9 +326,9 @@ except ValidationError as e:
302326

303327
### Authentication issues
304328

305-
**Common fixes:**
329+
**Common fixes:**
306330
- Verify environment URL format: `https://yourorg.crm.dynamics.com` (no trailing slash)
307-
- Ensure Azure Identity credentials have proper Dataverse permissions
331+
- Ensure Azure Identity credentials have proper Dataverse permissions
308332
- Check app registration permissions are granted and admin-consented
309333

310334
### Performance considerations
@@ -313,7 +337,7 @@ For optimal performance in production environments:
313337

314338
| Best Practice | Description |
315339
|---------------|-------------|
316-
| **Bulk Operations** | Pass lists to `create()`, `update()`, and `delete()` for automatic bulk processing |
340+
| **Bulk Operations** | Pass lists to `create()`, `update()` for automatic bulk processing, for `delete()`, set `use_bulk_delete` when passing lists to use bulk operation |
317341
| **Select Fields** | Specify `select` parameter to limit returned columns and reduce payload size |
318342
| **Page Size Control** | Use `top` and `page_size` parameters to control memory usage |
319343
| **Connection Reuse** | Reuse `DataverseClient` instances across operations |

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ dependencies = [
3737
"Issues" = "https://github.com/microsoft/PowerPlatform-DataverseClient-Python/issues"
3838
"Documentation" = "https://github.com/microsoft/PowerPlatform-DataverseClient-Python#readme"
3939

40+
[project.scripts]
41+
dataverse-install-claude-skill = "PowerPlatform.Dataverse._skill_installer:main"
42+
4043
[project.optional-dependencies]
4144
dev = [
4245
"pytest>=7.0.0",
@@ -58,6 +61,7 @@ namespaces = false
5861

5962
[tool.setuptools.package-data]
6063
"*" = ["py.typed"]
64+
"PowerPlatform.Dataverse.claude_skill" = ["SKILL.md"]
6165

6266
# Microsoft Python Standards - Linting & Formatting
6367
[tool.black]

0 commit comments

Comments
 (0)