You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Enhance OData client with support for include annotations and pagination options
- Added `include_annotations` parameter to `_RecordGet` and `_RecordList` classes for OData requests.
- Updated `_BatchClient` to handle new parameters in batch operations.
- Enhanced `_ODataClient` methods to support `include_annotations`, `expand`, `page_size`, and `count` parameters.
- Modified `BatchRecordOperations` to pass new parameters in batch record retrieval and listing methods.
- Updated `RecordOperations` to include new parameters for retrieving and listing records.
- Added unit tests to validate the new functionality for batch operations and record retrieval.
- Implemented migration tool updates to handle changes in method signatures and ensure backward compatibility.
Copy file name to clipboardExpand all lines: .claude/skills/dataverse-sdk-use/SKILL.md
+94-55Lines changed: 94 additions & 55 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,11 +28,12 @@ Use the PowerPlatform Dataverse Client Python SDK to interact with Microsoft Dat
28
28
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
29
29
30
30
### Paging
31
-
- Control page size with `page_size` parameter
31
+
- Control page size with `page_size` parameter on `records.list()`, `records.list_pages()`, or `QueryBuilder.page_size()`
32
32
- Use `top` parameter to limit total records returned
33
-
-Simple streaming: `records.list_pages(table, filter, select)` — yields one `QueryResult` per HTTP page (3 params only; use builder for advanced options)
34
-
-Advanced streaming: `client.query.builder(table)....execute_pages()` — full builder options, one `QueryResult` per page
33
+
-**Preferred**: `client.query.builder(table)....execute_pages()` — composable `where(col(...))` filters, formatted values, expand with nested selects, full pagination control
# Query with filter — follows @odata.nextLink automatically (multiple HTTP requests if needed),
102
-
# loads all matching records into memory, returns a single QueryResult.
103
-
# Page size is Dataverse's default (~5000/page); use top to bound total records and round-trips.
104
-
# For very large sets where memory is a concern, use records.list_pages() or execute_pages() instead.
105
-
result = client.records.list(
106
-
"account",
107
-
select=["accountid", "name"], # select is case-insensitive (automatically lowercased)
108
-
filter="statecode eq 0", # filter must use lowercase logical names (not transformed)
109
-
top=100, # bounds both total records returned and HTTP round-trips
110
-
)
102
+
# Simple shortcut — use records.list() only for basic filter + select without composable logic.
103
+
# Follows @odata.nextLink automatically and loads all matching records into memory.
104
+
# For filtering, sorting, expansion, or formatted values, prefer client.query.builder() (see below).
105
+
result = client.records.list("account", filter="statecode eq 0", select=["name", "accountid"])
111
106
for record in result:
112
107
print(record["name"])
108
+
```
113
109
114
-
# Simple streaming — page-by-page (3 params only; use builder for ordering/expand/count)
115
-
for page in client.records.list_pages(
116
-
"account",
117
-
select=["accountid", "name"],
118
-
filter="statecode eq 0",
119
-
):
120
-
for record in page:
121
-
print(record["name"])
110
+
#### Query Builder (Preferred for Filtering, Sorting, Expand, Formatted Values)
122
111
123
-
# Advanced streaming — full builder options, one QueryResult per HTTP page
112
+
Use `client.query.builder()` for any query that goes beyond simple filter + select. It provides composable `where(col(...))` expressions, formatted value support, nested expansion, and streaming — all with a fluent API.
113
+
114
+
```python
124
115
from PowerPlatform.Dataverse.models.filters import col
116
+
from PowerPlatform.Dataverse.models.query_builder import ExpandOption
117
+
118
+
# Basic query with composable filter and sort
119
+
result = (client.query.builder("account")
120
+
.select("accountid", "name", "statecode")
121
+
.where(col("statecode") ==0)
122
+
.order_by("name asc")
123
+
.execute())
124
+
for record in result:
125
+
print(record["name"])
126
+
127
+
# Composable filters — AND / OR / NOT using Python operators
The SDK provides DataFrame wrappers for all CRUD operations via the `client.dataframe` namespace, using pandas DataFrames and Series as input/output.
214
248
215
-
> **Note:**`client.dataframe.get()` is deprecated. Use `client.query.builder(table).select(...).where(...).to_dataframe()` instead.
249
+
> **Note:**`client.dataframe.get()` is deprecated. Use `client.query.builder(table).select(...).where(...).execute().to_dataframe()` instead. `QueryBuilder.to_dataframe()` (without `.execute()`) is also deprecated — always call `.execute()` first.
216
250
217
251
```python
218
252
import pandas as pd
219
253
220
-
# Query records -- returns a single DataFrame (GA builder pattern)
254
+
# Query records -- returns a single DataFrame (GA pattern: .execute().to_dataframe())
221
255
from PowerPlatform.Dataverse.models.filters import col
@@ -530,16 +567,17 @@ except ValidationError as e:
530
567
531
568
### Performance Optimization
532
569
533
-
1.**Use bulk operations** - Pass lists to create/update/delete for automatic optimization
534
-
2.**Specify select fields** - Limit returned columns to reduce payload size
535
-
3.**Control page size** - Use `top` and `page_size` parameters appropriately
536
-
4.**Reuse client instances** - Don't create new clients for each operation
537
-
5.**Use production credentials** - ClientSecretCredential or CertificateCredential for unattended operations
538
-
6.**Error handling** - Implement retry logic for transient errors (`e.is_transient`)
539
-
7.**Always include customization prefix** for custom tables/columns
540
-
8.**Use lowercase for column names, match `$metadata` for navigation properties** - Column names in `$select`/`$filter`/record payloads use lowercase LogicalNames. Navigation properties in `$expand` and `@odata.bind` keys are case-sensitive and must match the entity's `$metadata` (PascalCase for custom lookups like `new_CustomerId`, lowercase for system lookups like `parentaccountid`)
541
-
9.**Test in non-production environments** first
542
-
10.**Use named constants** - Import cascade behavior constants from `PowerPlatform.Dataverse.common.constants`
570
+
1.**Prefer `client.query.builder()` for any non-trivial query** — use the builder for filtering, sorting, expansion, or formatted values; `records.list()` is a convenience shortcut for simple filter+select only
571
+
2.**Use bulk operations** - Pass lists to create/update/delete for automatic optimization
572
+
3.**Specify select fields** - Limit returned columns to reduce payload size
573
+
4.**Control page size** - Use `top` and `page_size` parameters appropriately; use `execute_pages()` for large sets
574
+
5.**Reuse client instances** - Don't create new clients for each operation
575
+
6.**Use production credentials** - ClientSecretCredential or CertificateCredential for unattended operations
576
+
7.**Error handling** - Implement retry logic for transient errors (`e.is_transient`)
577
+
8.**Always include customization prefix** for custom tables/columns
578
+
9.**Use lowercase for column names, match `$metadata` for navigation properties** - Column names in `$select`/`$filter`/record payloads use lowercase LogicalNames. Navigation properties in `$expand` and `@odata.bind` keys are case-sensitive and must match the entity's `$metadata` (PascalCase for custom lookups like `new_CustomerId`, lowercase for system lookups like `parentaccountid`)
579
+
10.**Test in non-production environments** first
580
+
11.**Use named constants** - Import cascade behavior constants from `PowerPlatform.Dataverse.common.constants`
543
581
544
582
## Additional Resources
545
583
@@ -552,9 +590,10 @@ Load these resources as needed during development:
552
590
553
591
## Key Reminders
554
592
555
-
1.**Schema names are required** - Never use display names
556
-
2.**Custom tables need prefixes** - Include customization prefix (e.g., "new_")
557
-
3.**Filter is case-sensitive** - Use lowercase logical names
558
-
4.**Bulk operations are encouraged** - Pass lists for optimization
559
-
5.**No trailing slashes in URLs** - Format: `https://org.crm.dynamics.com`
560
-
6.**Structured errors** - Check `is_transient` for retry logic
593
+
1.**Use `client.query.builder()` for queries** — it's the primary query pattern; `records.list()` is a shortcut for trivial filter+select only
594
+
2.**Schema names are required** - Never use display names
595
+
3.**Custom tables need prefixes** - Include customization prefix (e.g., "new_")
596
+
4.**Filter is case-sensitive** - Use lowercase logical names
597
+
5.**Bulk operations are encouraged** - Pass lists for optimization
598
+
6.**No trailing slashes in URLs** - Format: `https://org.crm.dynamics.com`
599
+
7.**Structured errors** - Check `is_transient` for retry logic
Copy file name to clipboardExpand all lines: CHANGELOG.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,9 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
## [Unreleased]
9
9
10
10
### Added
11
-
-`client.records.retrieve(table, record_id)` — fetch a single record by GUID; returns `None` on 404 instead of raising (#175)
12
-
-`client.records.list(table, filter, select, top)` — eager fetch returning a flat `QueryResult`; GA replacement for `records.get()` without a record ID (#175)
13
-
-`client.records.list_pages(table, filter, select, top)` — lazy iterator yielding one `QueryResult` per HTTP page; streaming counterpart to `list()` (#175)
11
+
-`client.records.retrieve(table, record_id, *, select, include_annotations)` — fetch a single record by GUID; returns `None` on 404 instead of raising; `include_annotations` maps to the `Prefer: odata.include-annotations` header for formatted values and lookup labels (#175)
12
+
-`client.records.list(table, *, filter, select, top, orderby, expand, page_size, count, include_annotations)` — eager fetch returning a flat `QueryResult`; GA replacement for `records.get()` without a record ID; `page_size` controls `Prefer: odata.maxpagesize`, `count=True` adds `$count=true`, `include_annotations` requests formatted values (#175)
13
+
-`client.records.list_pages(table, *, filter, select, top, orderby, expand, page_size, count, include_annotations)` — lazy iterator yielding one `QueryResult` per HTTP page; streaming counterpart to `list()`; same parameter set (#175)
14
+
-`client.batch.records.retrieve()` and `client.batch.records.list()` now accept the same `include_annotations`, `orderby`, `expand`, `page_size`, and `count` parameters as their non-batch counterparts (#175)
14
15
-`client.query.fetchxml(xml)` — FetchXML support returning an inert `FetchXmlQuery`; no HTTP request is made until `.execute()` or `.execute_pages()` is called (#175)
15
16
-`FetchXmlQuery` implements the correct Dataverse paging cookie algorithm: annotation parsed as outer XML, `pagingcookie` attribute double URL-decoded, server-supplied `pagenumber` used for next page, `morerecords` handled as both `bool` and `"true"` string, `UserWarning` emitted on simple paging fallback, 32,768-character URL limit enforced (documented Dataverse GET cap), 10,000-page circuit breaker against runaway iteration (#175)
16
17
-`QueryBuilder.execute_pages()` — lazy per-page streaming returning one `QueryResult` per HTTP page; replaces deprecated `execute(by_page=True)` (#175)
@@ -19,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
20
-`DataverseModel` structural `Protocol` (`models/protocol.py`) — implement on any entity class to enable typed integration with CRUD operations without specifying table names or serializing manually (#175)
20
21
-`col()`, `raw()`, `QueryResult`, and `DataverseModel` exported from the top-level `PowerPlatform.Dataverse` package (#175)
21
22
- v0→v1 migration tool: `tools/migrate_v0_to_v1.py` rewrites v0 call sites to the v1 API with `--dry-run` support; covers `create`, `update`, `delete`, `get`, `list`, `fetchxml`, and query builder patterns (#175)
23
+
- Migration tool now auto-rewrites `QueryBuilder.to_dataframe()` → `.execute().to_dataframe()` (inserts `.execute()` when receiver is a recognised builder chain); output improved with `[NEEDS-MANUAL]` label for files that have no auto-rewrites but require manual attention, and a trailing note on `[MIGRATED]` lines when manual items remain (#175)
22
24
23
25
### Changed
24
26
-`QueryBuilder.execute()` now returns a flat `QueryResult` (all pages collected eagerly) instead of `Iterable[Record]` (#175)
0 commit comments