Skip to content

Commit 473110b

Browse files
committed
Merge branch 'main' into user/tpellissier/prefix-with-schemaname
2 parents b69d8f2 + 33bcca0 commit 473110b

14 files changed

Lines changed: 398 additions & 425 deletions

File tree

src/PowerPlatform/Dataverse/__init__.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,6 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT license.
33

4-
"""
5-
Microsoft Dataverse SDK for Python.
6-
7-
This package provides a high-level Python client for interacting with Microsoft Dataverse
8-
environments through the Web API. It supports CRUD operations, SQL queries, table metadata
9-
management, and file uploads with Azure Identity authentication.
10-
11-
Key Features:
12-
- OData CRUD operations (create, read, update, delete)
13-
- SQL query support via Web API
14-
- Table metadata operations (create, inspect, delete custom tables)
15-
- File column upload capabilities
16-
- Pandas integration for DataFrame-based operations
17-
- Azure Identity credential support
18-
19-
.. note::
20-
This SDK requires Azure Identity credentials for authentication. See the
21-
`Azure Identity documentation <https://learn.microsoft.com/python/api/overview/azure/identity-readme>`_
22-
for supported credential types.
23-
24-
Example:
25-
Basic client initialization and usage::
26-
27-
from azure.identity import InteractiveBrowserCredential
28-
from PowerPlatform.Dataverse import DataverseClient
29-
30-
credential = InteractiveBrowserCredential()
31-
client = DataverseClient(
32-
"https://org.crm.dynamics.com",
33-
credential
34-
)
35-
36-
# Create a record
37-
account_id = client.create("account", {"name": "Contoso"})[0]
38-
39-
# Query records
40-
accounts = client.get("account", filter="name eq 'Contoso'")
41-
for batch in accounts:
42-
for record in batch:
43-
print(record["name"])
44-
"""
45-
464
from .__version__ import __version__
475
from .client import DataverseClient
486

src/PowerPlatform/Dataverse/client.py

Lines changed: 77 additions & 100 deletions
Large diffs are not rendered by default.

src/PowerPlatform/Dataverse/core/auth.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
from __future__ import annotations
55

6+
"""
7+
Authentication helpers for Dataverse.
8+
9+
This module provides :class:`~PowerPlatform.Dataverse.core.auth.AuthManager`, a thin wrapper over any Azure Identity
10+
``TokenCredential`` for acquiring OAuth2 access tokens, and :class:`~PowerPlatform.Dataverse.core.auth.TokenPair` for
11+
storing the acquired token alongside its scope.
12+
"""
13+
614
from dataclasses import dataclass
715

816
from azure.core.credentials import TokenCredential
@@ -14,9 +22,9 @@ class TokenPair:
1422
Container for an OAuth2 access token and its associated resource scope.
1523
1624
:param resource: The OAuth2 scope/resource for which the token was acquired.
17-
:type resource: str
25+
:type resource: ``str``
1826
:param access_token: The access token string.
19-
:type access_token: str
27+
:type access_token: ``str``
2028
"""
2129
resource: str
2230
access_token: str
@@ -43,7 +51,7 @@ def acquire_token(self, scope: str) -> TokenPair:
4351
Acquire an access token for the specified OAuth2 scope.
4452
4553
:param scope: OAuth2 scope string, typically ``"https://<org>.crm.dynamics.com/.default"``.
46-
:type scope: str
54+
:type scope: ``str``
4755
:return: Token pair containing the scope and access token.
4856
:rtype: ~PowerPlatform.Dataverse.core.auth.TokenPair
4957
:raises ~azure.core.exceptions.ClientAuthenticationError: If token acquisition fails.

src/PowerPlatform/Dataverse/core/config.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
from __future__ import annotations
55

6+
"""
7+
Dataverse client configuration.
8+
9+
Provides :class:`~PowerPlatform.Dataverse.core.config.DataverseConfig`, a lightweight
10+
immutable container for locale and (reserved) HTTP tuning options plus the
11+
convenience constructor :meth:`~PowerPlatform.Dataverse.core.config.DataverseConfig.from_env`.
12+
"""
13+
614
from dataclasses import dataclass
715
from typing import Optional
816

@@ -13,13 +21,13 @@ class DataverseConfig:
1321
Configuration settings for Dataverse client operations.
1422
1523
:param language_code: LCID (Locale ID) for localized labels and messages. Default is 1033 (English - United States).
16-
:type language_code: int
24+
:type language_code: ``int``
1725
:param http_retries: Optional maximum number of retry attempts for transient HTTP errors. Reserved for future use.
18-
:type http_retries: int or None
26+
:type http_retries: ``int`` | ``None``
1927
:param http_backoff: Optional backoff multiplier (in seconds) between retry attempts. Reserved for future use.
20-
:type http_backoff: float or None
28+
:type http_backoff: ``float`` | ``None``
2129
:param http_timeout: Optional request timeout in seconds. Reserved for future use.
22-
:type http_timeout: float or None
30+
:type http_timeout: ``float`` | ``None``
2331
"""
2432
language_code: int = 1033
2533

