Skip to content

Commit 3ed77be

Browse files
author
Samson Gebre
committed
Add unit tests for records.list_pages() and migration script for v0 to v1
- Implemented unit tests for the `list_pages` method in `TestListPages` class, covering various scenarios including iterator return, page content validation, and parameter passing. - Added checks for deprecation warnings to ensure no warnings are raised during the usage of `list_pages`. - Introduced a new migration script `migrate_v0_to_v1.py` to automate the transition from beta (v0) to GA (v1) API calls, including method renaming and argument adjustments. - Created a new `tools` directory to house the migration script.
1 parent 3765ee2 commit 3ed77be

39 files changed

Lines changed: 6059 additions & 2849 deletions

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

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The SDK supports Dataverse's native bulk operations: Pass lists to `create()`, `
3232
- Use `top` parameter to limit total records returned
3333

3434
### DataFrame Support
35-
- DataFrame operations are accessed via the `client.dataframe` namespace: `client.dataframe.get()`, `client.dataframe.create()`, `client.dataframe.update()`, `client.dataframe.delete()`
35+
- DataFrame operations are accessed via the `client.dataframe` namespace: `client.dataframe.create()`, `client.dataframe.update()`, `client.dataframe.delete()``client.dataframe.get()` is deprecated; use `client.query.builder(table).where(...).execute().to_dataframe()` instead
3636

3737
## Common Operations
3838

@@ -85,28 +85,40 @@ contact_ids = client.records.create("contact", contacts)
8585
#### Read Records
8686
```python
8787
# Get single record by ID
88-
account = client.records.get("account", account_id, select=["name", "telephone1"])
88+
account = client.records.retrieve("account", account_id, select=["name", "telephone1"])
8989

90-
# Query with filter (paginated)
91-
for page in client.records.get(
90+
# Query with filter — follows @odata.nextLink automatically (multiple HTTP requests if needed),
91+
# loads all matching records into memory, returns a single QueryResult.
92+
# Page size is Dataverse's default (~5000/page); use top to bound total records and round-trips.
93+
# For very large sets where memory is a concern, use query.builder().execute(by_page=True) instead.
94+
result = client.records.list(
9295
"account",
9396
select=["accountid", "name"], # select is case-insensitive (automatically lowercased)
9497
filter="statecode eq 0", # filter must use lowercase logical names (not transformed)
95-
top=100,
96-
):
98+
top=100, # bounds both total records returned and HTTP round-trips
99+
)
100+
for record in result:
101+
print(record["name"])
102+
103+
# For large result sets that must be streamed page-by-page (caller controls memory, one page at a time)
104+
for page in (client.query.builder("account")
105+
.select("accountid", "name")
106+
.where(col("statecode") == 0)
107+
.page_size(500) # optional: override Dataverse default page size
108+
.execute(by_page=True)):
97109
for record in page:
98110
print(record["name"])
99111

100-
# Query with navigation property expansion (case-sensitive!)
101-
for page in client.records.get(
102-
"account",
103-
select=["name"],
104-
expand=["primarycontactid"], # Navigation properties are case-sensitive!
105-
filter="statecode eq 0", # Column names must be lowercase logical names
106-
):
107-
for account in page:
108-
contact = account.get("primarycontactid", {})
109-
print(f"{account['name']} - {contact.get('fullname', 'N/A')}")
112+
# Query with navigation property expansion — use the query builder (records.list() has no expand)
113+
from PowerPlatform.Dataverse.models.query_builder import ExpandOption
114+
from PowerPlatform.Dataverse.models.filters import col
115+
for record in (client.query.builder("account")
116+
.select("name")
117+
.expand(ExpandOption("primarycontactid").select("fullname"))
118+
.where(col("statecode") == 0)
119+
.execute()):
120+
contact = record.get("primarycontactid", {})
121+
print(f"{record['name']} - {contact.get('fullname', 'N/A')}")
110122
```
111123

112124
#### Create Records with Lookup Bindings (@odata.bind)
@@ -179,18 +191,20 @@ client.records.delete("account", [id1, id2, id3], use_bulk_delete=True)
179191

180192
The SDK provides DataFrame wrappers for all CRUD operations via the `client.dataframe` namespace, using pandas DataFrames and Series as input/output.
181193

194+
> **Note:** `client.dataframe.get()` is deprecated. Use `client.query.builder(table).select(...).filter(...).to_dataframe()` instead.
195+
182196
```python
183197
import pandas as pd
184198

185-
# Query records -- returns a single DataFrame
186-
df = client.dataframe.get("account", filter="statecode eq 0", select=["name"])
199+
# Query records -- returns a single DataFrame (GA builder pattern)
200+
df = client.query.builder("account").filter("statecode eq 0").select("name").to_dataframe()
187201
print(f"Got {len(df)} rows")
188202

189-
# Limit results with top for large tables
190-
df = client.dataframe.get("account", select=["name"], top=100)
203+
# Limit results with top
204+
df = client.query.builder("account").select("name").top(100).to_dataframe()
191205

192206
# Fetch single record as one-row DataFrame
193-
df = client.dataframe.get("account", record_id=account_id, select=["name"])
207+
df = client.records.retrieve("account", account_id, select=["name"]).to_dataframe()
194208

195209
# Create records from a DataFrame (returns a Series of GUIDs)
196210
new_accounts = pd.DataFrame([
@@ -380,7 +394,8 @@ Use `client.batch` to send multiple operations in one HTTP request. All batch me
380394
batch = client.batch.new()
381395
batch.records.create("account", {"name": "Contoso"})
382396
batch.records.update("account", account_id, {"telephone1": "555-0100"})
383-
batch.records.get("account", account_id, select=["name"])
397+
batch.records.retrieve("account", account_id, select=["name"]) # single record (GA)
398+
batch.records.list("account", filter="statecode eq 0", top=50) # multi-record, single page
384399
batch.query.sql("SELECT TOP 5 name FROM account")
385400

386401
result = batch.execute()
@@ -412,7 +427,8 @@ print(f"Succeeded: {len(result.succeeded)}, Failed: {len(result.failed)}")
412427

413428
**Batch limitations:**
414429
- Maximum 1000 operations per batch
415-
- Paginated `records.get()` (without `record_id`) is not supported in batch
430+
- `batch.records.get()` is deprecated; use `batch.records.retrieve()` for single records
431+
- `batch.records.list()` returns a single page (no pagination); use `top` to bound results
416432
- `flush_cache()` is not supported in batch
417433

418434
## Error Handling
@@ -430,7 +446,7 @@ from PowerPlatform.Dataverse.core.errors import (
430446
from PowerPlatform.Dataverse.client import DataverseClient
431447

432448
try:
433-
client.records.get("account", "invalid-id")
449+
client.records.retrieve("account", "invalid-id")
434450
except HttpError as e:
435451
print(f"HTTP {e.status_code}: {e.message}")
436452
print(f"Error code: {e.code}")

0 commit comments

Comments
 (0)