boto3-assist 0.34.0__py3-none-any.whl → 0.36.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.
- boto3_assist/__init__.py +10 -0
- boto3_assist/connection.py +85 -6
- boto3_assist/connection_pool.py +179 -0
- boto3_assist/dynamodb/__init__.py +9 -0
- boto3_assist/dynamodb/dynamodb.py +215 -168
- boto3_assist/dynamodb/dynamodb_connection.py +2 -0
- boto3_assist/s3/__init__.py +9 -0
- boto3_assist/s3/s3.py +43 -0
- boto3_assist/s3/s3_connection.py +2 -0
- boto3_assist/sqs/__init__.py +12 -0
- boto3_assist/sqs/sqs_connection.py +131 -0
- boto3_assist/sqs/sqs_queue.py +307 -0
- boto3_assist/version.py +1 -1
- {boto3_assist-0.34.0.dist-info → boto3_assist-0.36.0.dist-info}/METADATA +1 -1
- {boto3_assist-0.34.0.dist-info → boto3_assist-0.36.0.dist-info}/RECORD +18 -12
- {boto3_assist-0.34.0.dist-info → boto3_assist-0.36.0.dist-info}/WHEEL +0 -0
- {boto3_assist-0.34.0.dist-info → boto3_assist-0.36.0.dist-info}/licenses/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.34.0.dist-info → boto3_assist-0.36.0.dist-info}/licenses/LICENSE.txt +0 -0
boto3_assist/__init__.py
CHANGED
boto3_assist/connection.py
CHANGED
|
@@ -5,6 +5,7 @@ MIT License. See Project Root for the license information.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from typing import Optional, List
|
|
8
|
+
import warnings
|
|
8
9
|
|
|
9
10
|
from aws_lambda_powertools import Logger
|
|
10
11
|
from botocore.config import Config
|
|
@@ -13,6 +14,7 @@ from boto3_assist.environment_services.environment_variables import (
|
|
|
13
14
|
EnvironmentVariables,
|
|
14
15
|
)
|
|
15
16
|
from boto3_assist.connection_tracker import ConnectionTracker
|
|
17
|
+
from boto3_assist.connection_pool import ConnectionPool
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
logger = Logger()
|
|
@@ -35,6 +37,7 @@ class Connection:
|
|
|
35
37
|
assume_role_chain: Optional[List[str]] = None,
|
|
36
38
|
assume_role_duration_seconds: Optional[int] = 3600,
|
|
37
39
|
config: Optional[Config] = None,
|
|
40
|
+
use_connection_pool: bool = False,
|
|
38
41
|
) -> None:
|
|
39
42
|
self.__aws_profile = aws_profile
|
|
40
43
|
self.__aws_region = aws_region
|
|
@@ -47,6 +50,8 @@ class Connection:
|
|
|
47
50
|
self.__assume_role_chain = assume_role_chain
|
|
48
51
|
self.__assume_role_duration_seconds = assume_role_duration_seconds
|
|
49
52
|
self.__config = config
|
|
53
|
+
self.__use_connection_pool = use_connection_pool
|
|
54
|
+
|
|
50
55
|
if self.__service_name is None:
|
|
51
56
|
raise RuntimeError(
|
|
52
57
|
"Service Name is not available. The service name is required."
|
|
@@ -54,6 +59,18 @@ class Connection:
|
|
|
54
59
|
|
|
55
60
|
self.raise_on_error: bool = True
|
|
56
61
|
|
|
62
|
+
# Issue deprecation warning if not using connection pool
|
|
63
|
+
if not use_connection_pool:
|
|
64
|
+
warnings.warn(
|
|
65
|
+
f"Creating {service_name} Connection without connection pooling. "
|
|
66
|
+
"This creates a new boto3 session on each instantiation, which can impact "
|
|
67
|
+
"Lambda performance. Consider using Connection.from_pool() or "
|
|
68
|
+
"use_connection_pool=True for better performance in Lambda functions. "
|
|
69
|
+
"The default will change to use_connection_pool=True in boto3-assist v2.0.0.",
|
|
70
|
+
DeprecationWarning,
|
|
71
|
+
stacklevel=2,
|
|
72
|
+
)
|
|
73
|
+
|
|
57
74
|
def setup(self, setup_source: Optional[str] = None) -> None:
|
|
58
75
|
"""
|
|
59
76
|
Setup the environment. Automatically called via init.
|
|
@@ -139,12 +156,74 @@ class Connection:
|
|
|
139
156
|
logger.debug("Setting Service Name")
|
|
140
157
|
self.__service_name = value
|
|
141
158
|
|
|
159
|
+
@classmethod
|
|
160
|
+
def from_pool(
|
|
161
|
+
cls,
|
|
162
|
+
service_name: str,
|
|
163
|
+
aws_profile: Optional[str] = None,
|
|
164
|
+
aws_region: Optional[str] = None,
|
|
165
|
+
aws_end_point_url: Optional[str] = None,
|
|
166
|
+
config: Optional[Config] = None,
|
|
167
|
+
**kwargs,
|
|
168
|
+
) -> "Connection":
|
|
169
|
+
"""
|
|
170
|
+
Create connection using connection pool (recommended for Lambda).
|
|
171
|
+
|
|
172
|
+
This is the recommended pattern for Lambda functions as it reuses
|
|
173
|
+
boto3 sessions across invocations in warm containers, reducing
|
|
174
|
+
connection overhead and improving performance.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
service_name: AWS service name (e.g., 's3', 'dynamodb', 'sqs')
|
|
178
|
+
aws_profile: AWS profile name (optional)
|
|
179
|
+
aws_region: AWS region (optional)
|
|
180
|
+
aws_end_point_url: Custom endpoint URL (optional, for moto testing)
|
|
181
|
+
config: Botocore Config object (optional)
|
|
182
|
+
**kwargs: Additional Connection parameters
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Connection instance configured to use connection pool
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
>>> # Recommended pattern for Lambda
|
|
189
|
+
>>> conn = Connection.from_pool(service_name="dynamodb")
|
|
190
|
+
>>> client = conn.session.client
|
|
191
|
+
>>>
|
|
192
|
+
>>> # Subsequent calls reuse the same session
|
|
193
|
+
>>> conn2 = Connection.from_pool(service_name="dynamodb")
|
|
194
|
+
>>> assert conn.session is conn2.session
|
|
195
|
+
"""
|
|
196
|
+
return cls(
|
|
197
|
+
service_name=service_name,
|
|
198
|
+
aws_profile=aws_profile,
|
|
199
|
+
aws_region=aws_region,
|
|
200
|
+
aws_end_point_url=aws_end_point_url,
|
|
201
|
+
config=config,
|
|
202
|
+
use_connection_pool=True,
|
|
203
|
+
**kwargs,
|
|
204
|
+
)
|
|
205
|
+
|
|
142
206
|
@property
|
|
143
207
|
def session(self) -> Boto3SessionManager:
|
|
144
208
|
"""Session"""
|
|
145
|
-
if self.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
209
|
+
if self.__use_connection_pool:
|
|
210
|
+
# Use connection pool for session management
|
|
211
|
+
pool = ConnectionPool.get_instance()
|
|
212
|
+
return pool.get_session(
|
|
213
|
+
service_name=self.service_name,
|
|
214
|
+
aws_profile=self.aws_profile,
|
|
215
|
+
aws_region=self.aws_region,
|
|
216
|
+
aws_endpoint_url=self.end_point_url,
|
|
217
|
+
config=self.__config,
|
|
218
|
+
assume_role_arn=self.__assume_role_arn,
|
|
219
|
+
assume_role_chain=self.__assume_role_chain,
|
|
220
|
+
assume_role_duration_seconds=self.__assume_role_duration_seconds,
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
# Legacy behavior: create session on demand
|
|
224
|
+
if self.__session is None:
|
|
225
|
+
self.setup(setup_source="session init")
|
|
226
|
+
|
|
227
|
+
if self.__session is None:
|
|
228
|
+
raise RuntimeError("Session is not available")
|
|
229
|
+
return self.__session
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
|
|
6
|
+
Connection pooling for boto3 sessions to improve Lambda performance.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Optional
|
|
10
|
+
from aws_lambda_powertools import Logger
|
|
11
|
+
from .boto3session import Boto3SessionManager
|
|
12
|
+
|
|
13
|
+
logger = Logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectionPool:
|
|
17
|
+
"""
|
|
18
|
+
Singleton connection pool for reusing boto3 sessions.
|
|
19
|
+
|
|
20
|
+
Recommended for Lambda functions to minimize connection overhead and
|
|
21
|
+
improve performance in warm containers.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> pool = ConnectionPool.get_instance()
|
|
25
|
+
>>> session = pool.get_session(service_name="dynamodb")
|
|
26
|
+
>>> client = session.client
|
|
27
|
+
|
|
28
|
+
# Subsequent calls reuse the same session
|
|
29
|
+
>>> session2 = pool.get_session(service_name="dynamodb")
|
|
30
|
+
>>> assert session is session2 # Same instance
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_instance: Optional["ConnectionPool"] = None
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
"""Initialize connection pool. Use get_instance() instead."""
|
|
37
|
+
self._connections: Dict[str, Boto3SessionManager] = {}
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def get_instance(cls) -> "ConnectionPool":
|
|
41
|
+
"""
|
|
42
|
+
Get singleton instance of the connection pool.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
ConnectionPool: Singleton instance
|
|
46
|
+
"""
|
|
47
|
+
if cls._instance is None:
|
|
48
|
+
logger.debug("Creating new ConnectionPool instance")
|
|
49
|
+
cls._instance = ConnectionPool()
|
|
50
|
+
return cls._instance
|
|
51
|
+
|
|
52
|
+
def get_session(
|
|
53
|
+
self,
|
|
54
|
+
service_name: str,
|
|
55
|
+
aws_profile: Optional[str] = None,
|
|
56
|
+
aws_region: Optional[str] = None,
|
|
57
|
+
aws_endpoint_url: Optional[str] = None,
|
|
58
|
+
**kwargs,
|
|
59
|
+
) -> Boto3SessionManager:
|
|
60
|
+
"""
|
|
61
|
+
Get or create cached session for service.
|
|
62
|
+
|
|
63
|
+
Sessions are cached based on service_name, profile, region, and endpoint.
|
|
64
|
+
Subsequent calls with the same parameters return the cached session.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
service_name: AWS service name (e.g., 's3', 'dynamodb', 'sqs')
|
|
68
|
+
aws_profile: AWS profile name (optional)
|
|
69
|
+
aws_region: AWS region (optional, defaults to environment/config)
|
|
70
|
+
aws_endpoint_url: Custom endpoint URL (optional, useful for moto testing)
|
|
71
|
+
**kwargs: Additional Boto3SessionManager parameters
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Boto3SessionManager: Cached or newly created session manager
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
>>> pool = ConnectionPool.get_instance()
|
|
78
|
+
>>> # First call creates new session
|
|
79
|
+
>>> s3_session = pool.get_session(service_name="s3")
|
|
80
|
+
>>> # Second call returns cached session
|
|
81
|
+
>>> s3_session2 = pool.get_session(service_name="s3")
|
|
82
|
+
>>> assert s3_session is s3_session2
|
|
83
|
+
"""
|
|
84
|
+
key = self._make_key(service_name, aws_profile, aws_region, aws_endpoint_url)
|
|
85
|
+
|
|
86
|
+
if key not in self._connections:
|
|
87
|
+
logger.debug(
|
|
88
|
+
f"Creating new session for {service_name}",
|
|
89
|
+
extra={
|
|
90
|
+
"service_name": service_name,
|
|
91
|
+
"aws_profile": aws_profile,
|
|
92
|
+
"aws_region": aws_region,
|
|
93
|
+
"cache_key": key,
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
self._connections[key] = Boto3SessionManager(
|
|
97
|
+
service_name=service_name,
|
|
98
|
+
aws_profile=aws_profile,
|
|
99
|
+
aws_region=aws_region,
|
|
100
|
+
aws_endpoint_url=aws_endpoint_url,
|
|
101
|
+
**kwargs,
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
logger.debug(
|
|
105
|
+
f"Reusing cached session for {service_name}",
|
|
106
|
+
extra={
|
|
107
|
+
"service_name": service_name,
|
|
108
|
+
"cache_key": key,
|
|
109
|
+
"total_cached_sessions": len(self._connections),
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return self._connections[key]
|
|
114
|
+
|
|
115
|
+
def reset(self):
|
|
116
|
+
"""
|
|
117
|
+
Reset all connections in the pool.
|
|
118
|
+
|
|
119
|
+
This clears all cached sessions. Useful for testing or when you need
|
|
120
|
+
to force recreation of all connections.
|
|
121
|
+
|
|
122
|
+
Warning:
|
|
123
|
+
This should only be used in testing scenarios. In production Lambda
|
|
124
|
+
functions, connections should persist across invocations.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
>>> pool = ConnectionPool.get_instance()
|
|
128
|
+
>>> pool.get_session(service_name="s3")
|
|
129
|
+
>>> pool.reset() # Clear all cached sessions
|
|
130
|
+
>>> # Next call will create new session
|
|
131
|
+
>>> pool.get_session(service_name="s3")
|
|
132
|
+
"""
|
|
133
|
+
logger.debug(f"Resetting connection pool ({len(self._connections)} sessions)")
|
|
134
|
+
self._connections.clear()
|
|
135
|
+
|
|
136
|
+
def get_stats(self) -> Dict[str, int]:
|
|
137
|
+
"""
|
|
138
|
+
Get statistics about the connection pool.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Dict with pool statistics:
|
|
142
|
+
- total_connections: Number of cached sessions
|
|
143
|
+
- services: Number of unique services
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> pool = ConnectionPool.get_instance()
|
|
147
|
+
>>> pool.get_session(service_name="s3")
|
|
148
|
+
>>> pool.get_session(service_name="dynamodb")
|
|
149
|
+
>>> stats = pool.get_stats()
|
|
150
|
+
>>> print(stats)
|
|
151
|
+
{'total_connections': 2, 'services': 2}
|
|
152
|
+
"""
|
|
153
|
+
services = set()
|
|
154
|
+
for key in self._connections.keys():
|
|
155
|
+
service_name = key.split(":")[0]
|
|
156
|
+
services.add(service_name)
|
|
157
|
+
|
|
158
|
+
return {"total_connections": len(self._connections), "services": len(services)}
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _make_key(
|
|
162
|
+
service_name: str,
|
|
163
|
+
profile: Optional[str],
|
|
164
|
+
region: Optional[str],
|
|
165
|
+
endpoint: Optional[str],
|
|
166
|
+
) -> str:
|
|
167
|
+
"""
|
|
168
|
+
Create cache key from connection parameters.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
service_name: AWS service name
|
|
172
|
+
profile: AWS profile name (or None)
|
|
173
|
+
region: AWS region (or None)
|
|
174
|
+
endpoint: Custom endpoint URL (or None)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
str: Cache key for this combination of parameters
|
|
178
|
+
"""
|
|
179
|
+
return f"{service_name}:{profile}:{region}:{endpoint}"
|