src/PowerPlatform/Dataverse/core/error_codes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ def http_subcode(status: int) -> str:
8181
Convert HTTP status code to error subcode string.
8282
8383
:param status: HTTP status code (e.g., 400, 404, 500).
84-
:type status: int
84+
:type status: ``int``
8585
:return: Error subcode string (e.g., "http_400", "http_404").
86-
:rtype: str
86+
:rtype: ``str``
8787
"""
8888
return HTTP_STATUS_TO_SUBCODE.get(status, f"http_{status}")
8989

@@ -95,8 +95,8 @@ def is_transient_status(status: int) -> bool:
9595
503 (Service Unavailable), and 504 (Gateway Timeout).
9696
9797
:param status: HTTP status code to check.
98-
:type status: int
98+
:type status: ``int``
9999
:return: True if the status code is considered transient.
100-
:rtype: bool
100+
:rtype: ``bool``
101101
"""
102102
return status in TRANSIENT_STATUS

src/PowerPlatform/Dataverse/core/errors.py

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT license.
33

4+
"""
5+
Structured Dataverse exception hierarchy.
6+
7+
This module provides :class:`~PowerPlatform.Dataverse.core.errors.DataverseError` and
8+
specialized :class:`~PowerPlatform.Dataverse.core.errors.ValidationError`,
9+
:class:`~PowerPlatform.Dataverse.core.errors.MetadataError`,
10+
:class:`~PowerPlatform.Dataverse.core.errors.SQLParseError`, and
11+
:class:`~PowerPlatform.Dataverse.core.errors.HttpError` for validation, metadata,
12+
SQL parsing, and Web API HTTP failures.
13+
"""
14+
415
from __future__ import annotations
516
from typing import Any, Dict, Optional
617
import datetime as _dt
@@ -10,24 +21,23 @@ class DataverseError(Exception):
1021
Base structured exception for the Dataverse SDK.
1122
1223
:param message: Human-readable error message.
13-
:type message: str
24+
:type message: ``str``
1425
:param code: Error category code (e.g. ``"validation_error"``, ``"http_error"``).
15-
:type code: str
26+
:type code: ``str``
1627
:param subcode: Optional subcategory or specific error identifier.
17-
:type subcode: str or None
28+
:type subcode: ``str`` | ``None``
1829
:param status_code: Optional HTTP status code if the error originated from an HTTP response.
19-
:type status_code: int or None
30+
:type status_code: ``int`` | ``None``
2031
:param details: Optional dictionary containing additional diagnostic information.
21-
:type details: dict or None
32+
:type details: ``dict`` | ``None``
2233
:param source: Error source, either ``"client"`` or ``"server"``.
23-
:type source: str
34+
:type source: ``str``
2435
:param is_transient: Whether the error is potentially transient and may succeed on retry.
25-
:type is_transient: bool
36+
:type is_transient: ``bool``
2637
"""
2738
def __init__(
2839
self,
2940
message: str,
30-
*,
3141
code: str,
3242
subcode: Optional[str] = None,
3343
status_code: Optional[int] = None,
@@ -50,7 +60,7 @@ def to_dict(self) -> Dict[str, Any]:
5060
Convert the error to a dictionary representation.
5161
5262
:return: Dictionary containing all error properties.
53-
:rtype: dict
63+
:rtype: ``dict``
5464
"""
5565
return {
5666
"message": self.message,
@@ -71,11 +81,11 @@ class ValidationError(DataverseError):
7181
Exception raised for client-side validation failures.
7282
7383
:param message: Human-readable validation error message.
74-
:type message: str
84+
:type message: ``str``
7585
:param subcode: Optional specific validation error identifier.
76-
:type subcode: str or None
86+
:type subcode: ``str`` | ``None``
7787
:param details: Optional dictionary with additional validation context.
78-
:type details: dict or None
88+
:type details: ``dict`` | ``None``
7989
"""
8090
def __init__(self, message: str, *, subcode: Optional[str] = None, details: Optional[Dict[str, Any]] = None):
8191
super().__init__(message, code="validation_error", subcode=subcode, details=details, source="client")
@@ -85,11 +95,11 @@ class MetadataError(DataverseError):
8595
Exception raised for metadata operation failures.
8696
8797
:param message: Human-readable metadata error message.
88-
:type message: str
98+
:type message: ``str``
8999
:param subcode: Optional specific metadata error identifier.
90-
:type subcode: str or None
100+
:type subcode: ``str`` | ``None``
91101
:param details: Optional dictionary with additional metadata context.
92-
:type details: dict or None
102+
:type details: ``dict`` | ``None``
93103
"""
94104
def __init__(self, message: str, *, subcode: Optional[str] = None, details: Optional[Dict[str, Any]] = None):
95105
super().__init__(message, code="metadata_error", subcode=subcode, details=details, source="client")
@@ -99,11 +109,11 @@ class SQLParseError(DataverseError):
99109
Exception raised for SQL query parsing failures.
100110
101111
:param message: Human-readable SQL parsing error message.
102-
:type message: str
112+
:type message: ``str``
103113
:param subcode: Optional specific SQL parsing error identifier.
104-
:type subcode: str or None
114+
:type subcode: ``str`` | ``None``
105115
:param details: Optional dictionary with SQL query context and parse information.
106-
:type details: dict or None
116+
:type details: ``dict`` | ``None``
107117
"""
108118
def __init__(self, message: str, *, subcode: Optional[str] = None, details: Optional[Dict[str, Any]] = None):
109119
super().__init__(message, code="sql_parse_error", subcode=subcode, details=details, source="client")
@@ -113,27 +123,27 @@ class HttpError(DataverseError):
113123
Exception raised for HTTP request failures from the Dataverse Web API.
114124
115125
:param message: Human-readable HTTP error message, typically from the API error response.
116-
:type message: str
126+
:type message: ``str``
117127
:param status_code: HTTP status code (e.g. 400, 404, 500).
118-
:type status_code: int
128+
:type status_code: ``int``
119129
:param is_transient: Whether the error is transient (429, 503, 504) and may succeed on retry.
120-
:type is_transient: bool
130+
:type is_transient: ``bool``
121131
:param subcode: Optional HTTP status category (e.g. ``"4xx"``, ``"5xx"``).
122-
:type subcode: str or None
132+
:type subcode: ``str`` | ``None``
123133
:param service_error_code: Optional Dataverse-specific error code from the API response.
124-
:type service_error_code: str or None
134+
:type service_error_code: ``str`` | ``None``
125135
:param correlation_id: Optional correlation ID for tracking requests across services.
126-
:type correlation_id: str or None
136+
:type correlation_id: ``str`` | ``None``
127137
:param request_id: Optional request ID from the API response headers.
128-
:type request_id: str or None
138+
:type request_id: ``str`` | ``None``
129139
:param traceparent: Optional W3C trace context for distributed tracing.
130-
:type traceparent: str or None
140+
:type traceparent: ``str`` | ``None``
131141
:param body_excerpt: Optional excerpt of the response body for diagnostics.
132-
:type body_excerpt: str or None
142+
:type body_excerpt: ``str`` | ``None``
133143
:param retry_after: Optional number of seconds to wait before retrying (from Retry-After header).
134-
:type retry_after: int or None
144+
:type retry_after: ``int`` | ``None``
135145
:param details: Optional additional diagnostic details.
136-
:type details: dict or None
146+
:type details: ``dict`` | ``None``
137147
"""
138148
def __init__(
139149
self,

src/PowerPlatform/Dataverse/core/http.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"""
55
HTTP client with automatic retry logic and timeout handling.
66
7-
This module provides :class:`HttpClient`, a wrapper around the requests library
8-
that adds configurable retry behavior for transient network errors and
9-
intelligent timeout management based on HTTP method types.
7+
This module provides :class:`~PowerPlatform.Dataverse.core.http.HttpClient`, a wrapper
8+
around the requests library that adds configurable retry behavior for transient
9+
network errors and intelligent timeout management based on HTTP method types.
1010
"""
1111

1212
from __future__ import annotations
@@ -25,16 +25,15 @@ class HttpClient:
2525
management for different HTTP methods.
2626
2727
:param retries: Maximum number of retry attempts for transient errors. Default is 5.
28-
:type retries: int or None
28+
:type retries: ``int`` | ``None``
2929
:param backoff: Base delay in seconds between retry attempts. Default is 0.5.
30-
:type backoff: float or None
30+
:type backoff: ``float`` | ``None``
3131
:param timeout: Default request timeout in seconds. If None, uses per-method defaults.
32-
:type timeout: float or None
32+
:type timeout: ``float`` | ``None``
3333
"""
3434

3535
def __init__(
3636
self,
37-
*,
3837
retries: Optional[int] = None,
3938
backoff: Optional[float] = None,
4039
timeout: Optional[float] = None,
@@ -51,12 +50,12 @@ def request(self, method: str, url: str, **kwargs: Any) -> requests.Response:
5150
and retries on network errors with exponential backoff.
5251
5352
:param method: HTTP method (GET, POST, PUT, DELETE, etc.).
54-
:type method: str
53+
:type method: ``str``
5554
:param url: Target URL for the request.
56-
:type url: str
55+
:type url: ``str``
5756
:param kwargs: Additional arguments passed to ``requests.request()``, including headers, data, etc.
5857
:return: HTTP response object.
59-
:rtype: requests.Response
58+
:rtype: ``requests.Response``
6059
:raises requests.exceptions.RequestException: If all retry attempts fail.
6160
"""
6261
# If no timeout is provided, use the user-specified default timeout if set;

0 commit comments

Comments
 (0)