core-https 1.1.7__tar.gz → 2.0.1__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.
Files changed (39) hide show
  1. core_https-2.0.1/PKG-INFO +112 -0
  2. core_https-2.0.1/README.rst +74 -0
  3. {core_https-1.1.7 → core_https-2.0.1}/core_https/__init__.py +15 -2
  4. core_https-2.0.1/core_https/exceptions.py +152 -0
  5. core_https-2.0.1/core_https/requesters/aiohttp_.py +293 -0
  6. core_https-2.0.1/core_https/requesters/base.py +391 -0
  7. {core_https-1.1.7 → core_https-2.0.1}/core_https/requesters/requests_.py +17 -11
  8. {core_https-1.1.7 → core_https-2.0.1}/core_https/requesters/urllib3_.py +13 -15
  9. core_https-2.0.1/core_https/tests/__init__.py +0 -0
  10. core_https-2.0.1/core_https/tests/aiohttp_.py +268 -0
  11. core_https-2.0.1/core_https/tests/base.py +88 -0
  12. core_https-2.0.1/core_https/tests/decorators.py +325 -0
  13. core_https-2.0.1/core_https/tests/requests_.py +215 -0
  14. core_https-2.0.1/core_https/tests/urllib3_.py +353 -0
  15. core_https-2.0.1/core_https/utils.py +359 -0
  16. core_https-2.0.1/core_https.egg-info/PKG-INFO +112 -0
  17. {core_https-1.1.7 → core_https-2.0.1}/core_https.egg-info/SOURCES.txt +2 -1
  18. core_https-2.0.1/core_https.egg-info/requires.txt +14 -0
  19. {core_https-1.1.7 → core_https-2.0.1}/pyproject.toml +16 -17
  20. core_https-1.1.7/PKG-INFO +0 -76
  21. core_https-1.1.7/README.md +0 -34
  22. core_https-1.1.7/core_https/exceptions.py +0 -67
  23. core_https-1.1.7/core_https/requesters/aiohttp_.py +0 -178
  24. core_https-1.1.7/core_https/requesters/base.py +0 -161
  25. core_https-1.1.7/core_https/tests/aiohttp_.py +0 -106
  26. core_https-1.1.7/core_https/tests/base.py +0 -12
  27. core_https-1.1.7/core_https/tests/decorators.py +0 -180
  28. core_https-1.1.7/core_https/tests/requests_.py +0 -75
  29. core_https-1.1.7/core_https/tests/urllib3_.py +0 -189
  30. core_https-1.1.7/core_https/utils.py +0 -159
  31. core_https-1.1.7/core_https.egg-info/PKG-INFO +0 -76
  32. core_https-1.1.7/core_https.egg-info/requires.txt +0 -24
  33. {core_https-1.1.7 → core_https-2.0.1}/LICENSE +0 -0
  34. /core_https-1.1.7/core_https/requesters/__init__.py → /core_https-2.0.1/core_https/py.typed +0 -0
  35. {core_https-1.1.7/core_https/tests → core_https-2.0.1/core_https/requesters}/__init__.py +0 -0
  36. {core_https-1.1.7 → core_https-2.0.1}/core_https.egg-info/dependency_links.txt +0 -0
  37. {core_https-1.1.7 → core_https-2.0.1}/core_https.egg-info/top_level.txt +0 -0
  38. {core_https-1.1.7 → core_https-2.0.1}/setup.cfg +0 -0
  39. {core_https-1.1.7 → core_https-2.0.1}/setup.py +0 -0
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: core-https
3
+ Version: 2.0.1
4
+ Summary: This project/library contains common elements related to HTTP & API services...
5
+ Author-email: Alejandro Cora González <alek.cora.glez@gmail.com>
6
+ Maintainer: Alejandro Cora González
7
+ License: MIT
8
+ Project-URL: Homepage, https://gitlab.com/bytecode-solutions/core/core-https
9
+ Project-URL: Repository, https://gitlab.com/bytecode-solutions/core/core-https
10
+ Project-URL: Documentation, https://core-https.readthedocs.io/en/latest/
11
+ Project-URL: Issues, https://gitlab.com/bytecode-solutions/core/core-https/-/issues
12
+ Project-URL: Changelog, https://gitlab.com/bytecode-solutions/core/core-https/-/blob/master/CHANGELOG.md
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: Utilities
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/x-rst
27
+ License-File: LICENSE
28
+ Requires-Dist: aiohttp<4.0.0,>=3.12.0; python_version >= "3.9"
29
+ Requires-Dist: core-mixins>=2.2.2
30
+ Requires-Dist: core-tests>=2.0.2
31
+ Requires-Dist: requests<3.0.0,>=2.32.3; python_version >= "3.9"
32
+ Requires-Dist: typing-extensions>=4.8.0; python_version >= "3.9" and python_version < "3.11"
33
+ Requires-Dist: urllib3<3.0.0,>=2.2.3; python_version >= "3.9"
34
+ Provides-Extra: dev
35
+ Requires-Dist: core-dev-tools>=1.0.1; extra == "dev"
36
+ Requires-Dist: types-requests>=2.32.0.20250602; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ core-https
40
+ ===============================================================================
41
+
42
+ This project/library contains common elements related to HTTP...
43
+
44
+ ===============================================================================
45
+
46
+ .. image:: https://img.shields.io/pypi/pyversions/core-https.svg
47
+ :target: https://pypi.org/project/core-https/
48
+ :alt: Python Versions
49
+
50
+ .. image:: https://img.shields.io/badge/license-MIT-blue.svg
51
+ :target: https://gitlab.com/bytecode-solutions/core/core-https/-/blob/main/LICENSE
52
+ :alt: License
53
+
54
+ .. image:: https://gitlab.com/bytecode-solutions/core/core-https/badges/release/pipeline.svg
55
+ :target: https://gitlab.com/bytecode-solutions/core/core-https/-/pipelines
56
+ :alt: Pipeline Status
57
+
58
+ .. image:: https://readthedocs.org/projects/core-https/badge/?version=latest
59
+ :target: https://readthedocs.org/projects/core-https/
60
+ :alt: Docs Status
61
+
62
+ .. image:: https://img.shields.io/badge/security-bandit-yellow.svg
63
+ :target: https://github.com/PyCQA/bandit
64
+ :alt: Security
65
+
66
+ |
67
+
68
+ Execution Environment
69
+ ---------------------------------------
70
+
71
+ Install libraries
72
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73
+
74
+ .. code-block:: shell
75
+
76
+ pip install --upgrade pip
77
+ pip install virtualenv
78
+ ..
79
+
80
+ Create the Python Virtual Environment.
81
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82
+
83
+ .. code-block:: shell
84
+
85
+ virtualenv --python={{python-version}} .venv
86
+ virtualenv --python=python3.11 .venv
87
+ ..
88
+
89
+ Activate the Virtual Environment.
90
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91
+
92
+ .. code-block:: shell
93
+
94
+ source .venv/bin/activate
95
+ ..
96
+
97
+ Install required libraries.
98
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
99
+
100
+ .. code-block:: shell
101
+
102
+ pip install .
103
+ ..
104
+
105
+ Check tests and coverage.
106
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
107
+
108
+ .. code-block:: shell
109
+
110
+ python manager.py run-tests
111
+ python manager.py run-coverage
112
+ ..
@@ -0,0 +1,74 @@
1
+ core-https
2
+ ===============================================================================
3
+
4
+ This project/library contains common elements related to HTTP...
5
+
6
+ ===============================================================================
7
+
8
+ .. image:: https://img.shields.io/pypi/pyversions/core-https.svg
9
+ :target: https://pypi.org/project/core-https/
10
+ :alt: Python Versions
11
+
12
+ .. image:: https://img.shields.io/badge/license-MIT-blue.svg
13
+ :target: https://gitlab.com/bytecode-solutions/core/core-https/-/blob/main/LICENSE
14
+ :alt: License
15
+
16
+ .. image:: https://gitlab.com/bytecode-solutions/core/core-https/badges/release/pipeline.svg
17
+ :target: https://gitlab.com/bytecode-solutions/core/core-https/-/pipelines
18
+ :alt: Pipeline Status
19
+
20
+ .. image:: https://readthedocs.org/projects/core-https/badge/?version=latest
21
+ :target: https://readthedocs.org/projects/core-https/
22
+ :alt: Docs Status
23
+
24
+ .. image:: https://img.shields.io/badge/security-bandit-yellow.svg
25
+ :target: https://github.com/PyCQA/bandit
26
+ :alt: Security
27
+
28
+ |
29
+
30
+ Execution Environment
31
+ ---------------------------------------
32
+
33
+ Install libraries
34
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35
+
36
+ .. code-block:: shell
37
+
38
+ pip install --upgrade pip
39
+ pip install virtualenv
40
+ ..
41
+
42
+ Create the Python Virtual Environment.
43
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
+
45
+ .. code-block:: shell
46
+
47
+ virtualenv --python={{python-version}} .venv
48
+ virtualenv --python=python3.11 .venv
49
+ ..
50
+
51
+ Activate the Virtual Environment.
52
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
53
+
54
+ .. code-block:: shell
55
+
56
+ source .venv/bin/activate
57
+ ..
58
+
59
+ Install required libraries.
60
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61
+
62
+ .. code-block:: shell
63
+
64
+ pip install .
65
+ ..
66
+
67
+ Check tests and coverage.
68
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
69
+
70
+ .. code-block:: shell
71
+
72
+ python manager.py run-tests
73
+ python manager.py run-coverage
74
+ ..
@@ -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):
@@ -0,0 +1,152 @@
1
+ # -*- coding: utf-8 -*-
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
+
51
+ from typing import Dict
52
+
53
+
54
+ class InternalServerError(Exception):
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
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ status_code: int,
70
+ details: str,
71
+ *args,
72
+ ) -> None:
73
+ super().__init__(*args)
74
+ self.status_code = status_code
75
+ self.details = details
76
+
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
+
83
+ return {
84
+ "type": self.__class__.__name__,
85
+ "details": self.details,
86
+ }
87
+
88
+
89
+ class ServiceException(InternalServerError):
90
+ """Exception caused for handled errors within the service"""
91
+
92
+
93
+ class AuthenticationException(ServiceException):
94
+ """Exception caused for authentication [401] issues"""
95
+
96
+ def __init__(
97
+ self,
98
+ status_code: int = 401,
99
+ details: str = "Unauthorized",
100
+ ) -> None:
101
+ super().__init__(
102
+ status_code=status_code,
103
+ details=details,
104
+ )
105
+
106
+
107
+ class AuthorizationException(ServiceException):
108
+ """Exception caused for authorization [403] issues"""
109
+
110
+ def __init__(
111
+ self,
112
+ status_code: int = 403,
113
+ details: str = "Forbidden",
114
+ ) -> None:
115
+ super().__init__(
116
+ status_code=status_code,
117
+ details=details,
118
+ )
119
+
120
+
121
+ class RateLimitException(ServiceException):
122
+ """
123
+ Exception caused [429] when a client has sent too many requests
124
+ to a server within a given time frame.
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ status_code: int = 429,
130
+ details: str = "Too Many Requests",
131
+ ) -> None:
132
+ super().__init__(
133
+ status_code=status_code,
134
+ details=details,
135
+ )
136
+
137
+
138
+ class RetryableException(InternalServerError):
139
+ """
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")
152
+ """
@@ -0,0 +1,293 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+ from typing import Dict
8
+ from typing import Optional
9
+
10
+ from aiohttp import (
11
+ ClientResponse,
12
+ ClientResponseError,
13
+ ClientSession,
14
+ ClientTimeout,
15
+ TCPConnector,
16
+ )
17
+ from core_mixins import Self
18
+
19
+ from .base import HTTPMethod
20
+ from .base import IRequester
21
+
22
+
23
+ class AioHttpRequester(IRequester):
24
+ """
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
42
+
43
+ .. code-block:: python
44
+
45
+ import aiohttp
46
+ from core_https.requesters.aiohttp_ import AioHttpRequester
47
+ from core_https.utils import HTTPMethod
48
+
49
+ requester: AioHttpRequester = AioHttpRequester(raise_for_status=True)
50
+
51
+ async def get():
52
+ # This is optional as the client creates one session for you if not provided.
53
+ session = aiohttp.ClientSession()
54
+
55
+ try:
56
+ response = await requester.request(
57
+ method=HTTPMethod.GET,
58
+ session=session,
59
+ url=url,
60
+ params={
61
+ "x-api-key": "..."
62
+ })
63
+
64
+ return await response.text()
65
+
66
+ except Exception as error:
67
+ pass
68
+
69
+ finally:
70
+ await session.close()
71
+
72
+ res = asyncio.run(get())
73
+ print(res)
74
+ ..
75
+ """
76
+
77
+ def __init__(
78
+ self,
79
+ session: Optional[ClientSession] = None,
80
+ retries: Optional[int] = 3,
81
+ **kwargs,
82
+ ) -> None:
83
+ """
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.
104
+ """
105
+
106
+ super().__init__(**kwargs)
107
+
108
+ self._session = session
109
+ self._session_lock = asyncio.Lock()
110
+ self._owns_session = session is None
111
+ self._timeout = ClientTimeout(total=self.timeout)
112
+ self.retries = retries
113
+
114
+ async def __aenter__(self) -> Self:
115
+ await self._ensure_session()
116
+ return self
117
+
118
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
119
+ await self.close()
120
+
121
+ @classmethod
122
+ def engine(cls) -> str:
123
+ return "aiohttp"
124
+
125
+ async def _ensure_session(self) -> ClientSession:
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
+ """
145
+
146
+ if self._session is None:
147
+ async with self._session_lock:
148
+ if self._session is None: # Double-check after acquiring lock...
149
+ self._session = ClientSession(
150
+ timeout=self._timeout,
151
+ connector=TCPConnector(
152
+ limit=self.connector_limit,
153
+ limit_per_host=self.connector_limit_per_host,
154
+ ),
155
+ )
156
+
157
+ self._owns_session = True
158
+
159
+ return self._session
160
+
161
+ async def request(
162
+ self,
163
+ url: str,
164
+ method: HTTPMethod = HTTPMethod.GET,
165
+ headers: Optional[Dict[str, Any]] = None,
166
+ retries: Optional[int] = None,
167
+ backoff_factor: Optional[float] = None,
168
+ session: Optional[ClientSession] = None,
169
+ params: Optional[Dict[str, Any]] = None,
170
+ timeout: Optional[float] = None,
171
+ **kwargs,
172
+ ) -> ClientResponse:
173
+ """
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.
182
+
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.
228
+ """
229
+
230
+ session_ = session or await self._ensure_session()
231
+ kwargs_ = kwargs.copy()
232
+
233
+ if timeout is not None:
234
+ kwargs_["timeout"] = ClientTimeout(total=timeout)
235
+
236
+ retries = retries if retries is not None else self.retries
237
+ if retries is None:
238
+ retries = 3
239
+
240
+ backoff_factor = (
241
+ backoff_factor
242
+ if backoff_factor is not None
243
+ else self.backoff_factor
244
+ if self.backoff_factor is not None
245
+ else 0.5
246
+ )
247
+
248
+ attempts = 0
249
+
250
+ while True:
251
+ attempts += 1
252
+
253
+ try:
254
+ response = await session_.request(
255
+ method=str(method),
256
+ url=url,
257
+ headers=headers,
258
+ params=params,
259
+ **kwargs_,
260
+ )
261
+
262
+ if self.raise_for_status:
263
+ response.raise_for_status()
264
+
265
+ return response
266
+
267
+ except ClientResponseError as error:
268
+ if attempts > retries:
269
+ self.raise_custom_exception(error.status, error.message)
270
+
271
+ await asyncio.sleep(backoff_factor * attempts)
272
+
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
+
291
+ if self._session and self._owns_session:
292
+ await self._session.close()
293
+ self._session = None