Skip to content

Commit c4d5614

Browse files
Abel Milashclaude
andcommitted
Add async client documentation to README and SDK use skill
Documents AsyncDataverseClient installation, quick start, query builder, batch/changesets, and DataFrame patterns in README.md and both copies of the dataverse-sdk-use SKILL.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9cb9d4a commit c4d5614

3 files changed

Lines changed: 320 additions & 5 deletions

File tree

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,115 @@ except ValidationError as e:
588588
10. **Test in non-production environments** first
589589
11. **Use named constants** - Import cascade behavior constants from `PowerPlatform.Dataverse.common.constants`
590590

591+
## Async Client
592+
593+
The SDK ships a full async client, `AsyncDataverseClient`, under `PowerPlatform.Dataverse.aio`. Requires the `[async]` extra: `pip install "PowerPlatform-Dataverse-Client[async]"`.
594+
595+
### Import
596+
```python
597+
from azure.identity.aio import InteractiveBrowserCredential # or ClientSecretCredential, etc.
598+
from PowerPlatform.Dataverse.aio.async_client import AsyncDataverseClient
599+
```
600+
601+
### Client Initialization
602+
```python
603+
# Context manager (recommended -- closes session and clears caches automatically)
604+
async with AsyncDataverseClient("https://yourorg.crm.dynamics.com", credential) as client:
605+
... # all operations here
606+
607+
# Standalone (call aclose() in a finally block)
608+
client = AsyncDataverseClient("https://yourorg.crm.dynamics.com", credential)
609+
try:
610+
...
611+
finally:
612+
await client.aclose()
613+
```
614+
615+
### CRUD Operations
616+
Every sync method has an async equivalent -- add `await`:
617+
```python
618+
# Create
619+
account_id = await client.records.create("account", {"name": "Contoso Ltd"})
620+
621+
# Read
622+
account = await client.records.retrieve("account", account_id, select=["name", "telephone1"])
623+
624+
# Update
625+
await client.records.update("account", account_id, {"telephone1": "555-0200"})
626+
627+
# Delete
628+
await client.records.delete("account", account_id)
629+
630+
# Bulk create
631+
ids = await client.records.create("account", [{"name": "A"}, {"name": "B"}])
632+
```
633+
634+
### Query Builder
635+
```python
636+
from PowerPlatform.Dataverse.models.filters import col
637+
638+
# Collect all results
639+
result = await (
640+
client.query.builder("account")
641+
.select("name", "telephone1")
642+
.where(col("statecode") == 0)
643+
.top(10)
644+
.execute()
645+
)
646+
for record in result:
647+
print(record["name"])
648+
649+
# Lazy page iteration (memory-efficient)
650+
async for page in (
651+
client.query.builder("account")
652+
.select("name")
653+
.page_size(500)
654+
.execute_pages()
655+
):
656+
for record in page:
657+
print(record["name"])
658+
659+
# SQL query
660+
rows = await client.query.sql("SELECT TOP 5 name FROM account")
661+
662+
# FetchXML
663+
xml = '<fetch top="5"><entity name="account"><attribute name="name"/></entity></fetch>'
664+
rows = await client.query.fetchxml(xml).execute()
665+
```
666+
667+
### Batch and Changesets
668+
```python
669+
# Plain batch
670+
batch = client.batch.new()
671+
batch.records.create("account", {"name": "Alpha"})
672+
result = await batch.execute()
673+
674+
# Atomic changeset
675+
batch = client.batch.new()
676+
async with batch.changeset() as cs:
677+
ref = cs.records.create("contact", {"firstname": "Alice"})
678+
cs.records.update("account", account_id, {"primarycontactid@odata.bind": ref})
679+
result = await batch.execute()
680+
```
681+
682+
### DataFrame Operations
683+
```python
684+
import pandas as pd
685+
686+
# Query to DataFrame
687+
result = await (
688+
client.query.builder("account")
689+
.select("name", "telephone1")
690+
.where(col("statecode") == 0)
691+
.execute()
692+
)
693+
df = result.to_dataframe()
694+
695+
# Create from DataFrame
696+
new_accounts = pd.DataFrame([{"name": "Contoso"}, {"name": "Fabrikam"}])
697+
ids = await client.dataframe.create("account", new_accounts)
698+
```
699+
591700
## Additional Resources
592701

593702
Load these resources as needed during development:

