aws-python-helper 0.33.0__tar.gz → 0.35.0__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.
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/PKG-INFO +5 -6
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/README.md +4 -4
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/__init__.py +3 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/dispatcher.py +12 -0
- aws_python_helper-0.35.0/aws_python_helper/context/__init__.py +4 -0
- aws_python_helper-0.35.0/aws_python_helper/context/state_validator.py +54 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/base.py +2 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sns/publisher.py +22 -6
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sqs/consumer_base.py +6 -13
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/PKG-INFO +5 -6
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/SOURCES.txt +1 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/requires.txt +0 -1
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/pyproject.toml +1 -2
- aws_python_helper-0.33.0/aws_python_helper/context/__init__.py +0 -3
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/auth_middleware.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/auth_validators.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/base.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/exceptions.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/context/session.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/database_proxy.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/external_database_proxy.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/mongo_manager.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/fargate/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/fargate/executor.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/fargate/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/fargate/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/fargate/task_base.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/repository/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/repository/base.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sns/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sqs/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sqs/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sqs/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/utils/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/utils/json_encoder.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/utils/response.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/utils/serializer.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/top_level.txt +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-python-helper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.0
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,6 @@ Requires-Python: >=3.9
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
Requires-Dist: motor==3.3.2
|
|
17
17
|
Requires-Dist: pymongo==4.6.1
|
|
18
|
-
Requires-Dist: bcrypt>=4.0.0
|
|
19
18
|
|
|
20
19
|
# AWS Python Framework
|
|
21
20
|
|
|
@@ -657,7 +656,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
657
656
|
|-------------|-------------------------|
|
|
658
657
|
| **API Gateway** | `constitution-state` header → `session.state` (when `AUTHORIZATION` includes `state`); auth middleware → `session.user` (when includes `user`). Returns `400` if required header is missing |
|
|
659
658
|
| **Standalone Lambda** | `session` dict in the event payload — **required** (must include `state`), raises `ValueError` if missing |
|
|
660
|
-
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (JSON)
|
|
659
|
+
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
661
660
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
662
661
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
663
662
|
|
|
@@ -665,7 +664,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
665
664
|
|
|
666
665
|
| Downstream service | Propagation mechanism |
|
|
667
666
|
|--------------------|-----------------------|
|
|
668
|
-
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (JSON
|
|
667
|
+
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
|
|
669
668
|
| **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
|
|
670
669
|
|
|
671
670
|
This means that an API call with `constitution-state: connecticut` will automatically carry the full session (state + user) through SNS → SQS → Fargate without any code changes in your consumers or tasks.
|
|
@@ -1131,7 +1130,7 @@ environment_variables = {
|
|
|
1131
1130
|
By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
|
|
1132
1131
|
|
|
1133
1132
|
**Constitution-state handling in SQS:**
|
|
1134
|
-
- **Single mode**: the framework extracts
|
|
1133
|
+
- **Single mode**: the framework extracts the session from each record automatically (from SNS `MessageAttributes`, Base64-decoded) and sets it in context before calling `process_record()`. You do not need to extract it yourself.
|
|
1135
1134
|
- **Batch mode**: the framework groups the incoming records by `constitution-state` and calls `process_batch()` once per group, with the correct state in context for each group. This ensures that state-scoped repositories resolve to the right database even when a batch contains records from different states.
|
|
1136
1135
|
|
|
1137
1136
|
```python
|
|
@@ -1181,7 +1180,7 @@ class OrderConsumer(SQSConsumer):
|
|
|
1181
1180
|
|
|
1182
1181
|
### SNS Publisher - Batch Publishing
|
|
1183
1182
|
|
|
1184
|
-
The `SNSPublisher` automatically injects the current
|
|
1183
|
+
The `SNSPublisher` automatically injects the current session as a Base64-encoded `MessageAttribute` on every published message. Base64 encoding is used to avoid SNS filter policy issues with raw JSON string values in attributes. SQS consumers built with this framework will decode it automatically, ensuring the session flows end-to-end through the SNS → SQS chain without any manual code.
|
|
1185
1184
|
|
|
1186
1185
|
```python
|
|
1187
1186
|
topic = TitleIndexedTopic()
|
|
@@ -638,7 +638,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
638
638
|
|-------------|-------------------------|
|
|
639
639
|
| **API Gateway** | `constitution-state` header → `session.state` (when `AUTHORIZATION` includes `state`); auth middleware → `session.user` (when includes `user`). Returns `400` if required header is missing |
|
|
640
640
|
| **Standalone Lambda** | `session` dict in the event payload — **required** (must include `state`), raises `ValueError` if missing |
|
|
641
|
-
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (JSON)
|
|
641
|
+
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
642
642
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
643
643
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
644
644
|
|
|
@@ -646,7 +646,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
646
646
|
|
|
647
647
|
| Downstream service | Propagation mechanism |
|
|
648
648
|
|--------------------|-----------------------|
|
|
649
|
-
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (JSON
|
|
649
|
+
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
|
|
650
650
|
| **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
|
|
651
651
|
|
|
652
652
|
This means that an API call with `constitution-state: connecticut` will automatically carry the full session (state + user) through SNS → SQS → Fargate without any code changes in your consumers or tasks.
|
|
@@ -1112,7 +1112,7 @@ environment_variables = {
|
|
|
1112
1112
|
By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
|
|
1113
1113
|
|
|
1114
1114
|
**Constitution-state handling in SQS:**
|
|
1115
|
-
- **Single mode**: the framework extracts
|
|
1115
|
+
- **Single mode**: the framework extracts the session from each record automatically (from SNS `MessageAttributes`, Base64-decoded) and sets it in context before calling `process_record()`. You do not need to extract it yourself.
|
|
1116
1116
|
- **Batch mode**: the framework groups the incoming records by `constitution-state` and calls `process_batch()` once per group, with the correct state in context for each group. This ensures that state-scoped repositories resolve to the right database even when a batch contains records from different states.
|
|
1117
1117
|
|
|
1118
1118
|
```python
|
|
@@ -1162,7 +1162,7 @@ class OrderConsumer(SQSConsumer):
|
|
|
1162
1162
|
|
|
1163
1163
|
### SNS Publisher - Batch Publishing
|
|
1164
1164
|
|
|
1165
|
-
The `SNSPublisher` automatically injects the current
|
|
1165
|
+
The `SNSPublisher` automatically injects the current session as a Base64-encoded `MessageAttribute` on every published message. Base64 encoding is used to avoid SNS filter policy issues with raw JSON string values in attributes. SQS consumers built with this framework will decode it automatically, ensuring the session flows end-to-end through the SNS → SQS chain without any manual code.
|
|
1166
1166
|
|
|
1167
1167
|
```python
|
|
1168
1168
|
topic = TitleIndexedTopic()
|
|
@@ -29,6 +29,7 @@ from .repository.base import Repository
|
|
|
29
29
|
|
|
30
30
|
# Context
|
|
31
31
|
from .context.session import Session, get_session, set_session
|
|
32
|
+
from .context.state_validator import StateValidator, InvalidStateError
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
__all__ = [
|
|
@@ -51,5 +52,7 @@ __all__ = [
|
|
|
51
52
|
'Session',
|
|
52
53
|
'get_session',
|
|
53
54
|
'set_session',
|
|
55
|
+
'StateValidator',
|
|
56
|
+
'InvalidStateError',
|
|
54
57
|
]
|
|
55
58
|
|
|
@@ -13,6 +13,7 @@ from .exceptions import UnauthorizedError, ForbiddenError, AuthenticationError
|
|
|
13
13
|
from .auth_middleware import AuthMiddleware
|
|
14
14
|
from .auth_validators import TokenValidator
|
|
15
15
|
from ..context.session import get_session
|
|
16
|
+
from ..context.state_validator import StateValidator, InvalidStateError
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
@@ -96,6 +97,17 @@ class Dispatcher:
|
|
|
96
97
|
},
|
|
97
98
|
'headers': {}
|
|
98
99
|
}
|
|
100
|
+
try:
|
|
101
|
+
await StateValidator.validate(state)
|
|
102
|
+
except InvalidStateError as e:
|
|
103
|
+
return {
|
|
104
|
+
'code': 403,
|
|
105
|
+
'body': {
|
|
106
|
+
'error': 'Forbidden',
|
|
107
|
+
'message': str(e)
|
|
108
|
+
},
|
|
109
|
+
'headers': {}
|
|
110
|
+
}
|
|
99
111
|
session = get_session()
|
|
100
112
|
session.state = state
|
|
101
113
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Dict, Tuple
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidStateError(Exception):
|
|
6
|
+
"""Raised when a state is not found or not active in the database."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StateValidator:
|
|
11
|
+
"""
|
|
12
|
+
Validates that a state exists and is active in the core.states collection.
|
|
13
|
+
|
|
14
|
+
Uses an in-memory cache with a 5-minute TTL to avoid querying the database
|
|
15
|
+
on every request. Only valid (active) states are cached — invalid states
|
|
16
|
+
always hit the database so that recently activated states are picked up
|
|
17
|
+
immediately.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_cache: Dict[str, float] = {} # state -> timestamp when cached
|
|
21
|
+
_CACHE_TTL: int = 300 # 5 minutes
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
async def validate(cls, state: str):
|
|
25
|
+
"""
|
|
26
|
+
Validate that state exists in core.states with is_active=True.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
state: The state name to validate
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
InvalidStateError: If the state doesn't exist or is not active
|
|
33
|
+
"""
|
|
34
|
+
now = time.time()
|
|
35
|
+
|
|
36
|
+
if state in cls._cache:
|
|
37
|
+
if now - cls._cache[state] < cls._CACHE_TTL:
|
|
38
|
+
return
|
|
39
|
+
del cls._cache[state]
|
|
40
|
+
|
|
41
|
+
from ..database.mongo_manager import MongoManager
|
|
42
|
+
db = MongoManager.get_database('core')
|
|
43
|
+
doc = await db.states.find_one({"name": state, "is_active": True})
|
|
44
|
+
|
|
45
|
+
if doc:
|
|
46
|
+
cls._cache[state] = now
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
raise InvalidStateError(f"State '{state}' is not valid or not active")
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def clear_cache(cls):
|
|
53
|
+
"""Clear the state validation cache."""
|
|
54
|
+
cls._cache.clear()
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/base.py
RENAMED
|
@@ -7,6 +7,7 @@ from typing import Dict, Any
|
|
|
7
7
|
import logging
|
|
8
8
|
|
|
9
9
|
from ..context.session import Session, set_session, get_session
|
|
10
|
+
from ..context.state_validator import StateValidator
|
|
10
11
|
from ..database.mongo_manager import MongoManager
|
|
11
12
|
from ..database.database_proxy import DatabaseProxy
|
|
12
13
|
from ..database.external_mongo_manager import ExternalMongoManager
|
|
@@ -169,6 +170,7 @@ class Lambda(ABC):
|
|
|
169
170
|
if not session_data or not session_data.get('state'):
|
|
170
171
|
raise ValueError("'session' with 'state' is required in the event")
|
|
171
172
|
session = Session.from_dict(session_data)
|
|
173
|
+
await StateValidator.validate(session.state)
|
|
172
174
|
set_session(session)
|
|
173
175
|
|
|
174
176
|
# Step 1: Validate
|
|
@@ -7,6 +7,7 @@ without complex validations, only basic JSON serialization.
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
+
import base64
|
|
10
11
|
from typing import Dict, Any, List, Optional, Union
|
|
11
12
|
import boto3
|
|
12
13
|
from abc import ABC
|
|
@@ -185,14 +186,27 @@ class SNSPublisher(ABC):
|
|
|
185
186
|
|
|
186
187
|
params['PublishBatchRequestEntries'] = message_to_publish
|
|
187
188
|
|
|
188
|
-
|
|
189
|
+
try:
|
|
190
|
+
result = self.sns_client.publish_batch(**params)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
self.logger.error(f"Error calling publish_batch to {self.topic_arn}: {e}")
|
|
193
|
+
raise
|
|
189
194
|
|
|
190
|
-
for message in result
|
|
195
|
+
for message in result.get('Successful', []):
|
|
191
196
|
message_ids_success.append(message['MessageId'])
|
|
192
197
|
|
|
193
|
-
for message in result
|
|
198
|
+
for message in result.get('Failed', []):
|
|
199
|
+
self.logger.error(
|
|
200
|
+
f"Failed to publish message {message.get('Id')} to {self.topic_arn}: "
|
|
201
|
+
f"Code={message.get('Code')}, Message={message.get('Message')}"
|
|
202
|
+
)
|
|
194
203
|
message_ids_failed.append(None)
|
|
195
|
-
|
|
204
|
+
|
|
205
|
+
self.logger.info(
|
|
206
|
+
f"Batch publish to {self.topic_arn}: "
|
|
207
|
+
f"{len(message_ids_success)} successful, {len(message_ids_failed)} failed"
|
|
208
|
+
)
|
|
209
|
+
|
|
196
210
|
return message_ids_success, message_ids_failed
|
|
197
211
|
|
|
198
212
|
def _serialize_message(self, content: Dict[str, Any]) -> str:
|
|
@@ -258,14 +272,16 @@ class SNSPublisher(ABC):
|
|
|
258
272
|
attributes = message.get("attributes")
|
|
259
273
|
subject = message.get("subject")
|
|
260
274
|
|
|
261
|
-
# Auto-inject session as a message attribute
|
|
275
|
+
# Auto-inject session as a message attribute (Base64-encoded to avoid
|
|
276
|
+
# SNS filter policy issues with JSON string values)
|
|
262
277
|
session = get_session()
|
|
263
278
|
session_dict = session.to_dict()
|
|
264
279
|
if session_dict:
|
|
265
280
|
if not attributes:
|
|
266
281
|
attributes = {}
|
|
267
282
|
if 'session' not in attributes:
|
|
268
|
-
|
|
283
|
+
session_json = self._serialize_message(session_dict)
|
|
284
|
+
attributes['session'] = base64.b64encode(session_json.encode('utf-8')).decode('utf-8')
|
|
269
285
|
|
|
270
286
|
params = {
|
|
271
287
|
'Id': str(index),
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/sqs/consumer_base.py
RENAMED
|
@@ -3,6 +3,7 @@ SQS Consumer Base - Base class for all SQS consumers
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import base64
|
|
6
7
|
from abc import ABC, abstractmethod
|
|
7
8
|
from typing import Dict, Any, List, Optional
|
|
8
9
|
import logging
|
|
@@ -277,9 +278,8 @@ class SQSConsumer(ABC):
|
|
|
277
278
|
"""
|
|
278
279
|
Extract Session from an SQS record without setting it in the context.
|
|
279
280
|
|
|
280
|
-
Looks
|
|
281
|
-
by SNSPublisher)
|
|
282
|
-
legacy 'constitution_state' field.
|
|
281
|
+
Looks in SNS MessageAttributes for 'session' (Base64-encoded, injected
|
|
282
|
+
automatically by SNSPublisher).
|
|
283
283
|
|
|
284
284
|
Args:
|
|
285
285
|
record: SQS record
|
|
@@ -287,7 +287,6 @@ class SQSConsumer(ABC):
|
|
|
287
287
|
Returns:
|
|
288
288
|
Session instance if found, None otherwise
|
|
289
289
|
"""
|
|
290
|
-
# Primary: SNS MessageAttributes (auto-injected by SNSPublisher)
|
|
291
290
|
raw_body = record.get('body', '{}')
|
|
292
291
|
if isinstance(raw_body, str):
|
|
293
292
|
try:
|
|
@@ -298,20 +297,14 @@ class SQSConsumer(ABC):
|
|
|
298
297
|
raw_body_json = raw_body or {}
|
|
299
298
|
|
|
300
299
|
if isinstance(raw_body_json, dict) and 'MessageAttributes' in raw_body_json:
|
|
301
|
-
# Try full session attribute first
|
|
302
300
|
session_attr = raw_body_json['MessageAttributes'].get('session', {})
|
|
303
301
|
session_str = session_attr.get('Value')
|
|
304
302
|
if session_str:
|
|
305
303
|
try:
|
|
306
|
-
|
|
307
|
-
|
|
304
|
+
decoded = base64.b64decode(session_str).decode('utf-8')
|
|
305
|
+
return Session.from_dict(json.loads(decoded))
|
|
306
|
+
except (json.JSONDecodeError, TypeError, Exception) as e:
|
|
308
307
|
self.logger.warning(f"Could not parse session attribute: {session_str}")
|
|
309
|
-
|
|
310
|
-
# Fallback: parsed body content (for direct SQS messages without SNS wrapping)
|
|
311
|
-
body = self.extract_content_message(record)
|
|
312
|
-
state = body.get("constitution_state") or body.get("content", {}).get("constitution_state")
|
|
313
|
-
if state:
|
|
314
|
-
return Session(state=state)
|
|
315
308
|
return None
|
|
316
309
|
|
|
317
310
|
def _extract_and_set_session(self, record: Dict[str, Any]) -> Optional[Session]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-python-helper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.0
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,6 @@ Requires-Python: >=3.9
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
Requires-Dist: motor==3.3.2
|
|
17
17
|
Requires-Dist: pymongo==4.6.1
|
|
18
|
-
Requires-Dist: bcrypt>=4.0.0
|
|
19
18
|
|
|
20
19
|
# AWS Python Framework
|
|
21
20
|
|
|
@@ -657,7 +656,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
657
656
|
|-------------|-------------------------|
|
|
658
657
|
| **API Gateway** | `constitution-state` header → `session.state` (when `AUTHORIZATION` includes `state`); auth middleware → `session.user` (when includes `user`). Returns `400` if required header is missing |
|
|
659
658
|
| **Standalone Lambda** | `session` dict in the event payload — **required** (must include `state`), raises `ValueError` if missing |
|
|
660
|
-
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (JSON)
|
|
659
|
+
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
661
660
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
662
661
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
663
662
|
|
|
@@ -665,7 +664,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
665
664
|
|
|
666
665
|
| Downstream service | Propagation mechanism |
|
|
667
666
|
|--------------------|-----------------------|
|
|
668
|
-
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (JSON
|
|
667
|
+
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
|
|
669
668
|
| **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
|
|
670
669
|
|
|
671
670
|
This means that an API call with `constitution-state: connecticut` will automatically carry the full session (state + user) through SNS → SQS → Fargate without any code changes in your consumers or tasks.
|
|
@@ -1131,7 +1130,7 @@ environment_variables = {
|
|
|
1131
1130
|
By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
|
|
1132
1131
|
|
|
1133
1132
|
**Constitution-state handling in SQS:**
|
|
1134
|
-
- **Single mode**: the framework extracts
|
|
1133
|
+
- **Single mode**: the framework extracts the session from each record automatically (from SNS `MessageAttributes`, Base64-decoded) and sets it in context before calling `process_record()`. You do not need to extract it yourself.
|
|
1135
1134
|
- **Batch mode**: the framework groups the incoming records by `constitution-state` and calls `process_batch()` once per group, with the correct state in context for each group. This ensures that state-scoped repositories resolve to the right database even when a batch contains records from different states.
|
|
1136
1135
|
|
|
1137
1136
|
```python
|
|
@@ -1181,7 +1180,7 @@ class OrderConsumer(SQSConsumer):
|
|
|
1181
1180
|
|
|
1182
1181
|
### SNS Publisher - Batch Publishing
|
|
1183
1182
|
|
|
1184
|
-
The `SNSPublisher` automatically injects the current
|
|
1183
|
+
The `SNSPublisher` automatically injects the current session as a Base64-encoded `MessageAttribute` on every published message. Base64 encoding is used to avoid SNS filter policy issues with raw JSON string values in attributes. SQS consumers built with this framework will decode it automatically, ensuring the session flows end-to-end through the SNS → SQS chain without any manual code.
|
|
1185
1184
|
|
|
1186
1185
|
```python
|
|
1187
1186
|
topic = TitleIndexedTopic()
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/SOURCES.txt
RENAMED
|
@@ -16,6 +16,7 @@ aws_python_helper/api/fetcher.py
|
|
|
16
16
|
aws_python_helper/api/handler.py
|
|
17
17
|
aws_python_helper/context/__init__.py
|
|
18
18
|
aws_python_helper/context/session.py
|
|
19
|
+
aws_python_helper/context/state_validator.py
|
|
19
20
|
aws_python_helper/database/__init__.py
|
|
20
21
|
aws_python_helper/database/database_proxy.py
|
|
21
22
|
aws_python_helper/database/external_database_proxy.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aws-python-helper"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.35.0"
|
|
8
8
|
description = "AWS Python Helper Framework"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -12,7 +12,6 @@ requires-python = ">=3.9"
|
|
|
12
12
|
dependencies = [
|
|
13
13
|
"motor==3.3.2",
|
|
14
14
|
"pymongo==4.6.1",
|
|
15
|
-
"bcrypt>=4.0.0",
|
|
16
15
|
]
|
|
17
16
|
|
|
18
17
|
authors = [
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/auth_middleware.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/api/auth_validators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/__init__.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/database_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/database/mongo_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/fargate/task_base.py
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/fetcher.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/lambda_standalone/handler.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/repository/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper/utils/json_encoder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.35.0}/aws_python_helper.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|