http-dynamix 1.0.0__py3-none-any.whl
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.
- http_dynamix/__init__.py +38 -0
- http_dynamix/_version.py +21 -0
- http_dynamix/auth.py +194 -0
- http_dynamix/clients/__init__.py +9 -0
- http_dynamix/clients/async_client.py +313 -0
- http_dynamix/clients/sync_client.py +312 -0
- http_dynamix/core/__init__.py +7 -0
- http_dynamix/core/segment_format.py +226 -0
- http_dynamix/enums.py +39 -0
- http_dynamix/factory.py +72 -0
- http_dynamix/httpx_logger.py +598 -0
- http_dynamix/log.py +448 -0
- http_dynamix/protocols.py +126 -0
- http_dynamix/py.typed +0 -0
- http_dynamix/types.py +7 -0
- http_dynamix-1.0.0.dist-info/METADATA +193 -0
- http_dynamix-1.0.0.dist-info/RECORD +19 -0
- http_dynamix-1.0.0.dist-info/WHEEL +4 -0
- http_dynamix-1.0.0.dist-info/licenses/LICENSE +21 -0
http_dynamix/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Copyright (c) 2024, Aydin A.
|
|
2
|
+
|
|
3
|
+
This module is the entry point for the http_dynamix package. It imports the
|
|
4
|
+
version number and the logger from the log module. It also imports the
|
|
5
|
+
HttpClientFactory class from the client_factory module.
|
|
6
|
+
"""
|
|
7
|
+
from http_dynamix.factory import ClientFactory
|
|
8
|
+
from http_dynamix.enums import SegmentFormat, ClientType
|
|
9
|
+
from http_dynamix._version import version
|
|
10
|
+
from http_dynamix.auth import BearerAuth, ApiKeyAuth
|
|
11
|
+
from http_dynamix.log import log as logger, LogMaster
|
|
12
|
+
from http_dynamix.core import SegmentFormatter
|
|
13
|
+
from http_dynamix.clients import SyncClient, AsyncClient, AsyncDynamicClient, SyncDynamicClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ClientFactory",
|
|
18
|
+
"ClientType",
|
|
19
|
+
"SegmentFormat",
|
|
20
|
+
"logger",
|
|
21
|
+
"LogMaster",
|
|
22
|
+
"version",
|
|
23
|
+
"BearerAuth",
|
|
24
|
+
"ApiKeyAuth"
|
|
25
|
+
"SegmentFormatter",
|
|
26
|
+
"BaseClient",
|
|
27
|
+
"BaseDynamicClient",
|
|
28
|
+
"SyncClient",
|
|
29
|
+
"AsyncClient",
|
|
30
|
+
"AsyncDynamicClient",
|
|
31
|
+
"SyncDynamicClient"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__author__ = "Aydin Abdi"
|
|
36
|
+
__version__ = version
|
|
37
|
+
__license__ = "MIT"
|
|
38
|
+
|
http_dynamix/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '1.0.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
http_dynamix/auth.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Authentication classes for dynamic HTTP client.
|
|
2
|
+
|
|
3
|
+
This module provides various authentication methods compatible with HTTPX client.
|
|
4
|
+
All authentication classes inherit from httpx.Auth and implement the required auth_flow
|
|
5
|
+
method for proper request authentication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Generator
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from http_dynamix.log import log as logger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class BearerAuth(httpx.Auth):
|
|
21
|
+
"""Bearer token authentication method.
|
|
22
|
+
|
|
23
|
+
Implements Bearer token authentication as defined in RFC 6750.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
token: The bearer token for authentication.
|
|
27
|
+
auth_header: Custom authorization header name (default: "Authorization").
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
token: str
|
|
31
|
+
auth_header: str = "Authorization"
|
|
32
|
+
|
|
33
|
+
def auth_flow(
|
|
34
|
+
self, request: httpx.Request
|
|
35
|
+
) -> Generator[httpx.Request, httpx.Response, None]:
|
|
36
|
+
"""Yields authenticated requests using Bearer auth scheme.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
request: The HTTP request object to authenticate.
|
|
40
|
+
|
|
41
|
+
Yields:
|
|
42
|
+
httpx.Request: The authenticated HTTP request.
|
|
43
|
+
"""
|
|
44
|
+
request.headers[self.auth_header] = self._build_auth_header()
|
|
45
|
+
logger.debug("Applied Bearer token auth")
|
|
46
|
+
yield request
|
|
47
|
+
|
|
48
|
+
def _build_auth_header(self) -> str:
|
|
49
|
+
"""Builds the Authorization header with Bearer token.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
str: The formatted Authorization header.
|
|
53
|
+
"""
|
|
54
|
+
return f"Bearer {self.token}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class ApiKeyAuth(httpx.Auth):
|
|
59
|
+
"""API key authentication method.
|
|
60
|
+
|
|
61
|
+
Implements API key authentication using custom header.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
api_key: The API key for authentication.
|
|
65
|
+
header_name: The header name for the API key (default: "X-API-Key").
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
api_key: str
|
|
69
|
+
header_name: str = "X-API-Key"
|
|
70
|
+
|
|
71
|
+
def auth_flow(
|
|
72
|
+
self, request: httpx.Request
|
|
73
|
+
) -> Generator[httpx.Request, httpx.Response, None]:
|
|
74
|
+
"""Yields authenticated requests using API key.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
request: The HTTP request object to authenticate.
|
|
78
|
+
|
|
79
|
+
Yields:
|
|
80
|
+
httpx.Request: The authenticated HTTP request.
|
|
81
|
+
"""
|
|
82
|
+
request.headers[self.header_name] = self.api_key
|
|
83
|
+
logger.debug(f"Applied API key auth with header: {self.header_name}")
|
|
84
|
+
yield request
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(slots=True)
|
|
88
|
+
class MultiAuth(httpx.Auth):
|
|
89
|
+
"""Multi-authentication method that combines multiple auth schemes.
|
|
90
|
+
|
|
91
|
+
Allows chaining multiple authentication methods to be applied in sequence.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
auth_methods: List of authentication methods implementing httpx.Auth.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
auth_methods: list[httpx.Auth] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
def auth_flow(
|
|
100
|
+
self, request: httpx.Request
|
|
101
|
+
) -> Generator[httpx.Request, httpx.Response, None]:
|
|
102
|
+
"""Yields authenticated requests applying multiple auth methods.
|
|
103
|
+
|
|
104
|
+
Applies each authentication method in sequence to the request.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
request: The HTTP request object to authenticate.
|
|
108
|
+
|
|
109
|
+
Yields:
|
|
110
|
+
httpx.Request: The authenticated HTTP request.
|
|
111
|
+
"""
|
|
112
|
+
current_request = request
|
|
113
|
+
for auth in self.auth_methods:
|
|
114
|
+
auth_flow = auth.auth_flow(current_request)
|
|
115
|
+
try:
|
|
116
|
+
while True:
|
|
117
|
+
current_request = next(auth_flow)
|
|
118
|
+
response = yield current_request
|
|
119
|
+
auth_flow.send(response)
|
|
120
|
+
except StopIteration:
|
|
121
|
+
pass
|
|
122
|
+
logger.debug(f"Applied {len(self.auth_methods)} authentication methods")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Auth-related configuration keys
|
|
126
|
+
AUTH_KEYS = frozenset(
|
|
127
|
+
["token", "api_key", "username", "password", "auth_header", "api_key_header"]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def filter_auth_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
132
|
+
"""Filter kwargs to only include auth-related arguments.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
kwargs: Original kwargs dictionary
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Dictionary containing only auth-related arguments
|
|
139
|
+
"""
|
|
140
|
+
return {k: v for k, v in kwargs.items() if k in AUTH_KEYS}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def create_auth(
|
|
144
|
+
token: str | None = None,
|
|
145
|
+
api_key: str | None = None,
|
|
146
|
+
username: str | bytes | None = None,
|
|
147
|
+
password: str | bytes | None = None,
|
|
148
|
+
auth_header: str = "Authorization",
|
|
149
|
+
api_key_header: str = "X-API-Key",
|
|
150
|
+
) -> MultiAuth | None:
|
|
151
|
+
"""Factory function to create authentication method based on provided credentials.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
token: Bearer token for authentication.
|
|
155
|
+
api_key: API key for authentication.
|
|
156
|
+
username: Username for basic authentication.
|
|
157
|
+
password: Password for basic authentication.
|
|
158
|
+
auth_header: Custom authorization header name (default: "Authorization").
|
|
159
|
+
api_key_header: Custom API key header name (default: "X-API-Key").
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
An instance of appropriate Auth class or None if no credentials provided.
|
|
163
|
+
"""
|
|
164
|
+
auth_methods: list[httpx.Auth] = []
|
|
165
|
+
|
|
166
|
+
if not any([token, api_key, username, password]):
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
if token:
|
|
170
|
+
auth_methods.append(BearerAuth(token=token, auth_header=auth_header))
|
|
171
|
+
|
|
172
|
+
if api_key:
|
|
173
|
+
auth_methods.append(ApiKeyAuth(api_key=api_key, header_name=api_key_header))
|
|
174
|
+
|
|
175
|
+
if username and password:
|
|
176
|
+
auth_methods.append(httpx.BasicAuth(username=username, password=password))
|
|
177
|
+
|
|
178
|
+
return MultiAuth(auth_methods) if auth_methods else None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def prepare_auth(**kwargs: Any) -> httpx.Auth | None:
|
|
182
|
+
"""Prepares the authentication method for use in HTTPX client.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
**kwargs: Keyword arguments containing authentication credentials.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
An instance of appropriate Auth class or None if no credentials provided.
|
|
189
|
+
"""
|
|
190
|
+
auth_kwargs = filter_auth_kwargs(kwargs)
|
|
191
|
+
auth = create_auth(**auth_kwargs)
|
|
192
|
+
if auth:
|
|
193
|
+
logger.debug(f"Prepared authentication: {type(auth).__name__}")
|
|
194
|
+
return auth
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Async HTTP client with dynamic path creation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Coroutine
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from httpx import Response
|
|
11
|
+
|
|
12
|
+
from http_dynamix.core import PathSegment, SegmentFormatter
|
|
13
|
+
from http_dynamix.enums import HTTPMethod, SegmentFormat
|
|
14
|
+
from http_dynamix.httpx_logger import loggix
|
|
15
|
+
from http_dynamix.log import log as logger
|
|
16
|
+
from http_dynamix.protocols import AsyncClientProtocol
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AsyncDynamicClient(AsyncClientProtocol):
|
|
21
|
+
"""Asynchronous dynamic HTTP client.
|
|
22
|
+
|
|
23
|
+
This class allows for dynamic path creation and HTTP requests.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
|
|
28
|
+
async with AsyncClient("https://api.example.com") as client:
|
|
29
|
+
response = await client.users.john.get()
|
|
30
|
+
print(response.json())
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
client: AsyncClient
|
|
34
|
+
segments: list[PathSegment] = field(default_factory=list)
|
|
35
|
+
segment_format: SegmentFormat = SegmentFormat.DEFAULT
|
|
36
|
+
known_paths: dict[str, str] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
def _transform_path(self) -> str:
|
|
39
|
+
transformed_segments = []
|
|
40
|
+
|
|
41
|
+
for segment in self.segments:
|
|
42
|
+
if segment.value is not None:
|
|
43
|
+
transformed_segments.append(str(segment.value))
|
|
44
|
+
else:
|
|
45
|
+
segment_name = segment.name
|
|
46
|
+
transformed = SegmentFormatter(
|
|
47
|
+
self.segment_format, self.known_paths
|
|
48
|
+
).transform(segment_name)
|
|
49
|
+
transformed_segments.append(transformed)
|
|
50
|
+
|
|
51
|
+
return "/".join(transformed_segments)
|
|
52
|
+
|
|
53
|
+
def __getattr__(self, name: str) -> AsyncDynamicClient:
|
|
54
|
+
"""Handle attribute access for dynamic path creation.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: The name of the attribute.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The dynamic client.
|
|
61
|
+
"""
|
|
62
|
+
new_segments = self.segments.copy()
|
|
63
|
+
new_segments.append(PathSegment(name=name, format=self.segment_format))
|
|
64
|
+
|
|
65
|
+
return self.__class__(
|
|
66
|
+
client=self.client,
|
|
67
|
+
segments=new_segments,
|
|
68
|
+
segment_format=self.segment_format,
|
|
69
|
+
known_paths=self.known_paths,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def __getitem__(self, key: str | int | SegmentFormat) -> AsyncDynamicClient:
|
|
73
|
+
"""Handle item access for dynamic path creation.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
key: The key to access.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The dynamic client.
|
|
80
|
+
"""
|
|
81
|
+
if not self.segments:
|
|
82
|
+
raise ValueError("Cannot use [] operator without a path segment")
|
|
83
|
+
|
|
84
|
+
new_segments = self.segments.copy()
|
|
85
|
+
|
|
86
|
+
# If the key is a SegmentFormat, update the last segment with the format
|
|
87
|
+
if isinstance(key, SegmentFormat):
|
|
88
|
+
last_segment = new_segments.pop()
|
|
89
|
+
new_segments.append(last_segment.with_format(key))
|
|
90
|
+
else:
|
|
91
|
+
# Otherwise treat the key as a parameter value
|
|
92
|
+
last_segment = new_segments.pop()
|
|
93
|
+
new_segments.append(
|
|
94
|
+
PathSegment(
|
|
95
|
+
name=last_segment.name, format=last_segment.format, value=key
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return self.__class__(
|
|
100
|
+
client=self.client,
|
|
101
|
+
segments=new_segments,
|
|
102
|
+
segment_format=self.segment_format,
|
|
103
|
+
known_paths=self.known_paths,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def with_format(self, format: SegmentFormat) -> AsyncDynamicClient:
|
|
107
|
+
"""Set the segment format.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
format: The segment format.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The dynamic client.
|
|
114
|
+
"""
|
|
115
|
+
return self.__class__(
|
|
116
|
+
client=self.client,
|
|
117
|
+
segments=self.segments,
|
|
118
|
+
segment_format=format,
|
|
119
|
+
known_paths=self.known_paths,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _get_url(self) -> str:
|
|
123
|
+
url = f"{self.client.base_url}/{self._transform_path()}".strip("/")
|
|
124
|
+
logger.debug(f"Constructed URL: {url}")
|
|
125
|
+
return str(httpx.URL(url))
|
|
126
|
+
|
|
127
|
+
async def request(
|
|
128
|
+
self,
|
|
129
|
+
method: HTTPMethod,
|
|
130
|
+
**kwargs: Any,
|
|
131
|
+
) -> Coroutine[Any, Any, Response]:
|
|
132
|
+
"""Make an HTTP request.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
method: The HTTP method to use.
|
|
136
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The response from the request.
|
|
140
|
+
"""
|
|
141
|
+
url = self._get_url()
|
|
142
|
+
|
|
143
|
+
# Remove None values
|
|
144
|
+
request_kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
145
|
+
|
|
146
|
+
logger.debug(f"{method.value} request to {url} with {request_kwargs!r}")
|
|
147
|
+
response = await self.client._client.request(
|
|
148
|
+
method.value, url, **request_kwargs
|
|
149
|
+
)
|
|
150
|
+
response.raise_for_status()
|
|
151
|
+
loggix(response)
|
|
152
|
+
return cast(Coroutine[Any, Any, Response], response)
|
|
153
|
+
|
|
154
|
+
async def aclose(self) -> None:
|
|
155
|
+
"""Close the client."""
|
|
156
|
+
await self.client.aclose() # pragma: no cover
|
|
157
|
+
|
|
158
|
+
async def get(self, **kwargs: Any) -> Any:
|
|
159
|
+
"""Make a GET request.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
The response from the request.
|
|
166
|
+
"""
|
|
167
|
+
return await self.request(HTTPMethod.GET, **kwargs)
|
|
168
|
+
|
|
169
|
+
async def post(self, **kwargs: Any) -> Any:
|
|
170
|
+
"""Make a POST request.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
The response from the request.
|
|
177
|
+
"""
|
|
178
|
+
return await self.request(HTTPMethod.POST, **kwargs)
|
|
179
|
+
|
|
180
|
+
async def put(self, **kwargs: Any) -> Any:
|
|
181
|
+
"""Make a PUT request.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
The response from the request.
|
|
188
|
+
"""
|
|
189
|
+
return await self.request(HTTPMethod.PUT, **kwargs)
|
|
190
|
+
|
|
191
|
+
async def delete(self, **kwargs: Any) -> Any:
|
|
192
|
+
"""Make a DELETE request.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
The response from the request.
|
|
199
|
+
"""
|
|
200
|
+
return await self.request(HTTPMethod.DELETE, **kwargs)
|
|
201
|
+
|
|
202
|
+
async def patch(self, **kwargs: Any) -> Any:
|
|
203
|
+
"""Make a PATCH request.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
The response from the request.
|
|
210
|
+
"""
|
|
211
|
+
return await self.request(HTTPMethod.PATCH, **kwargs)
|
|
212
|
+
|
|
213
|
+
async def head(self, **kwargs: Any) -> Any:
|
|
214
|
+
"""Make a HEAD request.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
The response from the request.
|
|
221
|
+
"""
|
|
222
|
+
return await self.request(HTTPMethod.HEAD, **kwargs)
|
|
223
|
+
|
|
224
|
+
async def options(self, **kwargs: Any) -> Any:
|
|
225
|
+
"""Make an OPTIONS request.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
The response from the request.
|
|
232
|
+
"""
|
|
233
|
+
return await self.request(HTTPMethod.OPTIONS, **kwargs)
|
|
234
|
+
|
|
235
|
+
async def trace(self, **kwargs: Any) -> Any:
|
|
236
|
+
"""Make a TRACE request.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
The response from the request.
|
|
243
|
+
"""
|
|
244
|
+
return await self.request(HTTPMethod.TRACE, **kwargs)
|
|
245
|
+
|
|
246
|
+
async def connect(self, **kwargs: Any) -> Any:
|
|
247
|
+
"""Make a CONNECT request.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
**kwargs: Additional keyword arguments to pass to the request.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The response from the request.
|
|
254
|
+
"""
|
|
255
|
+
return await self.request(HTTPMethod.CONNECT, **kwargs)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@dataclass
|
|
259
|
+
class AsyncClient:
|
|
260
|
+
"""Asynchronous HTTP client.
|
|
261
|
+
|
|
262
|
+
This class allows for making HTTP requests.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
base_url: The base URL for the client.
|
|
266
|
+
segment_format: The segment format to use.
|
|
267
|
+
known_paths: A dictionary of known paths.
|
|
268
|
+
client_kwargs: Additional keyword arguments to pass to the HTTP client.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
base_url: str
|
|
272
|
+
segment_format: SegmentFormat = SegmentFormat.DEFAULT
|
|
273
|
+
known_paths: dict[str, str] = field(default_factory=dict)
|
|
274
|
+
client_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
275
|
+
|
|
276
|
+
_client: httpx.AsyncClient = field(init=False)
|
|
277
|
+
|
|
278
|
+
def __post_init__(self) -> None:
|
|
279
|
+
"""Initialize the HTTP client."""
|
|
280
|
+
self.client_kwargs["base_url"] = self.base_url
|
|
281
|
+
self._client = httpx.AsyncClient(**self.client_kwargs)
|
|
282
|
+
|
|
283
|
+
async def aclose(self) -> None:
|
|
284
|
+
"""Close the client."""
|
|
285
|
+
await self._client.aclose()
|
|
286
|
+
|
|
287
|
+
async def __aenter__(self) -> AsyncClient:
|
|
288
|
+
"""Enter the client.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
The client.
|
|
292
|
+
"""
|
|
293
|
+
return self
|
|
294
|
+
|
|
295
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
296
|
+
"""Exit the client."""
|
|
297
|
+
await self.aclose()
|
|
298
|
+
|
|
299
|
+
def __getattr__(self, name: str) -> AsyncDynamicClient:
|
|
300
|
+
"""Get an attribute.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
name: The name of the attribute.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
The dynamic client.
|
|
307
|
+
"""
|
|
308
|
+
return AsyncDynamicClient(
|
|
309
|
+
client=self,
|
|
310
|
+
segments=[PathSegment(name=name, format=self.segment_format)],
|
|
311
|
+
segment_format=self.segment_format,
|
|
312
|
+
known_paths=self.known_paths,
|
|
313
|
+
)
|