core-https 1.1.7__py3-none-any.whl → 2.0.1__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 CHANGED
@@ -1,10 +1,23 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ from core_mixins import StrEnum
4
+
3
5
  try:
4
- from enum import StrEnum
6
+ from http import HTTPStatus as _HTTPStatus
5
7
 
6
8
  except ImportError:
7
- from core_mixins.compatibility import StrEnum
9
+ from .utils import HTTPStatus as _HTTPStatus # type: ignore
10
+
11
+
12
+ __all__ = [
13
+ "HTTPStatus",
14
+ "StatusInfo"
15
+ ]
16
+
17
+
18
+ # Type alias that works with both standard library
19
+ # and the custom HTTPStatus...
20
+ HTTPStatus = _HTTPStatus
8
21
 
9
22
 
10
23
  class StatusInfo(StrEnum):
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
- """ Base class for exceptions on the project and unhandled errors """
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__(self, status_code: int, details: str, *args):
10
- super(InternalServerError, self).__init__(*args)
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
- """ Exception caused for handled errors within the service """
90
+ """Exception caused for handled errors within the service"""
23
91
 
24
92
 
25
93
  class AuthenticationException(ServiceException):
26
- """ Exception caused for authentication [401] issues """
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__(status_code=status_code, details=details)
101
+ super().__init__(
102
+ status_code=status_code,
103
+ details=details,
104
+ )
34
105
 
35
106
 
36
107
  class AuthorizationException(ServiceException):
37
- """ Exception caused for authorization [403] issues """
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__(status_code=status_code, details=details)
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__(status_code=status_code, details=details)
132
+ super().__init__(
133
+ status_code=status_code,
134
+ details=details,
135
+ )
59
136
 
60
137
 
61
138
  class RetryableException(InternalServerError):
62
139
  """
63
- Special case for exceptions that are considered retryable
64
- like: 429, 502, 503 or 504. It's useful when a retry mechanism
65
- is used, and you want to capture one type of error to trigger
66
- the retry.
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
@@ -7,18 +7,6 @@ from typing import Any
7
7
  from typing import Dict
8
8
  from typing import Optional
9
9
 
10
- try:
11
- from typing import Self
12
-
13
- except ImportError:
14
- from typing_extensions import Self
15
-
16
- try:
17
- from http import HTTPMethod
18
-
19
- except ImportError:
20
- from core_https.utils import HTTPMethod
21
-
22
10
  from aiohttp import (
23
11
  ClientResponse,
24
12
  ClientResponseError,
@@ -26,13 +14,31 @@ from aiohttp import (
26
14
  ClientTimeout,
27
15
  TCPConnector,
28
16
  )
17
+ from core_mixins import Self
29
18
 
19
+ from .base import HTTPMethod
30
20
  from .base import IRequester
31
21
 
32
22
 
33
23
  class AioHttpRequester(IRequester):
34
24
  """
35
- It uses `aiohttp` to make the requests.
25
+ Asynchronous HTTP requester implementation using aiohttp.
26
+
27
+ This class provides an async HTTP client interface using the aiohttp library.
28
+ It supports automatic session management, configurable retry logic with exponential
29
+ backoff, connection pooling, and comprehensive error handling.
30
+
31
+ The requester can work with externally provided ClientSession objects or create
32
+ and manage its own session internally. It implements the async context manager
33
+ protocol for convenient resource cleanup.
34
+
35
+ Features:
36
+ - Automatic session creation and management
37
+ - Configurable retry logic with exponential backoff
38
+ - Connection pooling with configurable limits
39
+ - Comprehensive HTTP exception mapping
40
+ - Support for custom timeouts per request
41
+ - Context manager support for resource cleanup
36
42
 
37
43
  .. code-block:: python
38
44
 
@@ -72,11 +78,29 @@ class AioHttpRequester(IRequester):
72
78
  self,
73
79
  session: Optional[ClientSession] = None,
74
80
  retries: Optional[int] = 3,
75
- **kwargs
81
+ **kwargs,
76
82
  ) -> None:
