Skip to content
57 changes: 37 additions & 20 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,49 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.0.0] - 2026-05-28

### Breaking Changes

- Previously deprecated flat methods on `DataverseClient` (`create`, `update`, `delete`, `get`, `list`, `query_sql`, `upload_file`, etc.) are now removed; use the namespaced operations: `client.records`, `client.query`, `client.tables`, `client.files`, `client.batch` (#175)
- `client.query.sql_select()`, `client.query.sql_joins()`, `client.query.sql_join()` are removed (#175)
- `QueryBuilder.execute()` now returns a flat `QueryResult` instead of `Iterable[Record]`; use `execute_pages()` for lazy iteration (#175)

Run `dataverse-migrate --dry-run .` to automatically rewrite v0 call sites (`pip install PowerPlatform-Dataverse-Client[migration]`).

### Added
- `client.records.retrieve(table, record_id, *, select, expand, include_annotations)` — fetch a single record by GUID; returns `None` on 404 instead of raising; `expand` adds `$expand` for navigation property expansion on the single-record GET; `include_annotations` maps to the `Prefer: odata.include-annotations` header for formatted values and lookup labels (#175)
- `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)
- `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)
- `client.query.fetchxml(xml)` — FetchXML support returning an inert `FetchXmlQuery`; no HTTP request is made until `.execute()` or `.execute_pages()` is called (#175)
- `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)
- `QueryBuilder.execute_pages()` — lazy per-page streaming returning one `QueryResult` per HTTP page; replaces deprecated `execute(by_page=True)` (#175)
- `QueryBuilder.where()` — composable filter expressions using `col()` and Python operators (`==`, `>`, `&`, `|`, `~`); replaces deprecated `filter_eq()`, `filter_contains()`, and other `filter_*` helpers (#175)
- `QueryResult.__getitem__` — index access (`result[0]`) returns a `Record`; slice access (`result[1:5]`) returns a new `QueryResult` (#175)
- `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)
- `col()`, `raw()`, `QueryResult`, and `DataverseModel` exported from the top-level `PowerPlatform.Dataverse` package (#175)
- v0→v1 migration tool: installed as the `dataverse-migrate` console script (also runnable via `python -m PowerPlatform.Dataverse.migration.migrate_v0_to_v1`); rewrites v0 call sites to the v1 API with `--dry-run` support; covers `create`, `update`, `delete`, `get`, `list`, `fetchxml`, and query builder patterns; requires the `[migration]` optional extra (`pip install PowerPlatform-Dataverse-Client[migration]`) (#175)
- 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)

- **Async client** — `AsyncDataverseClient` with full feature parity to the sync SDK; all operation namespaces available with `async with` lifecycle management. Install with `pip install PowerPlatform-Dataverse-Client[async]` (#171)
- **Single-record fetch** — `client.records.retrieve(table, record_id, *, select, expand, include_annotations)` returns `None` on 404 instead of raising; `expand` adds `$expand` for navigation-property expansion on the single-record GET; `include_annotations` maps to the `Prefer: odata.include-annotations` header for formatted values and lookup labels (#175)
- **Multi-record fetch** — `client.records.list(table, *, filter, select, top, orderby, expand, page_size, count, include_annotations)` returns an eager 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)
- **Streaming multi-record fetch** — `client.records.list_pages(...)` yields one `QueryResult` per HTTP page; streaming counterpart to `list()` with the same parameter set (#175)
- **FetchXML** — `client.query.fetchxml(xml)` returns an inert `FetchXmlQuery`; no HTTP call until `.execute()` or `.execute_pages()`. Paging implements the documented Dataverse algorithm: annotation parsed as outer XML, `pagingcookie` attribute double URL-decoded, server-supplied `pagenumber` used for the 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)
- **Streaming QueryBuilder** — `QueryBuilder.execute_pages()` lazily yields one `QueryResult` per HTTP page; replaces deprecated `execute(by_page=True)` (#175)
- **Composable filters** — `QueryBuilder.where(col("name") == "Contoso")` with Python operators (`==`, `>`, `&`, `|`, `~`); replaces the deprecated `filter_eq()`, `filter_contains()`, and other `filter_*` helpers (#175)
- **`QueryResult` indexing** — `result[0]` returns a `Record`; `result[1:5]` returns a new `QueryResult` (#175)
- **`DataverseModel` protocol** — structural `Protocol` in `models/protocol.py` for typed entity classes; enables CRUD operations without manual table names or dict serialization (#175)
- **Top-level re-exports** — `col()`, `raw()`, `QueryResult`, and `DataverseModel` importable directly from the top-level `PowerPlatform.Dataverse` package (#175)
- **Shorter imports** — public types (`Record`, `DataverseError`, `QueryBuilder`, `BatchResult`, and others) now importable directly from `PowerPlatform.Dataverse.models`, `.core`, and `.operations` (#165)
- **Migration tool** — installed as the `dataverse-migrate` console script (also runnable via `python -m PowerPlatform.Dataverse.migration.migrate_v0_to_v1`); rewrites v0 call sites to the v1 API with `--dry-run` support; covers `create`, `update`, `delete`, `get`, `list`, `fetchxml`, and query-builder patterns; emits a `[NEEDS-MANUAL]` label for files with no auto-rewrites but manual attention needed, and appends a trailing note on `[MIGRATED]` lines when manual items remain. Requires the `[migration]` optional extra (`pip install PowerPlatform-Dataverse-Client[migration]`) (#175)

### Changed
- `QueryBuilder.execute()` now returns a flat `QueryResult` (all pages collected eagerly) instead of `Iterable[Record]` (#175)

- `records.get()` deprecation extended: calling with a `record_id` emits `DeprecationWarning` directing callers to `retrieve()`; calling without a `record_id` directs callers to `list()` (#175)
- `OperationContext` now validates keys and values against an allowlist; unknown keys or non-conforming values raise `ValidationError`, preventing PII from reaching the `User-Agent` header (#181)
- Table creation now uses the `CreateEntities` API, improving performance (#183)
- `float`/`double` column precision default raised from 2 to 5 decimal places, preventing silent truncation of values like `2.718` (#185)
- Server inner error messages now surface in `HttpError.message` on both single-request and batch paths (#185)
- `pandas.DataFrame` with MultiIndex columns now raises a descriptive error with a flatten hint instead of failing deep in serialization (#185)

### Fixed

- Client creation no longer errors on Python 3.10 and 3.11 (#188)
- SQL guardrails now catch write statements hidden inside comments, string literals, or zero-width prefixes (#185)

### Deprecated
- `QueryBuilder.execute(by_page=True)` and `execute(by_page=False)` emit `UserWarning`; use `execute_pages()` and `execute()` respectively (#175)
- `client.query.odata_select()`, `client.query.odata_expands()`, `client.query.odata_expand()`, `client.query.odata_bind()` emit `DeprecationWarning`; navigation property helpers are replaced by `QueryBuilder.expand()` (#175)

### Removed
- All v0 flat methods on `DataverseClient` (`create`, `update`, `delete`, `get`, `list`, `query_sql`, etc.) removed (~570 lines); use the `client.records`, `client.query`, and `client.batch` namespaces (#175)
- `client.query.sql_select()`, `client.query.sql_joins()`, `client.query.sql_join()` removed (#175)
- `QueryBuilder.execute(by_page=True)` and `execute(by_page=False)` emit `UserWarning`; use `execute_pages()` and `execute()` respectively (#175)
- `client.query.odata_select()`, `client.query.odata_expands()`, `client.query.odata_expand()`, `client.query.odata_bind()` emit `DeprecationWarning`; navigation-property helpers are replaced by `QueryBuilder.expand()` (#175)

## [0.1.0b10] - 2026-05-12

Expand Down Expand Up @@ -156,7 +172,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Comprehensive error handling with specific exception types (`DataverseError`, `AuthenticationError`, etc.) (#22, #24)
- HTTP retry logic with exponential backoff for resilient operations (#72)

[Unreleased]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b10...HEAD
[Unreleased]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b10...v1.0.0
[0.1.0b10]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b9...v0.1.0b10
[0.1.0b9]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b8...v0.1.0b9
[0.1.0b8]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b7...v0.1.0b8
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "PowerPlatform-Dataverse-Client"
version = "0.1.0b11"
version = "1.0.0"
Comment thread
abelmilash-msft marked this conversation as resolved.
description = "Python SDK for Microsoft Dataverse"
readme = {file = "README.md", content-type = "text/markdown"}
authors = [{name = "Microsoft Corporation"}]
Expand All @@ -13,7 +13,7 @@ license-files = ["LICENSE"]
requires-python = ">=3.10"
keywords = ["dataverse", "powerapps", "powerplatform", "crm", "dynamics", "odata"]
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
Expand Down
Loading