Skip to content

Commit c302a87

Browse files
Merge pull request #45 from microsoft/feature/microsoft-python-standards
Add Microsoft Python standards compliance (Type Safety, Enhanced Metadata, Missing Docstrings)
2 parents 41f8842 + ebd447e commit c302a87

4 files changed

Lines changed: 115 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ authors = [{name = "Microsoft Corporation"}]
1111
license = "MIT"
1212
license-files = ["LICENSE"]
1313
requires-python = ">=3.10"
14+
keywords = ["dataverse", "powerapps", "powerplatform", "crm", "dynamics", "odata"]
1415
classifiers = [
16+
"Development Status :: 4 - Beta",
17+
"Intended Audience :: Developers",
18+
"Programming Language :: Python :: 3",
1519
"Programming Language :: Python :: 3.10",
1620
"Programming Language :: Python :: 3.11",
1721
"Programming Language :: Python :: 3.12",
1822
"Programming Language :: Python :: 3.13",
1923
"Operating System :: OS Independent",
24+
"Topic :: Software Development :: Libraries :: Python Modules",
25+
"Typing :: Typed",
2026
]
2127
dependencies = [
2228
"azure-identity>=1.17.0",
@@ -27,6 +33,19 @@ dependencies = [
2733

2834
[project.urls]
2935
"Homepage" = "https://github.com/microsoft/PowerPlatform-DataverseClient-Python"
36+
"Repository" = "https://github.com/microsoft/PowerPlatform-DataverseClient-Python.git"
37+
"Issues" = "https://github.com/microsoft/PowerPlatform-DataverseClient-Python/issues"
38+
"Documentation" = "https://github.com/microsoft/PowerPlatform-DataverseClient-Python#readme"
39+
40+
[project.optional-dependencies]
41+
dev = [
42+
"pytest>=7.0.0",
43+
"pytest-cov>=4.0.0",
44+
"black>=23.0.0",
45+
"isort>=5.12.0",
46+
"mypy>=1.0.0",
47+
"ruff>=0.1.0",
48+
]
3049

3150
[tool.setuptools]
3251
package-dir = {"" = "src"}
@@ -36,3 +55,33 @@ zip-safe = false
3655
where = ["src"]
3756
include = ["PowerPlatform*"]
3857
namespaces = false
58+
59+
[tool.setuptools.package-data]
60+
"*" = ["py.typed"]
61+
62+
# Microsoft Python Standards - Linting & Formatting
63+
[tool.black]
64+
line-length = 120
65+
target-version = ['py310']
66+
67+
[tool.isort]
68+
profile = "black"
69+
line_length = 120
70+
71+
[tool.mypy]
72+
python_version = "3.10"
73+
strict = true
74+
warn_return_any = true
75+
warn_unused_configs = true
76+
77+
[tool.ruff]
78+
line-length = 120
79+
target-version = "py310"
80+
select = [
81+
"E", "W", # pycodestyle
82+
"F", # pyflakes
83+
"I", # isort
84+
"N", # pep8-naming
85+
"UP", # pyupgrade
86+
"B", # flake8-bugbear
87+
]

src/PowerPlatform/Dataverse/core/error_codes.py

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

4+
"""
5+
Error code constants and utilities for Dataverse SDK exceptions.
6+
7+
This module defines error subcodes used throughout the SDK for categorizing
8+
different types of failures, including HTTP errors, validation errors,
9+
SQL parsing errors, and metadata operation errors.
10+
"""
11+
412
# HTTP subcode constants
513
HTTP_400 = "http_400"
614
HTTP_401 = "http_401"
@@ -69,7 +77,26 @@
6977
TRANSIENT_STATUS = {429, 502, 503, 504}
7078

7179
def http_subcode(status: int) -> str:
80+
"""
81+
Convert HTTP status code to error subcode string.
82+
83+
:param status: HTTP status code (e.g., 400, 404, 500).
84+
:type status: int
85+
:return: Error subcode string (e.g., "http_400", "http_404").
86+
:rtype: str
87+
"""
7288
return HTTP_STATUS_TO_SUBCODE.get(status, f"http_{status}")
7389

7490
def is_transient_status(status: int) -> bool:
91+
"""
92+
Check if an HTTP status code indicates a transient error that may succeed on retry.
93+
94+
Transient status codes include: 429 (Too Many Requests), 502 (Bad Gateway),
95+
503 (Service Unavailable), and 504 (Gateway Timeout).
96+
97+
:param status: HTTP status code to check.
98+
:type status: int
99+
:return: True if the status code is considered transient.
100+
:rtype: bool
101+
"""
75102
return status in TRANSIENT_STATUS

src/PowerPlatform/Dataverse/core/http.py

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

4+
"""
5+
HTTP client with automatic retry logic and timeout handling.
6+
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.
10+
"""
11+
412
from __future__ import annotations
513

614
import time
@@ -10,6 +18,20 @@
1018

1119

1220
class HttpClient:
21+
"""
22+
HTTP client with configurable retry logic and timeout handling.
23+
24+
Provides automatic retry behavior for transient failures and default timeout
25+
management for different HTTP methods.
26+
27+
:param retries: Maximum number of retry attempts for transient errors. Default is 5.
28+
:type retries: int or None
29+
:param backoff: Base delay in seconds between retry attempts. Default is 0.5.
30+
:type backoff: float or None
31+
:param timeout: Default request timeout in seconds. If None, uses per-method defaults.
32+
:type timeout: float or None
33+
"""
34+
1335
def __init__(
1436
self,
1537
*,
@@ -22,8 +44,23 @@ def __init__(
2244
self.default_timeout: Optional[float] = timeout
2345

2446
def request(self, method: str, url: str, **kwargs: Any) -> requests.Response:
25-
# Apply per-method default timeouts if not provided
26-
# Apply default timeout if not provided; fall back to per-method defaults
47+
"""
48+
Execute an HTTP request with automatic retry logic and timeout management.
49+
50+
Applies default timeouts based on HTTP method (120s for POST/DELETE, 10s for others)
51+
and retries on network errors with exponential backoff.
52+
53+
:param method: HTTP method (GET, POST, PUT, DELETE, etc.).
54+
:type method: str
55+
:param url: Target URL for the request.
56+
:type url: str
57+
:param kwargs: Additional arguments passed to ``requests.request()``, including headers, data, etc.
58+
:return: HTTP response object.
59+
:rtype: requests.Response
60+
:raises requests.exceptions.RequestException: If all retry attempts fail.
61+
"""
62+
# If no timeout is provided, use the user-specified default timeout if set;
63+
# otherwise, apply per-method defaults (120s for POST/DELETE, 10s for others).
2764
if "timeout" not in kwargs:
2865
if self.default_timeout is not None:
2966
kwargs["timeout"] = self.default_timeout

src/PowerPlatform/Dataverse/py.typed

Whitespace-only changes.

0 commit comments

Comments
 (0)