adsk-platform-httpclient 0.2.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adsk_platform_httpclient-0.2.9/PKG-INFO +140 -0
- adsk_platform_httpclient-0.2.9/README.md +113 -0
- adsk_platform_httpclient-0.2.9/pyproject.toml +47 -0
- adsk_platform_httpclient-0.2.9/setup.cfg +4 -0
- adsk_platform_httpclient-0.2.9/src/adsk_platform_httpclient.egg-info/PKG-INFO +140 -0
- adsk_platform_httpclient-0.2.9/src/adsk_platform_httpclient.egg-info/SOURCES.txt +18 -0
- adsk_platform_httpclient-0.2.9/src/adsk_platform_httpclient.egg-info/dependency_links.txt +1 -0
- adsk_platform_httpclient-0.2.9/src/adsk_platform_httpclient.egg-info/requires.txt +3 -0
- adsk_platform_httpclient-0.2.9/src/adsk_platform_httpclient.egg-info/top_level.txt +1 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/__init__.py +28 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/access_token_provider.py +49 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/http_client_factory.py +170 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/__init__.py +7 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/error_handler.py +89 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/options/__init__.py +11 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/options/error_handler_option.py +19 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/options/query_parameter_handler_option.py +20 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/options/rate_limiting_handler_option.py +55 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/query_parameter_handler.py +64 -0
- adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/rate_limiting_handler.py +89 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adsk-platform-httpclient
|
|
3
|
+
Version: 0.2.9
|
|
4
|
+
Summary: Autodesk Platform Services: shared HTTP client and authentication utilities for Kiota-based SDKs
|
|
5
|
+
Author: duszykf
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Autodesk/APS-SDK-Kiota
|
|
8
|
+
Project-URL: Repository, https://github.com/Autodesk/APS-SDK-Kiota
|
|
9
|
+
Project-URL: Issues, https://github.com/Autodesk/APS-SDK-Kiota/issues
|
|
10
|
+
Keywords: autodesk,aps,kiota,sdk,httpclient
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: microsoft-kiota-abstractions<2.0.0,>=1.9.0
|
|
25
|
+
Requires-Dist: microsoft-kiota-http<2.0.0,>=1.9.0
|
|
26
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
27
|
+
|
|
28
|
+
# adsk-platform-httpclient
|
|
29
|
+
|
|
30
|
+
Shared HTTP client, authentication, and middleware utilities for [Autodesk Platform Services](https://aps.autodesk.com/) Python SDKs.
|
|
31
|
+
|
|
32
|
+
This package provides the foundational components used by all `adsk-platform-*` SDK packages.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install adsk-platform-httpclient
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **Note:** You typically don't need to install this package directly. It's automatically included as a dependency of the SDK packages (e.g., `adsk-platform-acc`, `adsk-platform-data-management`).
|
|
41
|
+
|
|
42
|
+
## Components
|
|
43
|
+
|
|
44
|
+
### HttpClientFactory
|
|
45
|
+
|
|
46
|
+
Creates pre-configured `httpx.AsyncClient` instances and Kiota request adapters with bearer-token authentication and the custom middleware pipeline.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from autodesk_common_httpclient import HttpClientFactory
|
|
50
|
+
|
|
51
|
+
async def get_access_token() -> str:
|
|
52
|
+
return "YOUR_ACCESS_TOKEN"
|
|
53
|
+
|
|
54
|
+
# Create a Kiota request adapter with authentication + middleware
|
|
55
|
+
adapter = HttpClientFactory.create_adapter(get_access_token)
|
|
56
|
+
|
|
57
|
+
# Or provide your own httpx client
|
|
58
|
+
import httpx
|
|
59
|
+
custom_client = httpx.AsyncClient(timeout=60.0)
|
|
60
|
+
adapter = HttpClientFactory.create_adapter(get_access_token, http_client=custom_client)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### AccessTokenProviderCallback
|
|
64
|
+
|
|
65
|
+
Wraps an async callback into a Kiota-compatible `AccessTokenProvider`.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from autodesk_common_httpclient import AccessTokenProviderCallback
|
|
69
|
+
|
|
70
|
+
provider = AccessTokenProviderCallback(get_access_token)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Custom Middleware
|
|
74
|
+
|
|
75
|
+
The package includes three custom middleware handlers that are automatically added to the HTTP pipeline, matching the C# `Autodesk.Common.HttpClientLibrary.Middleware`:
|
|
76
|
+
|
|
77
|
+
### ErrorHandler
|
|
78
|
+
|
|
79
|
+
Raises an `httpx.HTTPStatusError` when the HTTP response has a non-success status code (4xx/5xx). Enabled by default.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from autodesk_common_httpclient import ErrorHandlerOption
|
|
83
|
+
|
|
84
|
+
# Disable for a specific request
|
|
85
|
+
error_option = ErrorHandlerOption(enabled=False)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### QueryParameterHandler
|
|
89
|
+
|
|
90
|
+
Appends or overwrites query parameters on every outgoing request. Useful for injecting API versions, region codes, or tracking identifiers.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from autodesk_common_httpclient import QueryParameterHandlerOption
|
|
94
|
+
|
|
95
|
+
query_option = QueryParameterHandlerOption(query_parameters={"region": "US"})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### RateLimitingHandler
|
|
99
|
+
|
|
100
|
+
Limits the number of concurrent requests per endpoint within a sliding time window. Disabled by default.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from autodesk_common_httpclient import RateLimitingHandlerOption
|
|
104
|
+
|
|
105
|
+
# Allow max 10 requests per endpoint per 60 seconds
|
|
106
|
+
rate_option = RateLimitingHandlerOption()
|
|
107
|
+
rate_option.set_rate_limit(max_requests=10, time_window_seconds=60.0)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Creating a client with rate limiting
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from autodesk_common_httpclient import HttpClientFactory
|
|
114
|
+
|
|
115
|
+
# Create an HTTP client with rate limiting enabled
|
|
116
|
+
client = HttpClientFactory.create_with_rate_limit(
|
|
117
|
+
max_requests=10,
|
|
118
|
+
time_window_seconds=60.0,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Use it with any SDK client
|
|
122
|
+
adapter = HttpClientFactory.create_adapter(get_access_token, http_client=client)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Middleware Pipeline Order
|
|
126
|
+
|
|
127
|
+
The middleware is executed in this order (matching the C# implementation):
|
|
128
|
+
|
|
129
|
+
1. **Kiota defaults** — Redirect, Retry, Parameters Name Decoding, URL Replace, User Agent, Headers Inspection
|
|
130
|
+
2. **RateLimitingHandler** — Per-endpoint rate limiting
|
|
131
|
+
3. **QueryParameterHandler** — Query parameter injection
|
|
132
|
+
4. **ErrorHandler** — Error response detection
|
|
133
|
+
|
|
134
|
+
## Requirements
|
|
135
|
+
|
|
136
|
+
- Python 3.10 or later
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# adsk-platform-httpclient
|
|
2
|
+
|
|
3
|
+
Shared HTTP client, authentication, and middleware utilities for [Autodesk Platform Services](https://aps.autodesk.com/) Python SDKs.
|
|
4
|
+
|
|
5
|
+
This package provides the foundational components used by all `adsk-platform-*` SDK packages.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install adsk-platform-httpclient
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Note:** You typically don't need to install this package directly. It's automatically included as a dependency of the SDK packages (e.g., `adsk-platform-acc`, `adsk-platform-data-management`).
|
|
14
|
+
|
|
15
|
+
## Components
|
|
16
|
+
|
|
17
|
+
### HttpClientFactory
|
|
18
|
+
|
|
19
|
+
Creates pre-configured `httpx.AsyncClient` instances and Kiota request adapters with bearer-token authentication and the custom middleware pipeline.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from autodesk_common_httpclient import HttpClientFactory
|
|
23
|
+
|
|
24
|
+
async def get_access_token() -> str:
|
|
25
|
+
return "YOUR_ACCESS_TOKEN"
|
|
26
|
+
|
|
27
|
+
# Create a Kiota request adapter with authentication + middleware
|
|
28
|
+
adapter = HttpClientFactory.create_adapter(get_access_token)
|
|
29
|
+
|
|
30
|
+
# Or provide your own httpx client
|
|
31
|
+
import httpx
|
|
32
|
+
custom_client = httpx.AsyncClient(timeout=60.0)
|
|
33
|
+
adapter = HttpClientFactory.create_adapter(get_access_token, http_client=custom_client)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### AccessTokenProviderCallback
|
|
37
|
+
|
|
38
|
+
Wraps an async callback into a Kiota-compatible `AccessTokenProvider`.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from autodesk_common_httpclient import AccessTokenProviderCallback
|
|
42
|
+
|
|
43
|
+
provider = AccessTokenProviderCallback(get_access_token)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Custom Middleware
|
|
47
|
+
|
|
48
|
+
The package includes three custom middleware handlers that are automatically added to the HTTP pipeline, matching the C# `Autodesk.Common.HttpClientLibrary.Middleware`:
|
|
49
|
+
|
|
50
|
+
### ErrorHandler
|
|
51
|
+
|
|
52
|
+
Raises an `httpx.HTTPStatusError` when the HTTP response has a non-success status code (4xx/5xx). Enabled by default.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from autodesk_common_httpclient import ErrorHandlerOption
|
|
56
|
+
|
|
57
|
+
# Disable for a specific request
|
|
58
|
+
error_option = ErrorHandlerOption(enabled=False)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### QueryParameterHandler
|
|
62
|
+
|
|
63
|
+
Appends or overwrites query parameters on every outgoing request. Useful for injecting API versions, region codes, or tracking identifiers.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from autodesk_common_httpclient import QueryParameterHandlerOption
|
|
67
|
+
|
|
68
|
+
query_option = QueryParameterHandlerOption(query_parameters={"region": "US"})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### RateLimitingHandler
|
|
72
|
+
|
|
73
|
+
Limits the number of concurrent requests per endpoint within a sliding time window. Disabled by default.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from autodesk_common_httpclient import RateLimitingHandlerOption
|
|
77
|
+
|
|
78
|
+
# Allow max 10 requests per endpoint per 60 seconds
|
|
79
|
+
rate_option = RateLimitingHandlerOption()
|
|
80
|
+
rate_option.set_rate_limit(max_requests=10, time_window_seconds=60.0)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Creating a client with rate limiting
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from autodesk_common_httpclient import HttpClientFactory
|
|
87
|
+
|
|
88
|
+
# Create an HTTP client with rate limiting enabled
|
|
89
|
+
client = HttpClientFactory.create_with_rate_limit(
|
|
90
|
+
max_requests=10,
|
|
91
|
+
time_window_seconds=60.0,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Use it with any SDK client
|
|
95
|
+
adapter = HttpClientFactory.create_adapter(get_access_token, http_client=client)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Middleware Pipeline Order
|
|
99
|
+
|
|
100
|
+
The middleware is executed in this order (matching the C# implementation):
|
|
101
|
+
|
|
102
|
+
1. **Kiota defaults** — Redirect, Retry, Parameters Name Decoding, URL Replace, User Agent, Headers Inspection
|
|
103
|
+
2. **RateLimitingHandler** — Per-endpoint rate limiting
|
|
104
|
+
3. **QueryParameterHandler** — Query parameter injection
|
|
105
|
+
4. **ErrorHandler** — Error response detection
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Python 3.10 or later
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "adsk-platform-httpclient"
|
|
7
|
+
version = "0.2.9"
|
|
8
|
+
description = "Autodesk Platform Services: shared HTTP client and authentication utilities for Kiota-based SDKs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "duszykf" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"autodesk",
|
|
17
|
+
"aps",
|
|
18
|
+
"kiota",
|
|
19
|
+
"sdk",
|
|
20
|
+
"httpclient",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Programming Language :: Python :: 3.14",
|
|
32
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
|
+
"Typing :: Typed",
|
|
34
|
+
]
|
|
35
|
+
dependencies = [
|
|
36
|
+
"microsoft-kiota-abstractions>=1.9.0,<2.0.0",
|
|
37
|
+
"microsoft-kiota-http>=1.9.0,<2.0.0",
|
|
38
|
+
"httpx>=0.27.0,<1.0.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/Autodesk/APS-SDK-Kiota"
|
|
43
|
+
Repository = "https://github.com/Autodesk/APS-SDK-Kiota"
|
|
44
|
+
Issues = "https://github.com/Autodesk/APS-SDK-Kiota/issues"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adsk-platform-httpclient
|
|
3
|
+
Version: 0.2.9
|
|
4
|
+
Summary: Autodesk Platform Services: shared HTTP client and authentication utilities for Kiota-based SDKs
|
|
5
|
+
Author: duszykf
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Autodesk/APS-SDK-Kiota
|
|
8
|
+
Project-URL: Repository, https://github.com/Autodesk/APS-SDK-Kiota
|
|
9
|
+
Project-URL: Issues, https://github.com/Autodesk/APS-SDK-Kiota/issues
|
|
10
|
+
Keywords: autodesk,aps,kiota,sdk,httpclient
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: microsoft-kiota-abstractions<2.0.0,>=1.9.0
|
|
25
|
+
Requires-Dist: microsoft-kiota-http<2.0.0,>=1.9.0
|
|
26
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
27
|
+
|
|
28
|
+
# adsk-platform-httpclient
|
|
29
|
+
|
|
30
|
+
Shared HTTP client, authentication, and middleware utilities for [Autodesk Platform Services](https://aps.autodesk.com/) Python SDKs.
|
|
31
|
+
|
|
32
|
+
This package provides the foundational components used by all `adsk-platform-*` SDK packages.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install adsk-platform-httpclient
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **Note:** You typically don't need to install this package directly. It's automatically included as a dependency of the SDK packages (e.g., `adsk-platform-acc`, `adsk-platform-data-management`).
|
|
41
|
+
|
|
42
|
+
## Components
|
|
43
|
+
|
|
44
|
+
### HttpClientFactory
|
|
45
|
+
|
|
46
|
+
Creates pre-configured `httpx.AsyncClient` instances and Kiota request adapters with bearer-token authentication and the custom middleware pipeline.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from autodesk_common_httpclient import HttpClientFactory
|
|
50
|
+
|
|
51
|
+
async def get_access_token() -> str:
|
|
52
|
+
return "YOUR_ACCESS_TOKEN"
|
|
53
|
+
|
|
54
|
+
# Create a Kiota request adapter with authentication + middleware
|
|
55
|
+
adapter = HttpClientFactory.create_adapter(get_access_token)
|
|
56
|
+
|
|
57
|
+
# Or provide your own httpx client
|
|
58
|
+
import httpx
|
|
59
|
+
custom_client = httpx.AsyncClient(timeout=60.0)
|
|
60
|
+
adapter = HttpClientFactory.create_adapter(get_access_token, http_client=custom_client)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### AccessTokenProviderCallback
|
|
64
|
+
|
|
65
|
+
Wraps an async callback into a Kiota-compatible `AccessTokenProvider`.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from autodesk_common_httpclient import AccessTokenProviderCallback
|
|
69
|
+
|
|
70
|
+
provider = AccessTokenProviderCallback(get_access_token)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Custom Middleware
|
|
74
|
+
|
|
75
|
+
The package includes three custom middleware handlers that are automatically added to the HTTP pipeline, matching the C# `Autodesk.Common.HttpClientLibrary.Middleware`:
|
|
76
|
+
|
|
77
|
+
### ErrorHandler
|
|
78
|
+
|
|
79
|
+
Raises an `httpx.HTTPStatusError` when the HTTP response has a non-success status code (4xx/5xx). Enabled by default.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from autodesk_common_httpclient import ErrorHandlerOption
|
|
83
|
+
|
|
84
|
+
# Disable for a specific request
|
|
85
|
+
error_option = ErrorHandlerOption(enabled=False)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### QueryParameterHandler
|
|
89
|
+
|
|
90
|
+
Appends or overwrites query parameters on every outgoing request. Useful for injecting API versions, region codes, or tracking identifiers.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from autodesk_common_httpclient import QueryParameterHandlerOption
|
|
94
|
+
|
|
95
|
+
query_option = QueryParameterHandlerOption(query_parameters={"region": "US"})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### RateLimitingHandler
|
|
99
|
+
|
|
100
|
+
Limits the number of concurrent requests per endpoint within a sliding time window. Disabled by default.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from autodesk_common_httpclient import RateLimitingHandlerOption
|
|
104
|
+
|
|
105
|
+
# Allow max 10 requests per endpoint per 60 seconds
|
|
106
|
+
rate_option = RateLimitingHandlerOption()
|
|
107
|
+
rate_option.set_rate_limit(max_requests=10, time_window_seconds=60.0)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Creating a client with rate limiting
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from autodesk_common_httpclient import HttpClientFactory
|
|
114
|
+
|
|
115
|
+
# Create an HTTP client with rate limiting enabled
|
|
116
|
+
client = HttpClientFactory.create_with_rate_limit(
|
|
117
|
+
max_requests=10,
|
|
118
|
+
time_window_seconds=60.0,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Use it with any SDK client
|
|
122
|
+
adapter = HttpClientFactory.create_adapter(get_access_token, http_client=client)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Middleware Pipeline Order
|
|
126
|
+
|
|
127
|
+
The middleware is executed in this order (matching the C# implementation):
|
|
128
|
+
|
|
129
|
+
1. **Kiota defaults** — Redirect, Retry, Parameters Name Decoding, URL Replace, User Agent, Headers Inspection
|
|
130
|
+
2. **RateLimitingHandler** — Per-endpoint rate limiting
|
|
131
|
+
3. **QueryParameterHandler** — Query parameter injection
|
|
132
|
+
4. **ErrorHandler** — Error response detection
|
|
133
|
+
|
|
134
|
+
## Requirements
|
|
135
|
+
|
|
136
|
+
- Python 3.10 or later
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/adsk_platform_httpclient.egg-info/PKG-INFO
|
|
4
|
+
src/adsk_platform_httpclient.egg-info/SOURCES.txt
|
|
5
|
+
src/adsk_platform_httpclient.egg-info/dependency_links.txt
|
|
6
|
+
src/adsk_platform_httpclient.egg-info/requires.txt
|
|
7
|
+
src/adsk_platform_httpclient.egg-info/top_level.txt
|
|
8
|
+
src/autodesk_common_httpclient/__init__.py
|
|
9
|
+
src/autodesk_common_httpclient/access_token_provider.py
|
|
10
|
+
src/autodesk_common_httpclient/http_client_factory.py
|
|
11
|
+
src/autodesk_common_httpclient/middleware/__init__.py
|
|
12
|
+
src/autodesk_common_httpclient/middleware/error_handler.py
|
|
13
|
+
src/autodesk_common_httpclient/middleware/query_parameter_handler.py
|
|
14
|
+
src/autodesk_common_httpclient/middleware/rate_limiting_handler.py
|
|
15
|
+
src/autodesk_common_httpclient/middleware/options/__init__.py
|
|
16
|
+
src/autodesk_common_httpclient/middleware/options/error_handler_option.py
|
|
17
|
+
src/autodesk_common_httpclient/middleware/options/query_parameter_handler_option.py
|
|
18
|
+
src/autodesk_common_httpclient/middleware/options/rate_limiting_handler_option.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
autodesk_common_httpclient
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Autodesk Platform Services: shared HTTP client utilities for Kiota-based SDKs."""
|
|
2
|
+
|
|
3
|
+
from autodesk_common_httpclient.access_token_provider import AccessTokenProviderCallback
|
|
4
|
+
from autodesk_common_httpclient.http_client_factory import HttpClientFactory
|
|
5
|
+
from autodesk_common_httpclient.middleware import (
|
|
6
|
+
ErrorContext,
|
|
7
|
+
ErrorHandler,
|
|
8
|
+
QueryParameterHandler,
|
|
9
|
+
RateLimitingHandler,
|
|
10
|
+
)
|
|
11
|
+
from autodesk_common_httpclient.middleware.options import (
|
|
12
|
+
ErrorHandlerOption,
|
|
13
|
+
QueryParameterHandlerOption,
|
|
14
|
+
RateLimitingHandlerOption,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AccessTokenProviderCallback",
|
|
19
|
+
"ErrorContext",
|
|
20
|
+
"ErrorHandler",
|
|
21
|
+
"ErrorHandlerOption",
|
|
22
|
+
"HttpClientFactory",
|
|
23
|
+
"QueryParameterHandler",
|
|
24
|
+
"QueryParameterHandlerOption",
|
|
25
|
+
"RateLimitingHandler",
|
|
26
|
+
"RateLimitingHandlerOption",
|
|
27
|
+
]
|
|
28
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Access token provider that wraps an async callback for Kiota authentication."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from kiota_abstractions.authentication import (
|
|
8
|
+
AccessTokenProvider,
|
|
9
|
+
AllowedHostsValidator,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AccessTokenProviderCallback(AccessTokenProvider):
|
|
14
|
+
"""Wraps an async callback into a Kiota-compatible :class:`AccessTokenProvider`.
|
|
15
|
+
|
|
16
|
+
This is the Python equivalent of the C# ``AccessTokenProvider`` class that wraps
|
|
17
|
+
a ``Func<Task<string>>`` delegate.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
get_access_token: An async callable that returns a valid access token string.
|
|
21
|
+
|
|
22
|
+
Example::
|
|
23
|
+
|
|
24
|
+
async def my_token_provider() -> str:
|
|
25
|
+
return "my-access-token"
|
|
26
|
+
|
|
27
|
+
provider = AccessTokenProviderCallback(my_token_provider)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, get_access_token: Callable[[], Awaitable[str]]) -> None:
|
|
31
|
+
self._get_access_token = get_access_token
|
|
32
|
+
|
|
33
|
+
async def get_authorization_token(
|
|
34
|
+
self, uri: str, additional_authentication_context: dict[str, Any] = {}
|
|
35
|
+
) -> str:
|
|
36
|
+
"""Return the access token by invoking the wrapped callback.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
uri: The target URI (ignored — the same token is used for all hosts).
|
|
40
|
+
additional_authentication_context: Additional context (ignored).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The access token string.
|
|
44
|
+
"""
|
|
45
|
+
return await self._get_access_token()
|
|
46
|
+
|
|
47
|
+
def get_allowed_hosts_validator(self) -> AllowedHostsValidator:
|
|
48
|
+
"""Return an :class:`AllowedHostsValidator` that allows all hosts."""
|
|
49
|
+
return AllowedHostsValidator([])
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Factory for creating Kiota request adapters with Autodesk authentication and middleware.
|
|
2
|
+
|
|
3
|
+
This is the Python equivalent of the C# ``Autodesk.Common.HttpClientLibrary.HttpClientFactory``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from kiota_abstractions.authentication import BaseBearerTokenAuthenticationProvider
|
|
12
|
+
from kiota_abstractions.request_option import RequestOption
|
|
13
|
+
from kiota_http.httpx_request_adapter import HttpxRequestAdapter
|
|
14
|
+
from kiota_http.kiota_client_factory import KiotaClientFactory
|
|
15
|
+
from kiota_http.middleware.middleware import BaseMiddleware
|
|
16
|
+
|
|
17
|
+
from autodesk_common_httpclient.access_token_provider import AccessTokenProviderCallback
|
|
18
|
+
from autodesk_common_httpclient.middleware.error_handler import ErrorHandler
|
|
19
|
+
from autodesk_common_httpclient.middleware.options.error_handler_option import ErrorHandlerOption
|
|
20
|
+
from autodesk_common_httpclient.middleware.options.query_parameter_handler_option import (
|
|
21
|
+
QueryParameterHandlerOption,
|
|
22
|
+
)
|
|
23
|
+
from autodesk_common_httpclient.middleware.options.rate_limiting_handler_option import (
|
|
24
|
+
RateLimitingHandlerOption,
|
|
25
|
+
)
|
|
26
|
+
from autodesk_common_httpclient.middleware.query_parameter_handler import QueryParameterHandler
|
|
27
|
+
from autodesk_common_httpclient.middleware.rate_limiting_handler import RateLimitingHandler
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HttpClientFactory:
|
|
31
|
+
"""Creates pre-configured HTTP clients and Kiota request adapters.
|
|
32
|
+
|
|
33
|
+
This mirrors the C# ``HttpClientFactory`` from ``Autodesk.Common.HttpClientLibrary``,
|
|
34
|
+
including the custom middleware pipeline:
|
|
35
|
+
|
|
36
|
+
1. Kiota default handlers (redirect, retry, parameters name decoding, etc.)
|
|
37
|
+
2. **RateLimitingHandler** — per-endpoint sliding-window rate limiter
|
|
38
|
+
3. **QueryParameterHandler** — injects additional query parameters
|
|
39
|
+
4. **ErrorHandler** — raises on non-success HTTP responses
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def create(
|
|
44
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
45
|
+
options: Optional[dict[str, RequestOption]] = None,
|
|
46
|
+
) -> httpx.AsyncClient:
|
|
47
|
+
"""Create an ``httpx.AsyncClient`` with Kiota default + Autodesk custom middleware.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
http_client: An existing client to wrap. If ``None``, a new client
|
|
51
|
+
is created.
|
|
52
|
+
options: Optional dict of :class:`RequestOption` keyed by option key.
|
|
53
|
+
Recognised keys: ``RateLimitingHandlerOption``,
|
|
54
|
+
``QueryParameterHandlerOption``, ``ErrorHandlerOption``.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A configured :class:`httpx.AsyncClient` with the full middleware pipeline.
|
|
58
|
+
"""
|
|
59
|
+
# Resolve options (fall back to defaults)
|
|
60
|
+
rate_limit_option = RateLimitingHandlerOption()
|
|
61
|
+
query_param_option = QueryParameterHandlerOption()
|
|
62
|
+
error_option = ErrorHandlerOption()
|
|
63
|
+
|
|
64
|
+
if options:
|
|
65
|
+
opt = options.get(RateLimitingHandlerOption.get_key())
|
|
66
|
+
if isinstance(opt, RateLimitingHandlerOption):
|
|
67
|
+
rate_limit_option = opt
|
|
68
|
+
|
|
69
|
+
opt = options.get(QueryParameterHandlerOption.get_key())
|
|
70
|
+
if isinstance(opt, QueryParameterHandlerOption):
|
|
71
|
+
query_param_option = opt
|
|
72
|
+
|
|
73
|
+
opt = options.get(ErrorHandlerOption.get_key())
|
|
74
|
+
if isinstance(opt, ErrorHandlerOption):
|
|
75
|
+
error_option = opt
|
|
76
|
+
|
|
77
|
+
# Build the middleware list: Kiota defaults + custom handlers
|
|
78
|
+
middleware = KiotaClientFactory.get_default_middleware(options)
|
|
79
|
+
middleware.extend([
|
|
80
|
+
RateLimitingHandler(rate_limit_option),
|
|
81
|
+
QueryParameterHandler(query_param_option),
|
|
82
|
+
ErrorHandler(error_option),
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
client = http_client or KiotaClientFactory.get_default_client()
|
|
86
|
+
return KiotaClientFactory.create_with_custom_middleware(middleware, client)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def create_with_rate_limit(
|
|
90
|
+
max_requests: int,
|
|
91
|
+
time_window_seconds: float,
|
|
92
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
93
|
+
) -> httpx.AsyncClient:
|
|
94
|
+
"""Create an ``httpx.AsyncClient`` with rate limiting enabled.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
max_requests: Maximum requests per endpoint within the time window.
|
|
98
|
+
time_window_seconds: Duration of the sliding window in seconds.
|
|
99
|
+
http_client: Optional existing client to wrap.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
A configured :class:`httpx.AsyncClient`.
|
|
103
|
+
"""
|
|
104
|
+
rate_option = RateLimitingHandlerOption()
|
|
105
|
+
rate_option.set_rate_limit(max_requests, time_window_seconds)
|
|
106
|
+
return HttpClientFactory.create(
|
|
107
|
+
http_client,
|
|
108
|
+
options={RateLimitingHandlerOption.get_key(): rate_option},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def create_adapter(
|
|
113
|
+
get_access_token: Callable[[], Awaitable[str]],
|
|
114
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
115
|
+
) -> HttpxRequestAdapter:
|
|
116
|
+
"""Create a :class:`HttpxRequestAdapter` with bearer-token auth and custom middleware.
|
|
117
|
+
|
|
118
|
+
This is the Python equivalent of the C#
|
|
119
|
+
``HttpClientFactory.CreateAdapter(Func<Task<string>>, HttpClient?)``.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
get_access_token: An async callable returning an access token string.
|
|
123
|
+
http_client: Optional :class:`httpx.AsyncClient`. If ``None``, a new
|
|
124
|
+
client with the full middleware pipeline is created.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
A ready-to-use :class:`HttpxRequestAdapter`.
|
|
128
|
+
"""
|
|
129
|
+
auth = BaseBearerTokenAuthenticationProvider(
|
|
130
|
+
AccessTokenProviderCallback(get_access_token)
|
|
131
|
+
)
|
|
132
|
+
client = HttpClientFactory.create(http_client)
|
|
133
|
+
return HttpxRequestAdapter(auth, http_client=client)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def get_default_middleware(
|
|
137
|
+
options: Optional[dict[str, RequestOption]] = None,
|
|
138
|
+
) -> list[BaseMiddleware]:
|
|
139
|
+
"""Return the full middleware list (Kiota defaults + Autodesk custom).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
options: Optional dict of request options.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Ordered list of middleware handlers.
|
|
146
|
+
"""
|
|
147
|
+
rate_limit_option = RateLimitingHandlerOption()
|
|
148
|
+
query_param_option = QueryParameterHandlerOption()
|
|
149
|
+
error_option = ErrorHandlerOption()
|
|
150
|
+
|
|
151
|
+
if options:
|
|
152
|
+
opt = options.get(RateLimitingHandlerOption.get_key())
|
|
153
|
+
if isinstance(opt, RateLimitingHandlerOption):
|
|
154
|
+
rate_limit_option = opt
|
|
155
|
+
|
|
156
|
+
opt = options.get(QueryParameterHandlerOption.get_key())
|
|
157
|
+
if isinstance(opt, QueryParameterHandlerOption):
|
|
158
|
+
query_param_option = opt
|
|
159
|
+
|
|
160
|
+
opt = options.get(ErrorHandlerOption.get_key())
|
|
161
|
+
if isinstance(opt, ErrorHandlerOption):
|
|
162
|
+
error_option = opt
|
|
163
|
+
|
|
164
|
+
middleware = KiotaClientFactory.get_default_middleware(options)
|
|
165
|
+
middleware.extend([
|
|
166
|
+
RateLimitingHandler(rate_limit_option),
|
|
167
|
+
QueryParameterHandler(query_param_option),
|
|
168
|
+
ErrorHandler(error_option),
|
|
169
|
+
])
|
|
170
|
+
return middleware
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Custom Kiota middleware for Autodesk Platform Services."""
|
|
2
|
+
|
|
3
|
+
from autodesk_common_httpclient.middleware.error_handler import ErrorContext, ErrorHandler
|
|
4
|
+
from autodesk_common_httpclient.middleware.query_parameter_handler import QueryParameterHandler
|
|
5
|
+
from autodesk_common_httpclient.middleware.rate_limiting_handler import RateLimitingHandler
|
|
6
|
+
|
|
7
|
+
__all__ = ["ErrorContext", "ErrorHandler", "QueryParameterHandler", "RateLimitingHandler"]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Middleware that raises on non-success HTTP responses.
|
|
2
|
+
|
|
3
|
+
Python equivalent of the C# ``Autodesk.Common.HttpClientLibrary.Middleware.ErrorHandler``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from kiota_http.middleware.middleware import BaseMiddleware
|
|
12
|
+
|
|
13
|
+
from autodesk_common_httpclient.middleware.options.error_handler_option import ErrorHandlerOption
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ErrorContext:
|
|
18
|
+
"""Contains context information about a failed HTTP request."""
|
|
19
|
+
|
|
20
|
+
request_url: Optional[str] = None
|
|
21
|
+
request_method: Optional[str] = None
|
|
22
|
+
request_headers: dict[str, str] = field(default_factory=dict)
|
|
23
|
+
request_content: Optional[bytes] = None
|
|
24
|
+
response_headers: dict[str, str] = field(default_factory=dict)
|
|
25
|
+
response_content: Optional[bytes] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ErrorHandler(BaseMiddleware):
|
|
29
|
+
"""Raises an :class:`httpx.HTTPStatusError` when the response status is not successful.
|
|
30
|
+
|
|
31
|
+
The error includes an :class:`ErrorContext` attached via the ``context`` attribute
|
|
32
|
+
on the exception, containing the full request/response details for debugging.
|
|
33
|
+
This mirrors the C# ``ErrorHandler`` which throws ``HttpRequestException``
|
|
34
|
+
with the response attached in ``Data["context"]``.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
options: Configuration controlling whether the handler is active.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, options: ErrorHandlerOption | None = None) -> None:
|
|
41
|
+
super().__init__()
|
|
42
|
+
self.options = options or ErrorHandlerOption(enabled=True)
|
|
43
|
+
|
|
44
|
+
async def send(
|
|
45
|
+
self, request: httpx.Request, transport: httpx.AsyncBaseTransport
|
|
46
|
+
) -> httpx.Response:
|
|
47
|
+
"""Send the request, raising on non-2xx responses if enabled."""
|
|
48
|
+
# Buffer the request content before sending so it's available in the
|
|
49
|
+
# error context even after the request completes
|
|
50
|
+
request_content = request.content if request.content else None
|
|
51
|
+
|
|
52
|
+
response = await super().send(request, transport)
|
|
53
|
+
|
|
54
|
+
current_options = self._get_current_options(request)
|
|
55
|
+
|
|
56
|
+
if current_options.enabled and response.status_code >= 400:
|
|
57
|
+
# Read/buffer the response body so it remains readable after the
|
|
58
|
+
# exception propagates
|
|
59
|
+
response_body = response.text
|
|
60
|
+
response_content = response.content
|
|
61
|
+
|
|
62
|
+
error_context = ErrorContext(
|
|
63
|
+
request_url=str(request.url),
|
|
64
|
+
request_method=request.method,
|
|
65
|
+
request_headers=dict(request.headers),
|
|
66
|
+
request_content=request_content,
|
|
67
|
+
response_headers=dict(response.headers),
|
|
68
|
+
response_content=response_content,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
error = httpx.HTTPStatusError(
|
|
72
|
+
message=(
|
|
73
|
+
f"Request to '{request.url}' failed with status code "
|
|
74
|
+
f"'{response.status_code}'. Response body: {response_body}"
|
|
75
|
+
),
|
|
76
|
+
request=request,
|
|
77
|
+
response=response,
|
|
78
|
+
)
|
|
79
|
+
error.context = error_context # type: ignore[attr-defined]
|
|
80
|
+
raise error
|
|
81
|
+
|
|
82
|
+
return response
|
|
83
|
+
|
|
84
|
+
def _get_current_options(self, request: httpx.Request) -> ErrorHandlerOption:
|
|
85
|
+
"""Return per-request options if set, otherwise fall back to defaults."""
|
|
86
|
+
request_options = getattr(request, "options", None)
|
|
87
|
+
if request_options:
|
|
88
|
+
return request_options.get(ErrorHandlerOption.get_key(), self.options)
|
|
89
|
+
return self.options
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Middleware option classes for custom Autodesk handlers."""
|
|
2
|
+
|
|
3
|
+
from autodesk_common_httpclient.middleware.options.error_handler_option import ErrorHandlerOption
|
|
4
|
+
from autodesk_common_httpclient.middleware.options.query_parameter_handler_option import (
|
|
5
|
+
QueryParameterHandlerOption,
|
|
6
|
+
)
|
|
7
|
+
from autodesk_common_httpclient.middleware.options.rate_limiting_handler_option import (
|
|
8
|
+
RateLimitingHandlerOption,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = ["ErrorHandlerOption", "QueryParameterHandlerOption", "RateLimitingHandlerOption"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Option class for the ErrorHandler middleware."""
|
|
2
|
+
from kiota_abstractions.request_option import RequestOption
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ErrorHandlerOption(RequestOption):
|
|
6
|
+
"""Configuration for the :class:`~autodesk_common_httpclient.middleware.ErrorHandler`.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
enabled: If ``True``, the handler raises on non-success HTTP responses.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
ERROR_HANDLER_OPTION_KEY = "ErrorHandlerOption"
|
|
13
|
+
|
|
14
|
+
def __init__(self, enabled: bool = True) -> None:
|
|
15
|
+
self.enabled = enabled
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_key() -> str:
|
|
19
|
+
return ErrorHandlerOption.ERROR_HANDLER_OPTION_KEY
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Option class for the QueryParameterHandler middleware."""
|
|
2
|
+
from kiota_abstractions.request_option import RequestOption
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QueryParameterHandlerOption(RequestOption):
|
|
6
|
+
"""Configuration for the :class:`~autodesk_common_httpclient.middleware.QueryParameterHandler`.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
query_parameters: A dict of query parameter key-value pairs to append
|
|
10
|
+
or overwrite on every outgoing request.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
QUERY_PARAMETER_HANDLER_OPTION_KEY = "QueryParameterHandlerOption"
|
|
14
|
+
|
|
15
|
+
def __init__(self, query_parameters: dict[str, str] | None = None) -> None:
|
|
16
|
+
self.query_parameters: dict[str, str] = query_parameters or {}
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def get_key() -> str:
|
|
20
|
+
return QueryParameterHandlerOption.QUERY_PARAMETER_HANDLER_OPTION_KEY
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Option class for the RateLimitingHandler middleware."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from kiota_abstractions.request_option import RequestOption
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RateLimitingHandlerOption(RequestOption):
|
|
8
|
+
"""Configuration for the :class:`~autodesk_common_httpclient.middleware.RateLimitingHandler`.
|
|
9
|
+
|
|
10
|
+
Disabled by default. Call :meth:`set_rate_limit` to enable.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
RATE_LIMITING_HANDLER_OPTION_KEY = "RateLimitingHandlerOption"
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self._max_requests: int = 0
|
|
17
|
+
self._time_window_seconds: float = 0.0
|
|
18
|
+
|
|
19
|
+
def set_rate_limit(self, max_requests: int, time_window_seconds: float) -> None:
|
|
20
|
+
"""Enable rate limiting.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
max_requests: Maximum number of requests allowed within the time window.
|
|
24
|
+
Must be greater than zero.
|
|
25
|
+
time_window_seconds: Duration of the sliding window in seconds.
|
|
26
|
+
Must be greater than zero.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If either argument is not positive.
|
|
30
|
+
"""
|
|
31
|
+
if max_requests <= 0:
|
|
32
|
+
raise ValueError("max_requests must be greater than zero.")
|
|
33
|
+
if time_window_seconds <= 0:
|
|
34
|
+
raise ValueError("time_window_seconds must be greater than zero.")
|
|
35
|
+
self._max_requests = max_requests
|
|
36
|
+
self._time_window_seconds = time_window_seconds
|
|
37
|
+
|
|
38
|
+
def get_rate_limit(self) -> tuple[int, float] | None:
|
|
39
|
+
"""Return the current rate limit, or ``None`` if disabled.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A ``(max_requests, time_window_seconds)`` tuple, or ``None``.
|
|
43
|
+
"""
|
|
44
|
+
if self._max_requests == 0 or self._time_window_seconds == 0.0:
|
|
45
|
+
return None
|
|
46
|
+
return (self._max_requests, self._time_window_seconds)
|
|
47
|
+
|
|
48
|
+
def disable(self) -> None:
|
|
49
|
+
"""Disable rate limiting."""
|
|
50
|
+
self._max_requests = 0
|
|
51
|
+
self._time_window_seconds = 0.0
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def get_key() -> str:
|
|
55
|
+
return RateLimitingHandlerOption.RATE_LIMITING_HANDLER_OPTION_KEY
|
adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/query_parameter_handler.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Middleware that appends or overwrites query parameters on outgoing requests.
|
|
2
|
+
|
|
3
|
+
Python equivalent of the C# ``Autodesk.Common.HttpClientLibrary.Middleware.QueryParameterHandler``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from kiota_http.middleware.middleware import BaseMiddleware
|
|
11
|
+
|
|
12
|
+
from autodesk_common_httpclient.middleware.options.query_parameter_handler_option import (
|
|
13
|
+
QueryParameterHandlerOption,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QueryParameterHandler(BaseMiddleware):
|
|
18
|
+
"""Injects additional query parameters into every outgoing request URL.
|
|
19
|
+
|
|
20
|
+
Useful for injecting common parameters like API versions, region codes,
|
|
21
|
+
or tracking identifiers across all API calls.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
options: Configuration containing the query parameters to inject.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, options: QueryParameterHandlerOption | None = None) -> None:
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.options = options or QueryParameterHandlerOption()
|
|
30
|
+
|
|
31
|
+
async def send(
|
|
32
|
+
self, request: httpx.Request, transport: httpx.AsyncBaseTransport
|
|
33
|
+
) -> httpx.Response:
|
|
34
|
+
"""Merge configured query parameters into the request URL, then forward."""
|
|
35
|
+
current_options = self._get_current_options(request)
|
|
36
|
+
|
|
37
|
+
if current_options.query_parameters:
|
|
38
|
+
parsed = urlparse(str(request.url))
|
|
39
|
+
# Parse existing query string (keep values as flat strings)
|
|
40
|
+
existing_params = parse_qs(parsed.query, keep_blank_values=True)
|
|
41
|
+
# Overwrite / add new parameters
|
|
42
|
+
for key, value in current_options.query_parameters.items():
|
|
43
|
+
existing_params[key] = [value]
|
|
44
|
+
new_query = urlencode(existing_params, doseq=True)
|
|
45
|
+
new_url = urlunparse(parsed._replace(query=new_query))
|
|
46
|
+
request = httpx.Request(
|
|
47
|
+
method=request.method,
|
|
48
|
+
url=new_url,
|
|
49
|
+
headers=request.headers,
|
|
50
|
+
content=request.content,
|
|
51
|
+
extensions=request.extensions,
|
|
52
|
+
)
|
|
53
|
+
# Preserve options attribute if present
|
|
54
|
+
if hasattr(request, "options"):
|
|
55
|
+
pass # httpx.Request is immutable; options are already on the original
|
|
56
|
+
|
|
57
|
+
return await super().send(request, transport)
|
|
58
|
+
|
|
59
|
+
def _get_current_options(self, request: httpx.Request) -> QueryParameterHandlerOption:
|
|
60
|
+
"""Return per-request options if set, otherwise fall back to defaults."""
|
|
61
|
+
request_options = getattr(request, "options", None)
|
|
62
|
+
if request_options:
|
|
63
|
+
return request_options.get(QueryParameterHandlerOption.get_key(), self.options)
|
|
64
|
+
return self.options
|
adsk_platform_httpclient-0.2.9/src/autodesk_common_httpclient/middleware/rate_limiting_handler.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Middleware that limits concurrent requests per endpoint using a sliding window.
|
|
2
|
+
|
|
3
|
+
Python equivalent of the C# ``Autodesk.Common.HttpClientLibrary.Middleware.RateLimitingHandler``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from kiota_http.middleware.middleware import BaseMiddleware
|
|
12
|
+
|
|
13
|
+
from autodesk_common_httpclient.middleware.options.rate_limiting_handler_option import (
|
|
14
|
+
RateLimitingHandlerOption,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _RateLimiter:
|
|
19
|
+
"""Per-endpoint sliding-window rate limiter (async-safe)."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, max_requests: int, time_window_seconds: float) -> None:
|
|
22
|
+
self._max_requests = max_requests
|
|
23
|
+
self._time_window_seconds = time_window_seconds
|
|
24
|
+
self._request_count = 0
|
|
25
|
+
self._reset_time = datetime.now(timezone.utc).timestamp() + time_window_seconds
|
|
26
|
+
self._lock = asyncio.Lock()
|
|
27
|
+
|
|
28
|
+
async def wait_for_availability(self) -> None:
|
|
29
|
+
"""Block until a request slot becomes available in the current window."""
|
|
30
|
+
async with self._lock:
|
|
31
|
+
now = datetime.now(timezone.utc).timestamp()
|
|
32
|
+
|
|
33
|
+
if now >= self._reset_time:
|
|
34
|
+
self._request_count = 0
|
|
35
|
+
self._reset_time = now + self._time_window_seconds
|
|
36
|
+
|
|
37
|
+
if self._request_count >= self._max_requests:
|
|
38
|
+
delay = self._reset_time - now
|
|
39
|
+
if delay > 0:
|
|
40
|
+
await asyncio.sleep(delay)
|
|
41
|
+
self._request_count = 0
|
|
42
|
+
self._reset_time = datetime.now(timezone.utc).timestamp() + self._time_window_seconds
|
|
43
|
+
|
|
44
|
+
self._request_count += 1
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RateLimitingHandler(BaseMiddleware):
|
|
48
|
+
"""Limits the number of concurrent requests per endpoint within a time window.
|
|
49
|
+
|
|
50
|
+
Endpoints are identified by ``METHOD|path`` (query string excluded) to avoid
|
|
51
|
+
counting different query parameter combinations as separate endpoints.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
options: Rate limiting configuration. Disabled by default.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, options: RateLimitingHandlerOption | None = None) -> None:
|
|
58
|
+
super().__init__()
|
|
59
|
+
self.options = options or RateLimitingHandlerOption()
|
|
60
|
+
self._rate_limiters: dict[str, _RateLimiter] = {}
|
|
61
|
+
|
|
62
|
+
async def send(
|
|
63
|
+
self, request: httpx.Request, transport: httpx.AsyncBaseTransport
|
|
64
|
+
) -> httpx.Response:
|
|
65
|
+
"""Wait for an available rate-limit slot, then forward the request."""
|
|
66
|
+
current_options = self._get_current_options(request)
|
|
67
|
+
rate_limit = current_options.get_rate_limit()
|
|
68
|
+
|
|
69
|
+
if rate_limit is not None:
|
|
70
|
+
max_requests, time_window_seconds = rate_limit
|
|
71
|
+
endpoint = self._get_endpoint(request)
|
|
72
|
+
if endpoint not in self._rate_limiters:
|
|
73
|
+
self._rate_limiters[endpoint] = _RateLimiter(max_requests, time_window_seconds)
|
|
74
|
+
await self._rate_limiters[endpoint].wait_for_availability()
|
|
75
|
+
|
|
76
|
+
return await super().send(request, transport)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _get_endpoint(request: httpx.Request) -> str:
|
|
80
|
+
"""Derive a stable endpoint key from the request (method + path, no query)."""
|
|
81
|
+
url = request.url
|
|
82
|
+
return f"{request.method}|{url.scheme}://{url.host}{url.path}"
|
|
83
|
+
|
|
84
|
+
def _get_current_options(self, request: httpx.Request) -> RateLimitingHandlerOption:
|
|
85
|
+
"""Return per-request options if set, otherwise fall back to defaults."""
|
|
86
|
+
request_options = getattr(request, "options", None)
|
|
87
|
+
if request_options:
|
|
88
|
+
return request_options.get(RateLimitingHandlerOption.get_key(), self.options)
|
|
89
|
+
return self.options
|