Skip to content

Commit e26dfbc

Browse files
suyask-msftclaude
andcommitted
Remove false-positive @odata.bind warning from _lowercase_keys
The runtime warning fired on legitimate lowercase system navigation properties (ownerid, parentaccountid, transactioncurrencyid) that have no underscore or are correctly all-lowercase. The real fix (preserving @odata.bind key casing) is already in place — the SDK should not second-guess what the caller passes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4d1dc81 commit e26dfbc

4 files changed

Lines changed: 2 additions & 56 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ except ValidationError as e:
373373
- Check filter/expand parameters use correct case
374374
- Verify column names exist and are spelled correctly
375375
- Ensure custom columns include customization prefix
376-
- For `@odata.bind` errors ("undeclared property"): the navigation property name before `@odata.bind` is case-sensitive and must match the entity's `$metadata` exactly (e.g., `new_CustomerId@odata.bind` for custom lookups, `parentaccountid@odata.bind` for system lookups). The SDK preserves `@odata.bind` key casing and emits a warning if it detects likely-wrong lowercase casing on custom lookups.
376+
- For `@odata.bind` errors ("undeclared property"): the navigation property name before `@odata.bind` is case-sensitive and must match the entity's `$metadata` exactly (e.g., `new_CustomerId@odata.bind` for custom lookups, `parentaccountid@odata.bind` for system lookups). The SDK preserves `@odata.bind` key casing.
377377

378378
## Best Practices
379379

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ except ValidationError as e:
373373
- Check filter/expand parameters use correct case
374374
- Verify column names exist and are spelled correctly
375375
- Ensure custom columns include customization prefix
376-
- For `@odata.bind` errors ("undeclared property"): the navigation property name before `@odata.bind` is case-sensitive and must match the entity's `$metadata` exactly (e.g., `new_CustomerId@odata.bind` for custom lookups, `parentaccountid@odata.bind` for system lookups). The SDK preserves `@odata.bind` key casing and emits a warning if it detects likely-wrong lowercase casing on custom lookups.
376+
- For `@odata.bind` errors ("undeclared property"): the navigation property name before `@odata.bind` is case-sensitive and must match the entity's `$metadata` exactly (e.g., `new_CustomerId@odata.bind` for custom lookups, `parentaccountid@odata.bind` for system lookups). The SDK preserves `@odata.bind` key casing.
377377

378378
## Best Practices
379379

src/PowerPlatform/Dataverse/data/_odata.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import re
1414
import json
1515
import uuid
16-
import warnings
1716
from datetime import datetime, timezone
1817
import importlib.resources as ir
1918
from contextlib import contextmanager
@@ -107,22 +106,6 @@ def _lowercase_keys(record: Dict[str, Any]) -> Dict[str, Any]:
107106
"""
108107
if not isinstance(record, dict):
109108
return record
110-
for k in record:
111-
if isinstance(k, str) and "@odata.bind" in k:
112-
nav_prop = k.split("@odata.bind")[0]
113-
if nav_prop and nav_prop == nav_prop.lower() and "_" in nav_prop:
114-
# Likely already-lowercased navigation property name.
115-
# Custom lookup navigation properties use PascalCase
116-
# (e.g. new_CustomerId), not lowercase LogicalName.
117-
warnings.warn(
118-
f"@odata.bind key '{k}' appears to use a lowercase "
119-
f"navigation property name. Navigation property names "
120-
f"are case-sensitive and must match the entity's "
121-
f"$metadata (e.g. 'new_CustomerId@odata.bind', not "
122-
f"'new_customerid@odata.bind'). This will likely "
123-
f"cause a 400 error.",
124-
stacklevel=4,
125-
)
126109
return {k.lower() if isinstance(k, str) and "@odata." not in k else k: v for k, v in record.items()}
127110

128111
@staticmethod

tests/unit/data/test_odata_internal.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -354,43 +354,6 @@ def test_odata_bind_keys_preserve_case(self):
354354
self.assertIn("new_CustomerId@odata.bind", payload)
355355
self.assertNotIn("new_customerid@odata.bind", payload)
356356

357-
def test_odata_bind_lowercase_warns(self):
358-
"""Lowercase @odata.bind nav property emits a warning."""
359-
import warnings
360-
361-
with warnings.catch_warnings(record=True) as w:
362-
warnings.simplefilter("always")
363-
self.od._upsert(
364-
"accounts",
365-
"account",
366-
{"accountnumber": "ACC-001"},
367-
{
368-
"name": "Contoso",
369-
"new_customerid@odata.bind": "/contacts(00000000-0000-0000-0000-000000000001)",
370-
},
371-
)
372-
odata_warnings = [x for x in w if "@odata.bind" in str(x.message)]
373-
self.assertEqual(len(odata_warnings), 1)
374-
self.assertIn("case-sensitive", str(odata_warnings[0].message))
375-
376-
def test_odata_bind_pascalcase_no_warning(self):
377-
"""PascalCase @odata.bind nav property does NOT emit a warning."""
378-
import warnings
379-
380-
with warnings.catch_warnings(record=True) as w:
381-
warnings.simplefilter("always")
382-
self.od._upsert(
383-
"accounts",
384-
"account",
385-
{"accountnumber": "ACC-001"},
386-
{
387-
"name": "Contoso",
388-
"new_CustomerId@odata.bind": "/contacts(00000000-0000-0000-0000-000000000001)",
389-
},
390-
)
391-
odata_warnings = [x for x in w if "@odata.bind" in str(x.message)]
392-
self.assertEqual(len(odata_warnings), 0)
393-
394357
def test_convert_labels_skips_odata_keys(self):
395358
"""_convert_labels_to_ints should skip @odata.bind keys (no metadata lookup)."""
396359
# Patch _optionset_map to track calls

0 commit comments

Comments
 (0)