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
6.**Internal vs public naming** - Modules, files, and functions not meant to be part of the public API must use a `_` prefix (e.g., `_odata.py`, `_relationships.py`). Files without the prefix (e.g., `constants.py`, `metadata.py`) are public and importable by SDK consumers
22
22
23
+
### Dataverse Property Naming Rules
24
+
25
+
Dataverse uses two different naming conventions for properties. Getting this wrong causes 400 errors that are hard to debug.
26
+
27
+
| Property type | Name convention | Example | When used |
Navigation property names are case-sensitive and must match the entity's `$metadata`. Using the logical name instead of the navigation property name results in 400 Bad Request errors.
33
+
34
+
**Critical rule:** The OData parser validates `@odata.bind` property names **case-sensitively** against declared navigation properties. Lowercasing `new_CustomerId@odata.bind` to `new_customerid@odata.bind` causes: `ODataException: An undeclared property 'new_customerid' which only has property annotations...`
35
+
36
+
**SDK implementation:**
37
+
38
+
-`_lowercase_keys()` lowercases all keys EXCEPT those containing `@odata.` (preserves navigation property casing in `@odata.bind` keys)
39
+
-`_lowercase_list()` lowercases `$select` and `$orderby` params (structural properties)
40
+
-`$expand` params are passed as-is (navigation properties, PascalCase)
41
+
-`_convert_labels_to_ints()` skips `@odata.` keys entirely (they are annotations, not attributes)
42
+
43
+
**When adding new code that processes record dicts or builds query parameters:**
44
+
45
+
- Always use `_lowercase_keys()` for record payloads. Never manually call `.lower()` on all keys
46
+
- Never lowercase `$expand` values or `@odata.bind` key prefixes
47
+
- If iterating record keys, skip keys containing `@odata.` when doing attribute-level operations
48
+
23
49
### Code Style
24
50
25
51
6.**No emojis** - Do not use emoji in code, comments, or output
@@ -28,3 +54,35 @@ This skill provides guidance for developers working on the PowerPlatform Dataver
28
54
9.**Document public APIs** - Add Sphinx-style docstrings with examples for public methods
29
55
10.**Define __all__ in module files** - Each module declares its own exports via `__all__` (e.g., `errors.py` defines `__all__ = ["HttpError", ...]`). Package `__init__.py` files should not re-export or redefine another module's `__all__`; they use `__all__ = []` to indicate no star-import exports.
30
56
11.**Run black before committing** - Always run `python -m black <changed files>` before committing. CI will reject unformatted code. Config is in `pyproject.toml` under `[tool.black]`.
57
+
58
+
### Docstring Type Annotations (Microsoft Learn Compatibility)
59
+
60
+
This SDK's API reference is published on Microsoft Learn. The Learn doc pipeline parses `:type:` and `:rtype:` directives differently from standard Sphinx -- every word between `:class:` references is treated as a separate cross-reference (`<xref:word>`). Using Sphinx-style `:class:\`list\` of :class:\`str\`` produces broken `<xref:of>` links on Learn.
61
+
62
+
**Rules for `:type:` and `:rtype:` directives:**
63
+
64
+
- Use Python bracket notation for generic types: `list[str]`, `dict[str, typing.Any]`, `list[dict]`
65
+
- Use `or` (without `:class:`) for union types: `str or None`, `dict or list[dict]`
66
+
- Use bracket nesting for complex types: `collections.abc.Iterable[list[dict]]`
67
+
- Use `~` prefix for SDK types to show short name: `list[~PowerPlatform.Dataverse.models.record.Record]`
68
+
-`:class:` is fine for single standalone types: `:class:\`str\``, `:class:\`bool\``
69
+
70
+
**Never** use `:class:\`X\` of :class:\`Y\`` or `:class:\`X\` mapping :class:\`Y\` to :class:\`Z\`` -- the words `of`, `mapping`, `to` become broken `<xref:>` links.
Copy file name to clipboardExpand all lines: .claude/skills/dataverse-sdk-use/SKILL.md
+56-2Lines changed: 56 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,6 +30,9 @@ The SDK supports Dataverse's native bulk operations: Pass lists to `create()`, `
30
30
- Control page size with `page_size` parameter
31
31
- Use `top` parameter to limit total records returned
32
32
33
+
### DataFrame Support
34
+
- DataFrame operations are accessed via the `client.dataframe` namespace: `client.dataframe.get()`, `client.dataframe.create()`, `client.dataframe.update()`, `client.dataframe.delete()`
35
+
33
36
## Common Operations
34
37
35
38
### Import
@@ -105,6 +108,20 @@ for page in client.records.get(
Creates or updates records identified by alternate keys. Single item → PATCH; multiple items →`UpsertMultiple` bulk action.
135
+
Creates or updates records identified by alternate keys. Single item -> PATCH; multiple items ->`UpsertMultiple` bulk action.
119
136
> **Prerequisite**: The table must have an alternate key configured in Dataverse for the columns used in `alternate_key`. Without it, Dataverse will reject the request with a 400 error.
120
137
```python
121
138
from PowerPlatform.Dataverse.models.upsert import UpsertItem
SQL queries are **read-only** and support limited SQL syntax. A single SELECT statement with optional WHERE, TOP (integer literal), ORDER BY (column names only), and a simple table alias after FROM is supported. But JOIN and subqueries may not be. Refer to the Dataverse documentation for the current feature set.
@@ -416,6 +469,7 @@ except ValidationError as e:
416
469
- Check filter/expand parameters use correct case
417
470
- Verify column names exist and are spelled correctly
418
471
- Ensure custom columns include customization prefix
472
+
- 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.
419
473
420
474
## Best Practices
421
475
@@ -428,7 +482,7 @@ except ValidationError as e:
428
482
5.**Use production credentials** - ClientSecretCredential or CertificateCredential for unattended operations
429
483
6.**Error handling** - Implement retry logic for transient errors (`e.is_transient`)
430
484
7.**Always include customization prefix** for custom tables/columns
431
-
8.**Use lowercase** - Generally using lowercase input won't go wrong, except for custom table/column naming
485
+
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`)
432
486
9.**Test in non-production environments** first
433
487
10.**Use named constants** - Import cascade behavior constants from `PowerPlatform.Dataverse.common.constants`
Copy file name to clipboardExpand all lines: CHANGELOG.md
+23Lines changed: 23 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
8
+
## [0.1.0b7] - 2026-03-17
9
+
10
+
### Added
11
+
- DataFrame namespace: `client.dataframe.get()`, `.create()`, `.update()`, `.delete()` for working with Dataverse records as pandas DataFrames and Series — no manual dict conversion required (#98)
12
+
- Table metadata now includes `primary_name_attribute` and `primary_id_attribute` from `tables.create()` and `tables.get_info()` (#148)
13
+
14
+
### Changed
15
+
-`pandas>=2.0.0` is now a required dependency (#98)
16
+
17
+
## [0.1.0b6] - 2026-03-12
18
+
19
+
### Added
20
+
- Context manager support: `with DataverseClient(...) as client:` for automatic resource cleanup, HTTP connection pooling, and `close()` for explicit lifecycle management (#117)
21
+
- Typed return models `Record`, `TableInfo`, and `ColumnInfo` for record and table metadata operations, replacing raw `Dict[str, Any]` returns with full backward compatibility (`result["key"]` still works) (#115)
22
+
- Alternate key management: `client.tables.create_alternate_key()`, `client.tables.get_alternate_keys()`, `client.tables.delete_alternate_key()` with typed `AlternateKeyInfo` model (#126)
23
+
24
+
### Fixed
25
+
-`@odata.bind` lookup bindings now preserve navigation property casing (e.g., `new_CustomerId@odata.bind`), fixing `400 Bad Request` errors on create/update/upsert with lookup fields (#137)
26
+
- Reduced unnecessary HTTP round-trips on create/update/upsert when records contain `@odata.bind` keys (#137)
27
+
- Single-record `get()` now lowercases `$select` column names consistently with multi-record queries (#137)
28
+
8
29
## [0.1.0b5] - 2026-02-27
9
30
10
31
### Fixed
@@ -70,6 +91,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
91
- Comprehensive error handling with specific exception types (`DataverseError`, `AuthenticationError`, etc.) (#22, #24)
71
92
- HTTP retry logic with exponential backoff for resilient operations (#72)
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+37Lines changed: 37 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -121,4 +121,41 @@ published release:
121
121
# After publishing v0.1.0b4, bump to v0.1.0b5 on main
122
122
# Update version in pyproject.toml
123
123
# Commit directly to main: "Bump version to 0.1.0b5 for next development cycle"
124
+
```
125
+
126
+
### Docstring Type Annotations (Microsoft Learn Compatibility)
127
+
128
+
This SDK's API reference is published on [Microsoft Learn](https://learn.microsoft.com). The Learn doc pipeline processes `:type:` and `:rtype:` Sphinx directives differently from standard Sphinx -- every word between `:class:` back-tick references is treated as a separate cross-reference (`<xref:word>`). For example:
129
+
130
+
```
131
+
:rtype: :class:`list` of :class:`str`
132
+
```
133
+
134
+
This produces a broken `<xref:of>` link because `of` is not a valid type.
135
+
136
+
**Rules for `:type:` and `:rtype:` directives:**
137
+
138
+
- Use **Python bracket notation** for generic types: `list[str]`, `dict[str, typing.Any]`, `list[dict]`
139
+
- Use **`or`** (without `:class:`) for union types: `str or None`, `dict or list[dict]`
140
+
- Use **bracket nesting** for complex types: `collections.abc.Iterable[list[dict]]`
141
+
-`:class:` is fine for **single standalone types**: `` :class:`str` ``, `` :class:`bool` ``
142
+
143
+
**NEVER** use the following patterns -- the connector words (`of`, `mapping`, `to`) become broken `<xref:>` links on Learn:
144
+
145
+
```
146
+
:class:`X` of :class:`Y`
147
+
:class:`X` mapping :class:`Y` to :class:`Z`
148
+
```
149
+
150
+
Correct:
151
+
```
152
+
:type data: dict or list[dict]
153
+
:rtype: list[str]
154
+
:type select: list[str] or None
155
+
```
156
+
157
+
Wrong:
158
+
```
159
+
:type data: :class:`dict` or :class:`list` of :class:`dict`
0 commit comments