core-https 1.1.7__py3-none-any.whl → 2.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.
- core_https/__init__.py +1 -1
- core_https/exceptions.py +103 -18
- core_https/py.typed +0 -0
- core_https/requesters/aiohttp_.py +145 -25
- core_https/requesters/base.py +274 -44
- core_https/requesters/requests_.py +17 -11
- core_https/requesters/urllib3_.py +13 -15
- core_https/tests/aiohttp_.py +184 -22
- core_https/tests/base.py +77 -1
- core_https/tests/decorators.py +186 -41
- core_https/tests/requests_.py +156 -16
- core_https/tests/urllib3_.py +197 -33
- core_https/utils.py +209 -4
- core_https-2.0.0.dist-info/METADATA +112 -0
- core_https-2.0.0.dist-info/RECORD +20 -0
- core_https-1.1.7.dist-info/METADATA +0 -76
- core_https-1.1.7.dist-info/RECORD +0 -19
- {core_https-1.1.7.dist-info → core_https-2.0.0.dist-info}/WHEEL +0 -0
- {core_https-1.1.7.dist-info → core_https-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {core_https-1.1.7.dist-info → core_https-2.0.0.dist-info}/top_level.txt +0 -0
core_https/__init__.py
CHANGED
core_https/exceptions.py
CHANGED
|
@@ -1,47 +1,121 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
+
"""
|
|
4
|
+
HTTP client exception hierarchy for the core_https library.
|
|
5
|
+
|
|
6
|
+
This module provides a comprehensive set of exception classes for handling
|
|
7
|
+
HTTP-related errors in a structured and consistent manner. The exception
|
|
8
|
+
hierarchy is designed to allow for fine-grained error handling while
|
|
9
|
+
maintaining compatibility with standard HTTP status codes.
|
|
10
|
+
|
|
11
|
+
Exception Hierarchy:
|
|
12
|
+
Exception
|
|
13
|
+
└── InternalServerError (base for all HTTP errors)
|
|
14
|
+
├── ServiceException (handled service errors)
|
|
15
|
+
│ ├── AuthenticationException (401 Unauthorized)
|
|
16
|
+
│ ├── AuthorizationException (403 Forbidden)
|
|
17
|
+
│ └── RateLimitException (429 Too Many Requests)
|
|
18
|
+
└── RetryableException (errors that should trigger retries)
|
|
19
|
+
|
|
20
|
+
Usage Example:
|
|
21
|
+
Basic exception handling::
|
|
22
|
+
|
|
23
|
+
from core_https.exceptions import AuthenticationException, RateLimitException
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
response = requester.request(url="https://api.example.com")
|
|
27
|
+
except AuthenticationException as e:
|
|
28
|
+
logger.error(f"Authentication failed: {e.details}")
|
|
29
|
+
# Handle authentication error
|
|
30
|
+
except RateLimitException as e:
|
|
31
|
+
logger.warning(f"Rate limited: {e.details}")
|
|
32
|
+
# Implement backoff strategy
|
|
33
|
+
except ServiceException as e:
|
|
34
|
+
logger.error(f"Service error {e.status_code}: {e.details}")
|
|
35
|
+
# Handle other service errors
|
|
36
|
+
|
|
37
|
+
Error information extraction::
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# HTTP request that fails
|
|
41
|
+
pass
|
|
42
|
+
except InternalServerError as e:
|
|
43
|
+
error_info = e.get_error_info()
|
|
44
|
+
# {'type': 'AuthenticationException', 'details': 'Invalid API key'}
|
|
45
|
+
|
|
46
|
+
See Also:
|
|
47
|
+
- core_https.requesters.base.IRequester: Base requester that raises these exceptions
|
|
48
|
+
- HTTP status codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
|
49
|
+
"""
|
|
50
|
+
|
|
3
51
|
from typing import Dict
|
|
4
52
|
|
|
5
53
|
|
|
6
54
|
class InternalServerError(Exception):
|
|
7
|
-
"""
|
|
55
|
+
"""
|
|
56
|
+
Base class for all HTTP-related exceptions in the core_https library.
|
|
57
|
+
|
|
58
|
+
This exception serves as the root of the HTTP exception hierarchy and handles
|
|
59
|
+
unhandled errors that occur during HTTP operations. It provides structured
|
|
60
|
+
error information including HTTP status codes and detailed error messages.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
status_code: HTTP status code associated with the error
|
|
64
|
+
details: Detailed error message or description
|
|
65
|
+
"""
|
|
8
66
|
|
|
9
|
-
def __init__(
|
|
10
|
-
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
status_code: int,
|
|
70
|
+
details: str,
|
|
71
|
+
*args,
|
|
72
|
+
) -> None:
|
|
73
|
+
super().__init__(*args)
|
|
11
74
|
self.status_code = status_code
|
|
12
75
|
self.details = details
|
|
13
76
|
|
|
14
|
-
def get_error_info(self) -> Dict:
|
|
77
|
+
def get_error_info(self) -> Dict[str, str]:
|
|
78
|
+
"""
|
|
79
|
+
Get structured error information for logging or serialization.
|
|
80
|
+
:returns: Dictionary containing error type and details.
|
|
81
|
+
"""
|
|
82
|
+
|
|
15
83
|
return {
|
|
16
84
|
"type": self.__class__.__name__,
|
|
17
|
-
"details": self.details
|
|
85
|
+
"details": self.details,
|
|
18
86
|
}
|
|
19
87
|
|
|
20
88
|
|
|
21
89
|
class ServiceException(InternalServerError):
|
|
22
|
-
"""
|
|
90
|
+
"""Exception caused for handled errors within the service"""
|
|
23
91
|
|
|
24
92
|
|
|
25
93
|
class AuthenticationException(ServiceException):
|
|
26
|
-
"""
|
|
94
|
+
"""Exception caused for authentication [401] issues"""
|
|
27
95
|
|
|
28
96
|
def __init__(
|
|
29
97
|
self,
|
|
30
98
|
status_code: int = 401,
|
|
31
|
-
details: str = "Unauthorized"
|
|
99
|
+
details: str = "Unauthorized",
|
|
32
100
|
) -> None:
|
|
33
|
-
super().__init__(
|
|
101
|
+
super().__init__(
|
|
102
|
+
status_code=status_code,
|
|
103
|
+
details=details,
|
|
104
|
+
)
|
|
34
105
|
|
|
35
106
|
|
|
36
107
|
class AuthorizationException(ServiceException):
|
|
37
|
-
"""
|
|
108
|
+
"""Exception caused for authorization [403] issues"""
|
|
38
109
|
|
|
39
110
|
def __init__(
|
|
40
111
|
self,
|
|
41
112
|
status_code: int = 403,
|
|
42
|
-
details: str = "Forbidden"
|
|
113
|
+
details: str = "Forbidden",
|
|
43
114
|
) -> None:
|
|
44
|
-
super().__init__(
|
|
115
|
+
super().__init__(
|
|
116
|
+
status_code=status_code,
|
|
117
|
+
details=details,
|
|
118
|
+
)
|
|
45
119
|
|
|
46
120
|
|
|
47
121
|
class RateLimitException(ServiceException):
|
|
@@ -53,15 +127,26 @@ class RateLimitException(ServiceException):
|
|
|
53
127
|
def __init__(
|
|
54
128
|
self,
|
|
55
129
|
status_code: int = 429,
|
|
56
|
-
details: str = "Too Many Requests"
|
|
130
|
+
details: str = "Too Many Requests",
|
|
57
131
|
) -> None:
|
|
58
|
-
super().__init__(
|
|
132
|
+
super().__init__(
|
|
133
|
+
status_code=status_code,
|
|
134
|
+
details=details,
|
|
135
|
+
)
|
|
59
136
|
|
|
60
137
|
|
|
61
138
|
class RetryableException(InternalServerError):
|
|
62
139
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
140
|
+
Exception for HTTP errors that should trigger retry mechanisms.
|
|
141
|
+
|
|
142
|
+
This exception represents temporary failures that are likely to succeed
|
|
143
|
+
if retried after a short delay. Common retryable status codes include:
|
|
144
|
+
- 429 Too Many Requests (rate limiting)
|
|
145
|
+
- 502 Bad Gateway (temporary server issue)
|
|
146
|
+
- 503 Service Unavailable (server overload)
|
|
147
|
+
- 504 Gateway Timeout (temporary timeout)
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
raise RetryableException(status_code=503, details="Service temporarily unavailable")
|
|
151
|
+
raise RetryableException(status_code=502, details="Bad gateway from upstream")
|
|
67
152
|
"""
|
core_https/py.typed
ADDED
|
File without changes
|
|
@@ -13,12 +13,6 @@ try:
|
|
|
13
13
|
except ImportError:
|
|
14
14
|
from typing_extensions import Self
|
|
15
15
|
|
|
16
|
-
try:
|
|
17
|
-
from http import HTTPMethod
|
|
18
|
-
|
|
19
|
-
except ImportError:
|
|
20
|
-
from core_https.utils import HTTPMethod
|
|
21
|
-
|
|
22
16
|
from aiohttp import (
|
|
23
17
|
ClientResponse,
|
|
24
18
|
ClientResponseError,
|
|
@@ -28,11 +22,28 @@ from aiohttp import (
|
|
|
28
22
|
)
|
|
29
23
|
|
|
30
24
|
from .base import IRequester
|
|
25
|
+
from .base import HTTPMethod
|
|
31
26
|
|
|
32
27
|
|
|
33
28
|
class AioHttpRequester(IRequester):
|
|
34
29
|
"""
|
|
35
|
-
|
|
30
|
+
Asynchronous HTTP requester implementation using aiohttp.
|
|
31
|
+
|
|
32
|
+
This class provides an async HTTP client interface using the aiohttp library.
|
|
33
|
+
It supports automatic session management, configurable retry logic with exponential
|
|
34
|
+
backoff, connection pooling, and comprehensive error handling.
|
|
35
|
+
|
|
36
|
+
The requester can work with externally provided ClientSession objects or create
|
|
37
|
+
and manage its own session internally. It implements the async context manager
|
|
38
|
+
protocol for convenient resource cleanup.
|
|
39
|
+
|
|
40
|
+
Features:
|
|
41
|
+
- Automatic session creation and management
|
|
42
|
+
- Configurable retry logic with exponential backoff
|
|
43
|
+
- Connection pooling with configurable limits
|
|
44
|
+
- Comprehensive HTTP exception mapping
|
|
45
|
+
- Support for custom timeouts per request
|
|
46
|
+
- Context manager support for resource cleanup
|
|
36
47
|
|
|
37
48
|
.. code-block:: python
|
|
38
49
|
|
|
@@ -72,11 +83,29 @@ class AioHttpRequester(IRequester):
|
|
|
72
83
|
self,
|
|
73
84
|
session: Optional[ClientSession] = None,
|
|
74
85
|
retries: Optional[int] = 3,
|
|
75
|
-
**kwargs
|
|
86
|
+
**kwargs,
|
|
76
87
|
) -> None:
|
|
77
88
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
Initialize the AioHttpRequester.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
session: Optional pre-configured aiohttp ClientSession to use for requests.
|
|
93
|
+
If not provided, a new session will be created automatically with
|
|
94
|
+
the configured timeout and connection limits. When providing a custom
|
|
95
|
+
session, you are responsible for closing it.
|
|
96
|
+
|
|
97
|
+
retries: Number of retry attempts for failed requests. Defaults to 3.
|
|
98
|
+
Set to 0 to disable retries completely. Only applies to retryable
|
|
99
|
+
errors like network timeouts and server errors (5xx status codes).
|
|
100
|
+
|
|
101
|
+
**kwargs: Additional arguments passed to the base IRequester class,
|
|
102
|
+
including encoding, raise_for_status, backoff_factor, timeout,
|
|
103
|
+
connector_limit, and connector_limit_per_host.
|
|
104
|
+
|
|
105
|
+
Note:
|
|
106
|
+
The timeout specified in kwargs becomes the default session timeout.
|
|
107
|
+
Individual requests can override this using the timeout parameter
|
|
108
|
+
in the request() method.
|
|
80
109
|
"""
|
|
81
110
|
|
|
82
111
|
super().__init__(**kwargs)
|
|
@@ -99,7 +128,25 @@ class AioHttpRequester(IRequester):
|
|
|
99
128
|
return "aiohttp"
|
|
100
129
|
|
|
101
130
|
async def _ensure_session(self) -> ClientSession:
|
|
102
|
-
"""
|
|
131
|
+
"""
|
|
132
|
+
Ensure a ClientSession exists, creating one if necessary.
|
|
133
|
+
|
|
134
|
+
This method implements a thread-safe lazy initialization pattern using
|
|
135
|
+
double-checked locking to ensure only one session is created even when
|
|
136
|
+
called concurrently from multiple coroutines.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
ClientSession: The active aiohttp ClientSession instance.
|
|
140
|
+
|
|
141
|
+
Note:
|
|
142
|
+
If a session was provided in the constructor, it will be returned as-is.
|
|
143
|
+
If no session was provided, a new one will be created with the configured
|
|
144
|
+
timeout and connection limits.
|
|
145
|
+
|
|
146
|
+
Thread Safety:
|
|
147
|
+
This method is safe to call concurrently from multiple coroutines.
|
|
148
|
+
The double-check locking pattern ensures only one session is created.
|
|
149
|
+
"""
|
|
103
150
|
|
|
104
151
|
if self._session is None:
|
|
105
152
|
async with self._session_lock:
|
|
@@ -108,8 +155,9 @@ class AioHttpRequester(IRequester):
|
|
|
108
155
|
timeout=self._timeout,
|
|
109
156
|
connector=TCPConnector(
|
|
110
157
|
limit=self.connector_limit,
|
|
111
|
-
limit_per_host=self.connector_limit_per_host
|
|
112
|
-
)
|
|
158
|
+
limit_per_host=self.connector_limit_per_host,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
113
161
|
|
|
114
162
|
self._owns_session = True
|
|
115
163
|
|
|
@@ -119,19 +167,69 @@ class AioHttpRequester(IRequester):
|
|
|
119
167
|
self,
|
|
120
168
|
url: str,
|
|
121
169
|
method: HTTPMethod = HTTPMethod.GET,
|
|
122
|
-
session: Optional[ClientSession] = None,
|
|
123
170
|
headers: Optional[Dict[str, Any]] = None,
|
|
171
|
+
retries: Optional[int] = None,
|
|
172
|
+
backoff_factor: Optional[float] = None,
|
|
173
|
+
session: Optional[ClientSession] = None,
|
|
124
174
|
params: Optional[Dict[str, Any]] = None,
|
|
125
175
|
timeout: Optional[float] = None,
|
|
126
|
-
|
|
127
|
-
backoff_factor: Optional[int] = None,
|
|
128
|
-
**kwargs
|
|
176
|
+
**kwargs,
|
|
129
177
|
) -> ClientResponse:
|
|
130
178
|
"""
|
|
131
|
-
|
|
132
|
-
|
|
179
|
+
Make an asynchronous HTTP request with retry logic.
|
|
180
|
+
|
|
181
|
+
This method performs HTTP requests with automatic retry functionality,
|
|
182
|
+
exponential backoff, and comprehensive error handling. Failed requests
|
|
183
|
+
are retried based on the configured retry policy.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
url: The target URL for the HTTP request.
|
|
133
187
|
|
|
134
|
-
|
|
188
|
+
method: HTTP method to use. Defaults to GET. Supports all standard
|
|
189
|
+
HTTP methods (GET, POST, PUT, DELETE, etc.).
|
|
190
|
+
|
|
191
|
+
headers: Optional HTTP headers to include in the request. These will
|
|
192
|
+
be merged with any session-level headers.
|
|
193
|
+
|
|
194
|
+
retries: Number of retry attempts for this specific request.
|
|
195
|
+
If not provided, uses the instance-level retry setting.
|
|
196
|
+
Set to 0 to disable retries for this request.
|
|
197
|
+
|
|
198
|
+
backoff_factor: Multiplier for exponential backoff between retries.
|
|
199
|
+
The actual delay is calculated as: backoff_factor * attempt_number.
|
|
200
|
+
If not provided, uses the instance-level setting or defaults to 0.5.
|
|
201
|
+
|
|
202
|
+
session: Optional ClientSession to use for this request.
|
|
203
|
+
If not provided, uses the requester's session (creating one if necessary).
|
|
204
|
+
Useful for per-request session customization.
|
|
205
|
+
|
|
206
|
+
params: URL query parameters to include in the request.
|
|
207
|
+
Will be properly URL-encoded and appended to the URL.
|
|
208
|
+
|
|
209
|
+
timeout: Request timeout in seconds for this specific request.
|
|
210
|
+
Overrides the instance-level timeout setting.
|
|
211
|
+
Set to None to use the default timeout.
|
|
212
|
+
|
|
213
|
+
**kwargs: Additional parameters passed to aiohttp's request method.
|
|
214
|
+
Common options include: json, data, cookies, ssl, proxy, etc.
|
|
215
|
+
See aiohttp.ClientSession.request documentation for full list.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
ClientResponse: The aiohttp response object. Use methods like
|
|
219
|
+
.json(), .text(), .read() to extract the response content.
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
AuthenticationException: For 401 Unauthorized responses.
|
|
223
|
+
AuthorizationException: For 403 Forbidden responses.
|
|
224
|
+
RateLimitException: For 429 Too Many Requests (when retries exhausted).
|
|
225
|
+
RetryableException: For 502/503/504 server errors (when retries exhausted).
|
|
226
|
+
ServiceException: For other 4xx client errors.
|
|
227
|
+
InternalServerError: For 5xx server errors (when retries exhausted).
|
|
228
|
+
|
|
229
|
+
Note:
|
|
230
|
+
The retry logic only applies to network errors and server errors (5xx).
|
|
231
|
+
Client errors (4xx) are not retried, except for 429 (rate limit)
|
|
232
|
+
which may be retried based on the configured policy.
|
|
135
233
|
"""
|
|
136
234
|
|
|
137
235
|
session_ = session or await self._ensure_session()
|
|
@@ -141,15 +239,19 @@ class AioHttpRequester(IRequester):
|
|
|
141
239
|
kwargs_["timeout"] = ClientTimeout(total=timeout)
|
|
142
240
|
|
|
143
241
|
retries = retries if retries is not None else self.retries
|
|
144
|
-
|
|
242
|
+
if retries is None:
|
|
243
|
+
retries = 3
|
|
145
244
|
|
|
146
245
|
backoff_factor = (
|
|
147
246
|
backoff_factor
|
|
148
247
|
if backoff_factor is not None
|
|
149
|
-
else self.backoff_factor
|
|
248
|
+
else self.backoff_factor
|
|
249
|
+
if self.backoff_factor is not None
|
|
150
250
|
else 0.5
|
|
151
251
|
)
|
|
152
252
|
|
|
253
|
+
attempts = 0
|
|
254
|
+
|
|
153
255
|
while True:
|
|
154
256
|
attempts += 1
|
|
155
257
|
|
|
@@ -159,7 +261,8 @@ class AioHttpRequester(IRequester):
|
|
|
159
261
|
url=url,
|
|
160
262
|
headers=headers,
|
|
161
263
|
params=params,
|
|
162
|
-
**kwargs_
|
|
264
|
+
**kwargs_,
|
|
265
|
+
)
|
|
163
266
|
|
|
164
267
|
if self.raise_for_status:
|
|
165
268
|
response.raise_for_status()
|
|
@@ -172,7 +275,24 @@ class AioHttpRequester(IRequester):
|
|
|
172
275
|
|
|
173
276
|
await asyncio.sleep(backoff_factor * attempts)
|
|
174
277
|
|
|
175
|
-
async def close(self):
|
|
278
|
+
async def close(self) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Close the internal session if it was created by this requester.
|
|
281
|
+
|
|
282
|
+
This method performs cleanup of the internal ClientSession, but only
|
|
283
|
+
if the session was created internally. If a custom session was provided
|
|
284
|
+
in the constructor, it will not be closed as the caller is responsible
|
|
285
|
+
for managing its lifecycle.
|
|
286
|
+
|
|
287
|
+
The session reference is always cleared after calling this method,
|
|
288
|
+
regardless of whether it was closed or not.
|
|
289
|
+
|
|
290
|
+
Note:
|
|
291
|
+
This method is automatically called when using the async context
|
|
292
|
+
manager protocol (__aexit__). Manual calling is only necessary
|
|
293
|
+
when not using the context manager pattern.
|
|
294
|
+
"""
|
|
295
|
+
|
|
176
296
|
if self._session and self._owns_session:
|
|
177
297
|
await self._session.close()
|
|
178
|
-
|
|
298
|
+
self._session = None
|