README.md

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A Python client library for Microsoft Dataverse that provides a unified interfac
1616
- [Key features](#key-features)
1717
- [Getting started](#getting-started)
1818
- [Prerequisites](#prerequisites)
19-
- [Install the package](#install-the-package)
19+
- [Install the package](#install-the-package)
2020
- [Authenticate the client](#authenticate-the-client)
2121
- [Key concepts](#key-concepts)
2222
- [Examples](#examples)
@@ -30,6 +30,7 @@ A Python client library for Microsoft Dataverse that provides a unified interfac
3030
- [Relationship management](#relationship-management)
3131
- [File operations](#file-operations)
3232
- [Batch operations](#batch-operations)
33+
- [Async client](#async-client)
3334
- [Next steps](#next-steps)
3435
- [Troubleshooting](#troubleshooting)
3536
- [Contributing](#contributing)
@@ -53,7 +54,7 @@ A Python client library for Microsoft Dataverse that provides a unified interfac
5354

5455
### Prerequisites
5556

56-
- **Python 3.10+** (3.10, 3.11, 3.12, 3.13 supported)
57+
- **Python 3.10+** (3.10, 3.11, 3.12, 3.13 supported)
5758
- **Microsoft Dataverse environment** with appropriate permissions
5859
- **OAuth authentication configured** for your application
5960

@@ -92,7 +93,7 @@ The client requires any Azure Identity `TokenCredential` implementation for OAut
9293

9394
```python
9495
from azure.identity import (
95-
InteractiveBrowserCredential,
96+
InteractiveBrowserCredential,
9697
ClientSecretCredential,
9798
CertificateCredential,
9899
AzureCliCredential
@@ -103,7 +104,7 @@ from PowerPlatform.Dataverse.client import DataverseClient
103104
credential = InteractiveBrowserCredential() # Browser authentication
104105
# credential = AzureCliCredential() # If logged in via 'az login'
105106

106-
# Production options
107+
# Production options
107108
# credential = ClientSecretCredential(tenant_id, client_id, client_secret)
108109
# credential = CertificateCredential(tenant_id, client_id, cert_path)
109110

@@ -783,6 +784,102 @@ result = batch.execute()
783784

784785
For a complete example see [examples/advanced/batch.py](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/batch.py).
785786

787+
## Async client
788+
789+
The SDK ships a full async client, `AsyncDataverseClient`, for use in async applications. It mirrors every operation of the sync client — the same namespaces (`records`, `query`, `tables`, `files`, `batch`), the same method signatures, and the same return types.
790+
791+
### Install
792+
793+
The async client requires `aiohttp`, which is an optional extra:
794+
795+
```bash
796+
pip install "PowerPlatform-Dataverse-Client[async]"
797+
```
798+
799+
### Quick start
800+
801+
```python
802+
import asyncio
803+
from azure.identity.aio import InteractiveBrowserCredential
804+
from PowerPlatform.Dataverse.aio.async_client import AsyncDataverseClient
805+
806+
async def main():
807+
credential = InteractiveBrowserCredential()
808+
try:
809+
async with AsyncDataverseClient("https://yourorg.crm.dynamics.com", credential) as client:
810+
# Create a contact
811+
contact_id = await client.records.create("contact", {"firstname": "John", "lastname": "Doe"})
812+
813+
# Read it back
814+
contact = await client.records.retrieve("contact", contact_id, select=["firstname", "lastname"])
815+
print(f"Created: {contact['firstname']} {contact['lastname']}")
816+
817+
# Clean up
818+
await client.records.delete("contact", contact_id)
819+
finally:
820+
await credential.close()
821+
822+
asyncio.run(main())
823+
```
824+
825+
### Standalone usage (without `async with`)
826+
827+
```python
828+
client = AsyncDataverseClient("https://yourorg.crm.dynamics.com", credential)
829+
try:
830+
account_id = await client.records.create("account", {"name": "Contoso Ltd"})
831+
finally:
832+
await client.aclose()
833+
```
834+
835+
### Query builder
836+
837+
The async query builder API is identical to the sync one:
838+
839+
```python
840+
from PowerPlatform.Dataverse.models.filters import col
841+
842+
# Execute and collect all results
843+
result = await (
844+
client.query.builder("account")
845+
.select("name", "telephone1")
846+
.where(col("statecode") == 0)
847+
.top(10)
848+
.execute()
849+
)
850+
for record in result:
851+
print(record["name"])
852+
853+
# Lazy page-by-page iteration (memory-efficient for large sets)
854+
async for page in (
855+
client.query.builder("account")
856+
.select("name")
857+
.page_size(500)
858+
.execute_pages()
859+
):
860+
for record in page:
861+
print(record["name"])
862+
```
863+
864+
### Batch and changesets
865+
866+
```python
867+
batch = client.batch.new()
868+
batch.records.create("account", {"name": "Alpha"})
869+
batch.records.create("account", {"name": "Beta"})
870+
result = await batch.execute()
871+
print(f"Created {len(list(result.entity_ids))} records")
872+
873+
# Atomic changeset
874+
batch = client.batch.new()
875+
async with batch.changeset() as cs:
876+
ref = cs.records.create("contact", {"firstname": "Alice"})
877+
cs.records.update("account", account_id, {"primarycontactid@odata.bind": ref})
878+
result = await batch.execute()
879+
```
880+
881+
See [examples/aio/](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/tree/main/examples/aio) for async equivalents of all sync examples.
882+
786883
## Next steps
787884

788885
### More sample code
@@ -808,7 +905,7 @@ For comprehensive information on Microsoft Dataverse and related technologies:
808905
| Resource | Description |
809906
|----------|-------------|
810907
| **[Dataverse Developer Guide](https://learn.microsoft.com/power-apps/developer/data-platform/)** | Complete developer documentation for Microsoft Dataverse |
811-
| **[Dataverse Web API Reference](https://learn.microsoft.com/power-apps/developer/data-platform/webapi/)** | Detailed Web API reference and examples |
908+
| **[Dataverse Web API Reference](https://learn.microsoft.com/power-apps/developer/data-platform/webapi/)** | Detailed Web API reference and examples |
812909
| **[Azure Identity for Python](https://learn.microsoft.com/python/api/overview/azure/identity-readme)** | Authentication library documentation and credential types |
813910
| **[Power Platform Developer Center](https://learn.microsoft.com/power-platform/developer/)** | Broader Power Platform development resources |
814911
| **[Dataverse SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/overview)** | Official .NET SDK for Microsoft Dataverse |

src/PowerPlatform/Dataverse/claude_skill/dataverse-sdk-use/SKILL.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,115 @@ except ValidationError as e:
588588
10. **Test in non-production environments** first
589589
11. **Use named constants** - Import cascade behavior constants from `PowerPlatform.Dataverse.common.constants`
590590

591+
## Async Client
592+
593+
The SDK ships a full async client, `AsyncDataverseClient`, under `PowerPlatform.Dataverse.aio`. Requires the `[async]` extra: `pip install "PowerPlatform-Dataverse-Client[async]"`.
594+
595+
### Import
596+
```python
597+
from azure.identity.aio import InteractiveBrowserCredential # or ClientSecretCredential, etc.
598+
from PowerPlatform.Dataverse.aio.async_client import AsyncDataverseClient
599+
```
600+
601+
### Client Initialization
602+
```python
603+
# Context manager (recommended -- closes session and clears caches automatically)
604+
async with AsyncDataverseClient("https://yourorg.crm.dynamics.com", credential) as client:
605+
... # all operations here
606+
607+
# Standalone (call aclose() in a finally block)
608+
client = AsyncDataverseClient("https://yourorg.crm.dynamics.com", credential)
609+
try:
610+
...
611+
finally:
612+
await client.aclose()
613+
```
614+
615+
### CRUD Operations
616+
Every sync method has an async equivalent -- add `await`:
617+
```python
618+
# Create
619+
account_id = await client.records.create("account", {"name": "Contoso Ltd"})
620+
621+
# Read
622+
account = await client.records.retrieve("account", account_id, select=["name", "telephone1"])
623+
624+
# Update
625+
await client.records.update("account", account_id, {"telephone1": "555-0200"})
626+
627+
# Delete
628+
await client.records.delete("account", account_id)
629+
630+
# Bulk create
631+
ids = await client.records.create("account", [{"name": "A"}, {"name": "B"}])
632+
```
633+
634+
### Query Builder
635+
```python
636+
from PowerPlatform.Dataverse.models.filters import col
637+
638+
# Collect all results
639+
result = await (
640+
client.query.builder("account")
641+
.select("name", "telephone1")
642+
.where(col("statecode") == 0)
643+
.top(10)
644+
.execute()
645+
)
646+
for record in result:
647+
print(record["name"])
648+
649+
# Lazy page iteration (memory-efficient)
650+
async for page in (
651+
client.query.builder("account")
652+
.select("name")
653+
.page_size(500)
654+
.execute_pages()
655+
):
656+
for record in page:
657+
print(record["name"])
658+
659+
# SQL query
660+
rows = await client.query.sql("SELECT TOP 5 name FROM account")
661+
662+
# FetchXML
663+
xml = '<fetch top="5"><entity name="account"><attribute name="name"/></entity></fetch>'
664+
rows = await client.query.fetchxml(xml).execute()
665+
```
666+
667+
### Batch and Changesets
668+
```python
669+
# Plain batch
670+
batch = client.batch.new()
671+
batch.records.create("account", {"name": "Alpha"})
672+
result = await batch.execute()
673+
674+
# Atomic changeset
675+
batch = client.batch.new()
676+
async with batch.changeset() as cs:
677+
ref = cs.records.create("contact", {"firstname": "Alice"})
678+
cs.records.update("account", account_id, {"primarycontactid@odata.bind": ref})
679+
result = await batch.execute()
680+
```
681+
682+
### DataFrame Operations
683+
```python
684+
import pandas as pd
685+
686+
# Query to DataFrame
687+
result = await (
688+
client.query.builder("account")
689+
.select("name", "telephone1")
690+
.where(col("statecode") == 0)
691+
.execute()
692+
)
693+
df = result.to_dataframe()
694+
695+
# Create from DataFrame
696+
new_accounts = pd.DataFrame([{"name": "Contoso"}, {"name": "Fabrikam"}])
697+
ids = await client.dataframe.create("account", new_accounts)
698+
```
699+
591700
## Additional Resources
592701

593702
Load these resources as needed during development:

0 commit comments

Comments
 (0)