77
83
  """
78
- :param session: The session to use for requests.
79
- :param retries: Retry strategy to apply. Pass zero (0) to avoid retries.
84
+ Initialize the AioHttpRequester.
85
+
86
+ Args:
87
+ session: Optional pre-configured aiohttp ClientSession to use for requests.
88
+ If not provided, a new session will be created automatically with
89
+ the configured timeout and connection limits. When providing a custom
90
+ session, you are responsible for closing it.
91
+
92
+ retries: Number of retry attempts for failed requests. Defaults to 3.
93
+ Set to 0 to disable retries completely. Only applies to retryable
94
+ errors like network timeouts and server errors (5xx status codes).
95
+
96
+ **kwargs: Additional arguments passed to the base IRequester class,
97
+ including encoding, raise_for_status, backoff_factor, timeout,
98
+ connector_limit, and connector_limit_per_host.
99
+
100
+ Note:
101
+ The timeout specified in kwargs becomes the default session timeout.
102
+ Individual requests can override this using the timeout parameter
103
+ in the request() method.
80
104
  """
81
105
 
82
106
  super().__init__(**kwargs)
@@ -99,7 +123,25 @@ class AioHttpRequester(IRequester):
99
123
  return "aiohttp"
100
124
 
101
125
  async def _ensure_session(self) -> ClientSession:
102
- """ If the session doesn't exist, it creates it """
126
+ """
127
+ Ensure a ClientSession exists, creating one if necessary.
128
+
129
+ This method implements a thread-safe lazy initialization pattern using
130
+ double-checked locking to ensure only one session is created even when
131
+ called concurrently from multiple coroutines.
132
+
133
+ Returns:
134
+ ClientSession: The active aiohttp ClientSession instance.
135
+
136
+ Note:
137
+ If a session was provided in the constructor, it will be returned as-is.
138
+ If no session was provided, a new one will be created with the configured
139
+ timeout and connection limits.
140
+
141
+ Thread Safety:
142
+ This method is safe to call concurrently from multiple coroutines.
143
+ The double-check locking pattern ensures only one session is created.
144
+ """
103
145
 
104
146
  if self._session is None:
105
147
  async with self._session_lock:
@@ -108,8 +150,9 @@ class AioHttpRequester(IRequester):
108
150
  timeout=self._timeout,
109
151
  connector=TCPConnector(
110
152
  limit=self.connector_limit,
111
- limit_per_host=self.connector_limit_per_host
112
- ))
153
+ limit_per_host=self.connector_limit_per_host,
154
+ ),
155
+ )
113
156
 
114
157
  self._owns_session = True
115
158
 
@@ -119,19 +162,69 @@ class AioHttpRequester(IRequester):
119
162
  self,
120
163
  url: str,
121
164
  method: HTTPMethod = HTTPMethod.GET,
122
- session: Optional[ClientSession] = None,
123
165
  headers: Optional[Dict[str, Any]] = None,
166
+ retries: Optional[int] = None,
167
+ backoff_factor: Optional[float] = None,
168
+ session: Optional[ClientSession] = None,
124
169
  params: Optional[Dict[str, Any]] = None,
125
170
  timeout: Optional[float] = None,
126
- retries: Optional[int] = None,
127
- backoff_factor: Optional[int] = None,
128
- **kwargs
171
+ **kwargs,
129
172
  ) -> ClientResponse:
