Skip to content

Commit a3c6e55

Browse files
Merge branch 'main' into feature/alternate-keys
2 parents 6ceb1ed + c61c94a commit a3c6e55

7 files changed

Lines changed: 48 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.0b5] - 2026-02-27
9+
10+
### Fixed
11+
- UpsertMultiple: exclude alternate key fields from request body (#127). The create path of UpsertMultiple failed with `400 Bad Request` when alternate key column values appeared in both the body and `@odata.id`.
12+
813
## [0.1.0b4] - 2026-02-25
914

1015
### Added
@@ -65,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6570
- Comprehensive error handling with specific exception types (`DataverseError`, `AuthenticationError`, etc.) (#22, #24)
6671
- HTTP retry logic with exponential backoff for resilient operations (#72)
6772

73+
[0.1.0b5]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b4...v0.1.0b5
6874
[0.1.0b4]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b3...v0.1.0b4
6975
[0.1.0b3]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b2...v0.1.0b3
7076
[0.1.0b2]: https://github.com/microsoft/PowerPlatform-DataverseClient-Python/compare/v0.1.0b1...v0.1.0b2

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,6 @@ published release:
119119

120120
```bash
121121
# After publishing v0.1.0b4, bump to v0.1.0b5 on main
122-
# Update both pyproject.toml and src/PowerPlatform/Dataverse/__version__.py
122+
# Update version in pyproject.toml
123123
# Commit directly to main: "Bump version to 0.1.0b5 for next development cycle"
124124
```

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "PowerPlatform-Dataverse-Client"
7-
version = "0.1.0b5"
7+
version = "0.1.0b6"
88
description = "Python SDK for Microsoft Dataverse"
99
readme = {file = "README.md", content-type = "text/markdown"}
1010
authors = [{name = "Microsoft Corporation"}]
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT license.
33

4-
from .__version__ import __version__
4+
from importlib.metadata import version
5+
6+
__version__ = version("PowerPlatform-Dataverse-Client")
57

68
__all__ = ["__version__"]

src/PowerPlatform/Dataverse/__version__.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/PowerPlatform/Dataverse/data/_odata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
VALIDATION_UNSUPPORTED_CACHE_KIND,
3636
)
3737

38-
from ..__version__ import __version__ as _SDK_VERSION
38+
from .. import __version__ as _SDK_VERSION
3939

4040
_USER_AGENT = f"DataverseSvcPythonClient:{_SDK_VERSION}"
4141
_GUID_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")

tests/unit/data/test_odata_internal.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,42 @@ def test_equal_lengths_does_not_raise(self):
5757
self.assertEqual(len(post_calls), 1)
5858
self.assertIn("UpsertMultiple", post_calls[0].args[1])
5959

60+
def test_payload_excludes_alternate_key_fields(self):
61+
"""Alternate key fields must NOT appear in the request body (only in @odata.id)."""
62+
self.od._upsert_multiple(
63+
"accounts",
64+
"account",
65+
[{"accountnumber": "ACC-001"}],
66+
[{"name": "Contoso", "telephone1": "555-0100"}],
67+
)
68+
post_calls = [c for c in self.od._request.call_args_list if c.args[0] == "post"]
69+
self.assertEqual(len(post_calls), 1)
70+
payload = post_calls[0].kwargs.get("json", {})
71+
target = payload["Targets"][0]
72+
# accountnumber should only be in @odata.id, NOT as a body field
73+
self.assertNotIn("accountnumber", target)
74+
self.assertIn("name", target)
75+
self.assertIn("telephone1", target)
76+
self.assertIn("@odata.id", target)
77+
self.assertIn("accountnumber", target["@odata.id"])
78+
79+
def test_payload_excludes_alternate_key_even_when_in_record(self):
80+
"""If user passes matching key field in record, it should still be excluded from body."""
81+
self.od._upsert_multiple(
82+
"accounts",
83+
"account",
84+
[{"accountnumber": "ACC-001"}],
85+
[{"accountnumber": "ACC-001", "name": "Contoso"}],
86+
)
87+
post_calls = [c for c in self.od._request.call_args_list if c.args[0] == "post"]
88+
payload = post_calls[0].kwargs.get("json", {})
89+
target = payload["Targets"][0]
90+
# Even though user passed accountnumber in record with same value,
91+
# it should still appear in the body because it came from record_processed
92+
# (the conflict check allows matching values through)
93+
self.assertIn("@odata.id", target)
94+
self.assertIn("accountnumber", target["@odata.id"])
95+
6096
def test_record_conflicts_with_alternate_key_raises_value_error(self):
6197
"""_upsert_multiple raises ValueError when a record field contradicts its alternate key."""
6298
with self.assertRaises(ValueError) as ctx:

0 commit comments

Comments
 (0)