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.
- core_https-2.0.1/PKG-INFO +112 -0
- core_https-2.0.1/README.rst +74 -0
- {core_https-1.1.7 → core_https-2.0.1}/core_https/__init__.py +15 -2
- core_https-2.0.1/core_https/exceptions.py +152 -0
- core_https-2.0.1/core_https/requesters/aiohttp_.py +293 -0
- core_https-2.0.1/core_https/requesters/base.py +391 -0
- {core_https-1.1.7 → core_https-2.0.1}/core_https/requesters/requests_.py +17 -11
- {core_https-1.1.7 → core_https-2.0.1}/core_https/requesters/urllib3_.py +13 -15
- core_https-2.0.1/core_https/tests/__init__.py +0 -0
- core_https-2.0.1/core_https/tests/aiohttp_.py +268 -0
- core_https-2.0.1/core_https/tests/base.py +88 -0
- core_https-2.0.1/core_https/tests/decorators.py +325 -0
- core_https-2.0.1/core_https/tests/requests_.py +215 -0
- core_https-2.0.1/core_https/tests/urllib3_.py +353 -0
- core_https-2.0.1/core_https/utils.py +359 -0
- core_https-2.0.1/core_https.egg-info/PKG-INFO +112 -0
- {core_https-1.1.7 → core_https-2.0.1}/core_https.egg-info/SOURCES.txt +2 -1
- core_https-2.0.1/core_https.egg-info/requires.txt +14 -0
- {core_https-1.1.7 → core_https-2.0.1}/pyproject.toml +16 -17
- core_https-1.1.7/PKG-INFO +0 -76
- core_https-1.1.7/README.md +0 -34
- core_https-1.1.7/core_https/exceptions.py +0 -67
- core_https-1.1.7/core_https/requesters/aiohttp_.py +0 -178
- core_https-1.1.7/core_https/requesters/base.py +0 -161
- core_https-1.1.7/core_https/tests/aiohttp_.py +0 -106
- core_https-1.1.7/core_https/tests/base.py +0 -12
- core_https-1.1.7/core_https/tests/decorators.py +0 -180
- core_https-1.1.7/core_https/tests/requests_.py +0 -75
- core_https-1.1.7/core_https/tests/urllib3_.py +0 -189
- core_https-1.1.7/core_https/utils.py +0 -159
- core_https-1.1.7/core_https.egg-info/PKG-INFO +0 -76
- core_https-1.1.7/core_https.egg-info/requires.txt +0 -24
- {core_https-1.1.7 → core_https-2.0.1}/LICENSE +0 -0
- /core_https-1.1.7/core_https/requesters/__init__.py → /core_https-2.0.1/core_https/py.typed +0 -0
- {core_https-1.1.7/core_https/tests → core_https-2.0.1/core_https/requesters}/__init__.py +0 -0
- {core_https-1.1.7 → core_https-2.0.1}/core_https.egg-info/dependency_links.txt +0 -0
- {core_https-1.1.7 → core_https-2.0.1}/core_https.egg-info/top_level.txt +0 -0
- {core_https-1.1.7 → core_https-2.0.1}/setup.cfg +0 -0
- {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
|
|
6
|
+
from http import HTTPStatus as _HTTPStatus
|
|
5
7
|
|
|
6
8
|
except ImportError:
|
|
7
|
-
from
|
|
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
|