130
173
  """
131
- It makes the request using the session (externally provided or created
132
- if required) and return the response...
174
+ Make an asynchronous HTTP request with retry logic.
175
+
176
+ This method performs HTTP requests with automatic retry functionality,
177
+ exponential backoff, and comprehensive error handling. Failed requests
178
+ are retried based on the configured retry policy.
179
+
180
+ Args:
181
+ url: The target URL for the HTTP request.
133
182
 
134
- :returns: `aiohttp.ClientResponse` object.
183
+ method: HTTP method to use. Defaults to GET. Supports all standard
184
+ HTTP methods (GET, POST, PUT, DELETE, etc.).
185
+
186
+ headers: Optional HTTP headers to include in the request. These will
187
+ be merged with any session-level headers.
188
+
189
+ retries: Number of retry attempts for this specific request.
190
+ If not provided, uses the instance-level retry setting.
191
+ Set to 0 to disable retries for this request.
192
+
193
+ backoff_factor: Multiplier for exponential backoff between retries.
194
+ The actual delay is calculated as: backoff_factor * attempt_number.
195
+ If not provided, uses the instance-level setting or defaults to 0.5.
196
+
197
+ session: Optional ClientSession to use for this request.
198
+ If not provided, uses the requester's session (creating one if necessary).
199
+ Useful for per-request session customization.
200
+
201
+ params: URL query parameters to include in the request.
202
+ Will be properly URL-encoded and appended to the URL.
203
+
204
+ timeout: Request timeout in seconds for this specific request.
205
+ Overrides the instance-level timeout setting.
206
+ Set to None to use the default timeout.
207
+
208
+ **kwargs: Additional parameters passed to aiohttp's request method.
209
+ Common options include: json, data, cookies, ssl, proxy, etc.
210
+ See aiohttp.ClientSession.request documentation for full list.
211
+
212
+ Returns:
213
+ ClientResponse: The aiohttp response object. Use methods like
214
+ .json(), .text(), .read() to extract the response content.
215
+
216
+ Raises:
217
+ AuthenticationException: For 401 Unauthorized responses.
218
+ AuthorizationException: For 403 Forbidden responses.
219
+ RateLimitException: For 429 Too Many Requests (when retries exhausted).
220
+ RetryableException: For 502/503/504 server errors (when retries exhausted).
221
+ ServiceException: For other 4xx client errors.
222
+ InternalServerError: For 5xx server errors (when retries exhausted).
223
+
224
+ Note:
225
+ The retry logic only applies to network errors and server errors (5xx).
226
+ Client errors (4xx) are not retried, except for 429 (rate limit)
227
+ which may be retried based on the configured policy.
135
228
  """
136
229
 
137
230
  session_ = session or await self._ensure_session()
@@ -141,15 +234,19 @@ class AioHttpRequester(IRequester):
141
234
  kwargs_["timeout"] = ClientTimeout(total=timeout)
142
235
 
143
236
  retries = retries if retries is not None else self.retries
144
- attempts = 0
237
+ if retries is None:
238
+ retries = 3
145
239
 
146
240
  backoff_factor = (
147
241
  backoff_factor
148
242
  if backoff_factor is not None
149
- else self.backoff_factor if self.backoff_factor is not None
243
+ else self.backoff_factor
244
+ if self.backoff_factor is not None
150
245
  else 0.5
151
246
  )
152
247
 
248
+ attempts = 0
249
+
153
250
  while True:
154
251
  attempts += 1
155
252
 
@@ -159,7 +256,8 @@ class AioHttpRequester(IRequester):
159
256
  url=url,
160
257
  headers=headers,
161
258
  params=params,
162
- **kwargs_)
259
+ **kwargs_,
260
+ )
163
261
 
164
262
  if self.raise_for_status:
165
263
  response.raise_for_status()
@@ -172,7 +270,24 @@ class AioHttpRequester(IRequester):
172
270
 
173
271
  await asyncio.sleep(backoff_factor * attempts)
174
272
 
175
- async def close(self):
273
+ async def close(self) -> None:
274
+ """
275
+ Close the internal session if it was created by this requester.
276
+
277
+ This method performs cleanup of the internal ClientSession, but only
278
+ if the session was created internally. If a custom session was provided
279
+ in the constructor, it will not be closed as the caller is responsible
280
+ for managing its lifecycle.
281
+
282
+ The session reference is always cleared after calling this method,
283
+ regardless of whether it was closed or not.
284
+
285
+ Note:
286
+ This method is automatically called when using the async context
287
+ manager protocol (__aexit__). Manual calling is only necessary
288
+ when not using the context manager pattern.
289
+ """
290
+
176
291
  if self._session and self._owns_session:
177
292
  await self._session.close()
178
- self._session = None
293
+ self._session = None