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
Fix docstring type annotations for Microsoft Learn compatibility (microsoft#153)
## Summary
Fix broken cross-references (`<xref:of>`, `<xref:mapping>`, `<xref:to>`)
in the Microsoft Learn API reference docs caused by Sphinx-style
`:type:` and `:rtype:` directives.
## Problem
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.
For example:
```
:rtype: :class:`list` of :class:`str`
```
Becomes:
```yaml
types:
- <xref:list> <xref:of> <xref:str>
```
There is no type `of` in the Learn cross-reference database, so it
renders as a broken link on the published page.
## Root Cause
This was introduced in commit f0e8987 ("sphinx doc string", 2025-11-17)
which converted the original bracket-notation docstrings (`list[str]`,
`dict or list[dict]`) to Sphinx-style `:class:` syntax. Later commits
that added new APIs (operation namespaces, dataframe, etc.) perpetuated
the same broken pattern.
## Fix
Replaced all 42 occurrences across 6 source files with Python bracket
notation that the Learn pipeline handles correctly:
| Before (broken) | After (correct) |
|---|---|
| `:class:\`list\` of :class:\`str\`` | `list[str]` |
| `:class:\`dict\` or :class:\`list\` of :class:\`dict\`` | `dict or
list[dict]` |
| `:class:\`collections.abc.Iterable\` of :class:\`list\` of
:class:\`dict\`` | `collections.abc.Iterable[list[dict]]` |
| `:class:\`dict\` mapping :class:\`str\` to :class:\`typing.Any\`` |
`dict[str, typing.Any]` |
### Files changed
**Docstring fixes:**
- `src/PowerPlatform/Dataverse/client.py` (14 occurrences)
- `src/PowerPlatform/Dataverse/operations/records.py` (14 occurrences)
- `src/PowerPlatform/Dataverse/operations/tables.py` (7 occurrences)
- `src/PowerPlatform/Dataverse/operations/dataframe.py` (3 occurrences)
- `src/PowerPlatform/Dataverse/operations/query.py` (1 occurrence)
- `src/PowerPlatform/Dataverse/models/table_info.py` (3 occurrences)
**Prevention guidelines:**
- `.claude/skills/dataverse-sdk-dev/SKILL.md` -- added "Docstring Type
Annotations (Microsoft Learn Compatibility)" section
- `src/PowerPlatform/Dataverse/claude_skill/dataverse-sdk-dev/SKILL.md`
-- same section (kept both copies in sync)
## Testing
- All 398 unit tests pass
- Verified zero remaining occurrences of the broken pattern via regex
scan
---------
Co-authored-by: Saurabh Badenkal <sbadenkal@microsoft.com>
Copy file name to clipboardExpand all lines: .claude/skills/dataverse-sdk-dev/SKILL.md
+32Lines changed: 32 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,3 +54,35 @@ Navigation property names are case-sensitive and must match the entity's `$metad
54
54
9.**Document public APIs** - Add Sphinx-style docstrings with examples for public methods
55
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.
56
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: 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`
Copy file name to clipboardExpand all lines: src/PowerPlatform/Dataverse/claude_skill/dataverse-sdk-dev/SKILL.md
+32Lines changed: 32 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,3 +28,35 @@ This skill provides guidance for developers working on the PowerPlatform Dataver
28
28
9.**Document public APIs** - Add Sphinx-style docstrings with examples for public methods
29
29
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
30
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]`.
31
+
32
+
### Docstring Type Annotations (Microsoft Learn Compatibility)
33
+
34
+
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.
35
+
36
+
**Rules for `:type:` and `:rtype:` directives:**
37
+
38
+
- Use Python bracket notation for generic types: `list[str]`, `dict[str, typing.Any]`, `list[dict]`
39
+
- Use `or` (without `:class:`) for union types: `str or None`, `dict or list[dict]`
40
+
- Use bracket nesting for complex types: `collections.abc.Iterable[list[dict]]`
41
+
- Use `~` prefix for SDK types to show short name: `list[~PowerPlatform.Dataverse.models.record.Record]`
42
+
-`:class:` is fine for single standalone types: `:class:\`str\``, `:class:\`bool\``
43
+
44
+
**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.
:param records: A single record dictionary or a list of record dictionaries.
210
210
Each dictionary should contain column schema names as keys.
211
-
:type records: :class:`dict` or :class:`list` of :class:`dict`
211
+
:type records: dict or list[dict]
212
212
213
213
:return: List of created record GUIDs. Returns a single-element list for a single input.
214
-
:rtype: :class:`list` of :class:`str`
214
+
:rtype: list[str]
215
215
216
216
:raises TypeError: If ``records`` is not a dict or list[dict], or if the internal
217
217
client returns an unexpected type.
@@ -260,12 +260,12 @@ def update(
260
260
:param table_schema_name: Schema name of the table (e.g. ``"account"`` or ``"new_MyTestTable"``).
261
261
:type table_schema_name: :class:`str`
262
262
:param ids: Single GUID string or list of GUID strings to update.
263
-
:type ids: :class:`str` or :class:`list` of :class:`str`
263
+
:type ids: str or list[str]
264
264
:param changes: Dictionary of changes for single/broadcast mode, or list of dictionaries
265
265
for paired mode. When ``ids`` is a list and ``changes`` is a single dict,
266
266
the same changes are broadcast to all records. When both are lists, they must
267
267
have equal length for one-to-one mapping.
268
-
:type changes: :class:`dict` or :class:`list` of :class:`dict`
268
+
:type changes: dict or list[dict]
269
269
270
270
:raises TypeError: If ``ids`` is not str or list[str], or if ``changes`` type doesn't match usage pattern.
271
271
@@ -312,7 +312,7 @@ def delete(
312
312
:param table_schema_name: Schema name of the table (e.g. ``"account"`` or ``"new_MyTestTable"``).
313
313
:type table_schema_name: :class:`str`
314
314
:param ids: Single GUID string or list of GUID strings to delete.
315
-
:type ids: :class:`str` or :class:`list` of :class:`str`
315
+
:type ids: str or list[str]
316
316
:param use_bulk_delete: When ``True`` (default) and ``ids`` is a list, execute the BulkDelete action and
317
317
return its async job identifier. When ``False`` each record is deleted sequentially.
318
318
:type use_bulk_delete: :class:`bool`
@@ -367,21 +367,21 @@ def get(
367
367
:param record_id: Optional GUID to fetch a specific record. If None, queries multiple records.
368
368
:type record_id: :class:`str` or None
369
369
:param select: Optional list of attribute logical names to retrieve. Column names are case-insensitive and automatically lowercased (e.g. ``["new_Title", "new_Amount"]`` becomes ``"new_title,new_amount"``).
370
-
:type select: :class:`list` of :class:`str` or None
370
+
:type select: list[str] or None
371
371
:param filter: Optional OData filter string, e.g. ``"name eq 'Contoso'"`` or ``"new_quantity gt 5"``. Column names in filter expressions must use exact lowercase logical names (e.g. ``"new_quantity"``, not ``"new_Quantity"``). The filter string is passed directly to the Dataverse Web API without transformation.
372
372
:type filter: :class:`str` or None
373
373
:param orderby: Optional list of attributes to sort by, e.g. ``["name asc", "createdon desc"]``. Column names are automatically lowercased.
374
-
:type orderby: :class:`list` of :class:`str` or None
374
+
:type orderby: list[str] or None
375
375
:param top: Optional maximum number of records to return.
376
376
:type top: :class:`int` or None
377
377
:param expand: Optional list of navigation properties to expand, e.g. ``["primarycontactid"]``. Navigation property names are case-sensitive and must match the server-defined names exactly. These are NOT automatically transformed. Consult entity metadata for correct casing.
378
-
:type expand: :class:`list` of :class:`str` or None
378
+
:type expand: list[str] or None
379
379
:param page_size: Optional number of records per page for pagination.
380
380
:type page_size: :class:`int` or None
381
381
382
382
:return: Single record dict if ``record_id`` is provided, otherwise a generator
383
383
yielding lists of record dictionaries (one list per page).
384
-
:rtype: :class:`dict` or :class:`collections.abc.Iterable` of :class:`list` of :class:`dict`
384
+
:rtype: dict or collections.abc.Iterable[list[dict]]
385
385
386
386
:raises TypeError: If ``record_id`` is provided but not a string.
:return: List of result row dictionaries. Returns an empty list if no rows match.
459
-
:rtype: :class:`list` of :class:`dict`
459
+
:rtype: list[dict]
460
460
461
461
:raises ~PowerPlatform.Dataverse.core.errors.SQLParseError: If the SQL query uses unsupported syntax.
462
462
:raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the Web API returns an error.
@@ -545,7 +545,7 @@ class ItemStatus(IntEnum):
545
545
1036: {"Active": "Actif", "Inactive": "Inactif"}
546
546
}
547
547
548
-
:type columns: :class:`dict` mapping :class:`str` to :class:`typing.Any`
548
+
:type columns: dict[str, typing.Any]
549
549
:param solution_unique_name: Optional solution unique name that should own the new table. When omitted the table is created in the default solution.
550
550
:type solution_unique_name: :class:`str` or None
551
551
:param primary_column_schema_name: Optional primary name column schema name with customization prefix value (e.g. ``"new_MyTestTable"``). If not provided, defaults to ``"{customization prefix value}_Name"``.
List all non-private tables in the Dataverse environment.
635
635
636
636
:return: List of EntityDefinition metadata dictionaries.
637
-
:rtype: :class:`list` of :class:`dict`
637
+
:rtype: list[dict]
638
638
639
639
Example:
640
640
List all non-private tables and print their logical names::
@@ -666,9 +666,9 @@ def create_columns(
666
666
:param columns: Mapping of column schema names (with customization prefix value) to supported types. All custom column names must include the customization prefix value** (e.g. ``"new_Notes"``). Primitive types include
0 commit comments