sendgrid-async 2.1.2__tar.gz → 2.2.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.
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/PKG-INFO +51 -2
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/README.md +47 -0
- sendgrid_async-2.2.1/async_sendgrid/__init__.py +13 -0
- sendgrid_async-2.2.1/async_sendgrid/pool.py +155 -0
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/async_sendgrid/sendgrid.py +43 -9
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/async_sendgrid/telemetry.py +4 -2
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/pyproject.toml +5 -2
- sendgrid_async-2.1.2/async_sendgrid/__init__.py +0 -4
- sendgrid_async-2.1.2/async_sendgrid/pool.py +0 -72
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/LICENSE +0 -0
- {sendgrid_async-2.1.2 → sendgrid_async-2.2.1}/async_sendgrid/exception.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sendgrid-async
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: SendGrid using an httpx client
|
|
5
5
|
Home-page: https://github.com/EM51641/async-sendgrid-
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: sendgrid,client,async
|
|
8
8
|
Author: Elyes Mahjoubi
|
|
9
9
|
Author-email: elyesmahjoubi@gmail.com
|
|
10
|
-
Requires-Python: >=3.10
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -17,7 +17,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Requires-Dist: httpx (>=0.24.1,<0.29.0)
|
|
22
|
+
Requires-Dist: httpx-retries (>=0.4.0)
|
|
21
23
|
Requires-Dist: opentelemetry-api (>=1.34.0,<2.0.0)
|
|
22
24
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (>=1.34.0,<2.0.0)
|
|
23
25
|
Requires-Dist: opentelemetry-sdk (>=1.34.0,<2.0.0)
|
|
@@ -93,6 +95,53 @@ sendgrid = SendgridAPI(
|
|
|
93
95
|
)
|
|
94
96
|
```
|
|
95
97
|
|
|
98
|
+
### Retry Configuration
|
|
99
|
+
|
|
100
|
+
By default, requests are automatically retried up to 5 times with exponential backoff on transient failures (429 Too Many Requests, 502, 503, 504, and timeouts).
|
|
101
|
+
|
|
102
|
+
The delay between retries is calculated as:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
delay = backoff_factor * (2 ** attempt) * random(1 - backoff_jitter, 1)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
With the defaults (`backoff_factor=0.5`, `backoff_jitter=1.0`), delays range from 0 to 1s, 0 to 2s, 0 to 4s, etc. Jitter prevents thundering herd problems when multiple clients retry simultaneously.
|
|
109
|
+
|
|
110
|
+
Customize the retry behavior through the connection pool:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from async_sendgrid import SendgridAPI
|
|
114
|
+
from async_sendgrid.pool import ConnectionPool
|
|
115
|
+
|
|
116
|
+
pool = ConnectionPool(
|
|
117
|
+
retry_attempts=3, # Maximum retry attempts (default: 5)
|
|
118
|
+
backoff_factor=1.0, # Backoff multiplier in seconds (default: 0.5)
|
|
119
|
+
backoff_jitter=0.0, # Jitter multiplier, 0 to 1 (default: 1.0)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
sendgrid = SendgridAPI(
|
|
123
|
+
api_key="YOUR_API_KEY",
|
|
124
|
+
pool=pool,
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
To disable retries entirely, set `retry_attempts=0`:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
pool = ConnectionPool(retry_attempts=0)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
You can also override the retry strategy per call:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# Override both retry attempts and backoff for this call
|
|
138
|
+
response = await sendgrid.send(email, retry=10, backoff=1.0)
|
|
139
|
+
|
|
140
|
+
# Override just one (the other keeps the pool default)
|
|
141
|
+
response = await sendgrid.send(email, retry=3)
|
|
142
|
+
response = await sendgrid.send(email, backoff=0.1)
|
|
143
|
+
```
|
|
144
|
+
|
|
96
145
|
### Send emails on behalf of another user
|
|
97
146
|
|
|
98
147
|
Send emails on behalf of subusers:
|
|
@@ -66,6 +66,53 @@ sendgrid = SendgridAPI(
|
|
|
66
66
|
)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
### Retry Configuration
|
|
70
|
+
|
|
71
|
+
By default, requests are automatically retried up to 5 times with exponential backoff on transient failures (429 Too Many Requests, 502, 503, 504, and timeouts).
|
|
72
|
+
|
|
73
|
+
The delay between retries is calculated as:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
delay = backoff_factor * (2 ** attempt) * random(1 - backoff_jitter, 1)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
With the defaults (`backoff_factor=0.5`, `backoff_jitter=1.0`), delays range from 0 to 1s, 0 to 2s, 0 to 4s, etc. Jitter prevents thundering herd problems when multiple clients retry simultaneously.
|
|
80
|
+
|
|
81
|
+
Customize the retry behavior through the connection pool:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from async_sendgrid import SendgridAPI
|
|
85
|
+
from async_sendgrid.pool import ConnectionPool
|
|
86
|
+
|
|
87
|
+
pool = ConnectionPool(
|
|
88
|
+
retry_attempts=3, # Maximum retry attempts (default: 5)
|
|
89
|
+
backoff_factor=1.0, # Backoff multiplier in seconds (default: 0.5)
|
|
90
|
+
backoff_jitter=0.0, # Jitter multiplier, 0 to 1 (default: 1.0)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
sendgrid = SendgridAPI(
|
|
94
|
+
api_key="YOUR_API_KEY",
|
|
95
|
+
pool=pool,
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To disable retries entirely, set `retry_attempts=0`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
pool = ConnectionPool(retry_attempts=0)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
You can also override the retry strategy per call:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# Override both retry attempts and backoff for this call
|
|
109
|
+
response = await sendgrid.send(email, retry=10, backoff=1.0)
|
|
110
|
+
|
|
111
|
+
# Override just one (the other keeps the pool default)
|
|
112
|
+
response = await sendgrid.send(email, retry=3)
|
|
113
|
+
response = await sendgrid.send(email, backoff=0.1)
|
|
114
|
+
```
|
|
115
|
+
|
|
69
116
|
### Send emails on behalf of another user
|
|
70
117
|
|
|
71
118
|
Send emails on behalf of subusers:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from .sendgrid import SendgridAPI # noqa
|
|
4
|
+
from .pool import ConnectionPool # noqa
|
|
5
|
+
|
|
6
|
+
__version__ = "0.0.0-dev"
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = version("sendgrid-async")
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
__all__ = ["SendgridAPI", "ConnectionPool"]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connection pool manager for SendGrid API requests.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from httpx import AsyncClient, AsyncHTTPTransport, Limits # type: ignore
|
|
10
|
+
from httpx_retries import Retry, RetryTransport # type: ignore
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectionPool:
|
|
17
|
+
"""
|
|
18
|
+
A connection pool manager for SendGrid API requests.
|
|
19
|
+
This is a private class and is not meant to be used directly.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
max_connections: int = 10,
|
|
25
|
+
max_keepalive_connections: int = 5,
|
|
26
|
+
keepalive_expiry: float = 5.0,
|
|
27
|
+
retry_attempts: int = 5,
|
|
28
|
+
backoff_factor: float = 0.5,
|
|
29
|
+
backoff_jitter: float = 1.0,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Initialize the connection pool.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
max_connections (int, optional):
|
|
36
|
+
Maximum number of concurrent connections.
|
|
37
|
+
Defaults to 10.
|
|
38
|
+
max_keepalive_connections (int, optional):
|
|
39
|
+
Maximum number of keep-alive connections.
|
|
40
|
+
Defaults to 5.
|
|
41
|
+
keepalive_expiry (float, optional):
|
|
42
|
+
Keep-alive connection expiry time in
|
|
43
|
+
seconds. Defaults to 5.0.
|
|
44
|
+
retry_attempts (int, optional):
|
|
45
|
+
Maximum number of retry attempts for
|
|
46
|
+
transient failures (429, 5xx, timeouts).
|
|
47
|
+
Defaults to 5.
|
|
48
|
+
backoff_factor (float, optional):
|
|
49
|
+
Multiplier for exponential backoff between
|
|
50
|
+
retries. Defaults to 0.5.
|
|
51
|
+
backoff_jitter (float, optional):
|
|
52
|
+
Jitter multiplier applied to the backoff time,
|
|
53
|
+
between 0 and 1. Defaults to 1.0.
|
|
54
|
+
"""
|
|
55
|
+
self._validate_retry_attempts(retry_attempts)
|
|
56
|
+
self._validate_backoff_factor(backoff_factor)
|
|
57
|
+
self._validate_backoff_jitter(backoff_jitter)
|
|
58
|
+
|
|
59
|
+
self._limits = Limits(
|
|
60
|
+
max_connections=max_connections,
|
|
61
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
62
|
+
keepalive_expiry=keepalive_expiry,
|
|
63
|
+
)
|
|
64
|
+
self._retry = Retry(
|
|
65
|
+
total=retry_attempts,
|
|
66
|
+
backoff_factor=backoff_factor,
|
|
67
|
+
backoff_jitter=backoff_jitter,
|
|
68
|
+
allowed_methods=["POST"],
|
|
69
|
+
)
|
|
70
|
+
self._client: AsyncClient | None = None
|
|
71
|
+
|
|
72
|
+
def _create_client(
|
|
73
|
+
self,
|
|
74
|
+
headers: dict[str, Any],
|
|
75
|
+
retry: int | None = None,
|
|
76
|
+
backoff: float | None = None,
|
|
77
|
+
) -> AsyncClient:
|
|
78
|
+
"""
|
|
79
|
+
Get or create an HTTP client with the configured connection limits.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
headers (dict[str, Any]): The headers to use for the client.
|
|
83
|
+
retry (int, optional): Override the number of retry attempts.
|
|
84
|
+
Uses the pool default when not set.
|
|
85
|
+
backoff (float, optional): Override the backoff factor.
|
|
86
|
+
Uses the pool default when not set.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
AsyncClient: The configured HTTP client.
|
|
90
|
+
"""
|
|
91
|
+
if retry is not None:
|
|
92
|
+
self._validate_retry_attempts(retry)
|
|
93
|
+
if backoff is not None:
|
|
94
|
+
self._validate_backoff_factor(backoff)
|
|
95
|
+
|
|
96
|
+
retry_strategy = self._retry
|
|
97
|
+
if retry is not None or backoff is not None:
|
|
98
|
+
retry_strategy = Retry(
|
|
99
|
+
total=retry if retry is not None else self._retry.total,
|
|
100
|
+
backoff_factor=(
|
|
101
|
+
backoff
|
|
102
|
+
if backoff is not None
|
|
103
|
+
else self._retry.backoff_factor
|
|
104
|
+
),
|
|
105
|
+
backoff_jitter=self._retry.backoff_jitter,
|
|
106
|
+
allowed_methods=["POST"],
|
|
107
|
+
)
|
|
108
|
+
transport = RetryTransport(
|
|
109
|
+
transport=AsyncHTTPTransport(limits=self._limits),
|
|
110
|
+
retry=retry_strategy,
|
|
111
|
+
)
|
|
112
|
+
return AsyncClient(
|
|
113
|
+
headers=headers,
|
|
114
|
+
timeout=5.0,
|
|
115
|
+
transport=transport,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _validate_retry_attempts(retry_attempts: int) -> None:
|
|
120
|
+
if not isinstance(retry_attempts, int) or retry_attempts < 0:
|
|
121
|
+
raise ValueError("retry_attempts must be a positive integer")
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _validate_backoff_factor(backoff_factor: float) -> None:
|
|
125
|
+
if not isinstance(backoff_factor, (int, float)) or backoff_factor < 0:
|
|
126
|
+
raise ValueError("backoff_factor must be a positive number")
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def _validate_backoff_jitter(backoff_jitter: float) -> None:
|
|
130
|
+
if not isinstance(backoff_jitter, (int, float)) or backoff_jitter < 0:
|
|
131
|
+
raise ValueError("backoff_jitter must be a positive number")
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def limits(self) -> Limits:
|
|
135
|
+
"""
|
|
136
|
+
Get the current connection limits.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Limits: The current connection limits configuration.
|
|
140
|
+
"""
|
|
141
|
+
return self._limits
|
|
142
|
+
|
|
143
|
+
def __repr__(self) -> str:
|
|
144
|
+
return (
|
|
145
|
+
f"ConnectionPool("
|
|
146
|
+
f"max_connections={self._limits.max_connections}, "
|
|
147
|
+
f"max_keepalive_connections={self._limits.max_keepalive_connections}, " # noqa: E501
|
|
148
|
+
f"keepalive_expiry={self._limits.keepalive_expiry}, "
|
|
149
|
+
f"retry_attempts={self._retry.total}, "
|
|
150
|
+
f"backoff_factor={self._retry.backoff_factor}, "
|
|
151
|
+
f"backoff_jitter={self._retry.backoff_jitter})"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def __str__(self) -> str:
|
|
155
|
+
return repr(self)
|
|
@@ -50,7 +50,12 @@ class BaseSendgridAPI(ABC):
|
|
|
50
50
|
"""Not implemented"""
|
|
51
51
|
|
|
52
52
|
@abstractmethod
|
|
53
|
-
async def send(
|
|
53
|
+
async def send(
|
|
54
|
+
self,
|
|
55
|
+
message: Mail,
|
|
56
|
+
retry: Optional[int] = None,
|
|
57
|
+
backoff: Optional[float] = None,
|
|
58
|
+
) -> Response:
|
|
54
59
|
"""Not implemented"""
|
|
55
60
|
|
|
56
61
|
|
|
@@ -116,7 +121,12 @@ class SendgridAPI(BaseSendgridAPI):
|
|
|
116
121
|
return self._session
|
|
117
122
|
|
|
118
123
|
@trace_client()
|
|
119
|
-
async def send(
|
|
124
|
+
async def send(
|
|
125
|
+
self,
|
|
126
|
+
email: Mail,
|
|
127
|
+
retry: Optional[int] = None,
|
|
128
|
+
backoff: Optional[float] = None,
|
|
129
|
+
) -> Response:
|
|
120
130
|
"""
|
|
121
131
|
Make a Twilio SendGrid v3 API request with the request body generated
|
|
122
132
|
by the Mail object
|
|
@@ -124,16 +134,36 @@ class SendgridAPI(BaseSendgridAPI):
|
|
|
124
134
|
Args:
|
|
125
135
|
email: The Twilio SendGrid v3 API request body generated
|
|
126
136
|
by the Mail object or dict
|
|
137
|
+
retry: Override the number of retry attempts for this
|
|
138
|
+
request. Uses the client default when not set.
|
|
139
|
+
backoff: Override the backoff factor for this request.
|
|
140
|
+
Uses the client default when not set.
|
|
127
141
|
|
|
128
142
|
Returns:
|
|
129
143
|
The Twilio SendGrid v3 API response
|
|
130
144
|
"""
|
|
131
145
|
self._check_session_closed()
|
|
132
146
|
json_message = email.get()
|
|
133
|
-
|
|
134
|
-
|
|
147
|
+
|
|
148
|
+
if retry is not None or backoff is not None:
|
|
149
|
+
async with self._build_client(retry, backoff) as session:
|
|
150
|
+
return await self._send(session, json_message)
|
|
151
|
+
|
|
152
|
+
return await self._send(self._session, json_message)
|
|
153
|
+
|
|
154
|
+
async def _send(
|
|
155
|
+
self, client: AsyncClient, json_message: dict[str, Any]
|
|
156
|
+
) -> Response:
|
|
157
|
+
return await client.post(url=self._endpoint, json=json_message)
|
|
158
|
+
|
|
159
|
+
def _build_client(
|
|
160
|
+
self,
|
|
161
|
+
retry: Optional[int] = None,
|
|
162
|
+
backoff: Optional[float] = None,
|
|
163
|
+
) -> AsyncClient:
|
|
164
|
+
return self._pool._create_client(
|
|
165
|
+
self._headers, retry=retry, backoff=backoff
|
|
135
166
|
)
|
|
136
|
-
return response
|
|
137
167
|
|
|
138
168
|
def _check_session_closed(self):
|
|
139
169
|
"""
|
|
@@ -146,8 +176,12 @@ class SendgridAPI(BaseSendgridAPI):
|
|
|
146
176
|
logger.error("Session not initialized")
|
|
147
177
|
raise SessionClosedException("Session not initialized")
|
|
148
178
|
|
|
149
|
-
def __str__(self) -> str:
|
|
150
|
-
return f"SendGrid API Client\n • Endpoint: {self._endpoint}\n"
|
|
151
|
-
|
|
152
179
|
def __repr__(self) -> str:
|
|
153
|
-
return
|
|
180
|
+
return (
|
|
181
|
+
f"SendgridAPI("
|
|
182
|
+
f"endpoint={self._endpoint!r}, "
|
|
183
|
+
f"pool={self._pool!r})"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def __str__(self) -> str:
|
|
187
|
+
return repr(self)
|
|
@@ -75,11 +75,13 @@ def trace_client():
|
|
|
75
75
|
return func
|
|
76
76
|
|
|
77
77
|
@wraps(func)
|
|
78
|
-
async def wrapper(
|
|
78
|
+
async def wrapper(
|
|
79
|
+
self: SendgridAPI, email: Mail, **kwargs: Any
|
|
80
|
+
) -> Response:
|
|
79
81
|
span = create_span(_SPAN_NAME)
|
|
80
82
|
try:
|
|
81
83
|
set_sendgrid_metrics(span, email)
|
|
82
|
-
response: Response = await func(self, email)
|
|
84
|
+
response: Response = await func(self, email, **kwargs)
|
|
83
85
|
set_http_metrics(span, response)
|
|
84
86
|
return response
|
|
85
87
|
except Exception as exc:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "sendgrid-async"
|
|
3
|
-
version = "2.1
|
|
3
|
+
version = "2.2.1"
|
|
4
4
|
description = "SendGrid using an httpx client"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["Elyes Mahjoubi <elyesmahjoubi@gmail.com>"]
|
|
@@ -14,6 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Programming Language :: Python :: 3.11",
|
|
15
15
|
"Programming Language :: Python :: 3.12",
|
|
16
16
|
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
17
18
|
]
|
|
18
19
|
|
|
19
20
|
packages = [
|
|
@@ -26,9 +27,10 @@ include = [
|
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
[tool.poetry.dependencies]
|
|
29
|
-
python = "
|
|
30
|
+
python = ">=3.10"
|
|
30
31
|
sendgrid = "^6.7.0"
|
|
31
32
|
httpx = ">=0.24.1,<0.29.0"
|
|
33
|
+
httpx-retries = ">=0.4.0"
|
|
32
34
|
opentelemetry-api = "^1.34.0"
|
|
33
35
|
opentelemetry-sdk = "^1.34.0"
|
|
34
36
|
opentelemetry-exporter-otlp-proto-grpc = "^1.34.0"
|
|
@@ -37,6 +39,7 @@ opentelemetry-exporter-otlp-proto-grpc = "^1.34.0"
|
|
|
37
39
|
pytest = "^8.4.0"
|
|
38
40
|
pytest-asyncio = "^1.0.0"
|
|
39
41
|
pytest-cov = "^6.1.1"
|
|
42
|
+
pytest-httpserver = "^1.1.0"
|
|
40
43
|
mypy = "^1.0.0"
|
|
41
44
|
flake8 = "^7.2.0"
|
|
42
45
|
black = "^25.1.0"
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Connection pool manager for SendGrid API requests.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
8
|
-
|
|
9
|
-
from httpx import AsyncClient, Limits # type: ignore
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ConnectionPool:
|
|
16
|
-
"""
|
|
17
|
-
A connection pool manager for SendGrid API requests.
|
|
18
|
-
This is a private class and is not meant to be used directly.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
max_connections: int = 10,
|
|
24
|
-
max_keepalive_connections: int = 5,
|
|
25
|
-
keepalive_expiry: float = 5.0,
|
|
26
|
-
) -> None:
|
|
27
|
-
"""
|
|
28
|
-
Initialize the connection pool.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
max_connections (int, optional):
|
|
32
|
-
Maximum number of concurrent connections. Defaults to 10.
|
|
33
|
-
max_keepalive_connections (int, optional):
|
|
34
|
-
Maximum number of keep-alive connections. Defaults to 5.
|
|
35
|
-
keepalive_expiry (float, optional):
|
|
36
|
-
Keep-alive connection expiry time in seconds. Defaults to 5.0.
|
|
37
|
-
"""
|
|
38
|
-
self._limits = Limits(
|
|
39
|
-
max_connections=max_connections,
|
|
40
|
-
max_keepalive_connections=max_keepalive_connections,
|
|
41
|
-
keepalive_expiry=keepalive_expiry,
|
|
42
|
-
)
|
|
43
|
-
self._client: AsyncClient | None = None
|
|
44
|
-
|
|
45
|
-
def _create_client(self, headers: dict[str, Any]) -> AsyncClient:
|
|
46
|
-
"""
|
|
47
|
-
Get or create an HTTP client with the configured connection limits.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
headers (dict[str, Any]): The headers to use for the client.
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
AsyncClient: The configured HTTP client.
|
|
54
|
-
"""
|
|
55
|
-
return AsyncClient(headers=headers, limits=self._limits, timeout=5.0)
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def limits(self) -> Limits:
|
|
59
|
-
"""
|
|
60
|
-
Get the current connection limits.
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
Limits: The current connection limits configuration.
|
|
64
|
-
"""
|
|
65
|
-
return self._limits
|
|
66
|
-
|
|
67
|
-
def __str__(self) -> str:
|
|
68
|
-
return (
|
|
69
|
-
f"ConnectionPool(max_connections={self._limits.max_connections}, "
|
|
70
|
-
f"max_keepalive={self._limits.max_keepalive_connections}, "
|
|
71
|
-
f"keepalive_expiry={self._limits.keepalive_expiry})"
|
|
72
|
-
)
|
|
File without changes
|
|
File without changes
|