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 CHANGED
@@ -4,7 +4,7 @@ try:
4
4
  from enum import StrEnum
5
5
 
6
6
  except ImportError:
7
- from core_mixins.compatibility import StrEnum
7
+ from core_mixins.compatibility import StrEnum # type: ignore
8
8
 
9
9
 
10
10
  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
@@ -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
- It uses `aiohttp` to make the requests.
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
- :param session: The session to use for requests.
79
- :param retries: Retry strategy to apply. Pass zero (0) to avoid retries.
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
- """ If the session doesn't exist, it creates it """
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
- retries: Optional[int] = None,
127
- backoff_factor: Optional[int] = None,
128
- **kwargs
176
+ **kwargs,
129
177
  ) -> ClientResponse:
130
178
  """
131
- It makes the request using the session (externally provided or created
132
- if required) and return the response...
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
- :returns: `aiohttp.ClientResponse` object.
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
- attempts = 0
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 if self.backoff_factor is not None
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
- self._session = None
298
+ self._session = None