aws-python-helper 0.33.0__tar.gz → 0.34.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.34.0}/PKG-INFO +5 -5
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/README.md +4 -4
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/__init__.py +3 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/dispatcher.py +12 -0
- aws_python_helper-0.34.0/aws_python_helper/context/__init__.py +4 -0
- aws_python_helper-0.34.0/aws_python_helper/context/state_validator.py +54 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/base.py +2 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sns/publisher.py +22 -6
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/consumer_base.py +6 -13
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/PKG-INFO +5 -5
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/SOURCES.txt +1 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/pyproject.toml +1 -1
- aws_python_helper-0.33.0/aws_python_helper/context/__init__.py +0 -3
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/auth_middleware.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/auth_validators.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/base.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/exceptions.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/context/session.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/database_proxy.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/external_database_proxy.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/mongo_manager.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/executor.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/task_base.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/repository/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/repository/base.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sns/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/fetcher.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/handler.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/__init__.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/json_encoder.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/response.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/serializer.py +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/requires.txt +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/top_level.txt +0 -0
- {aws_python_helper-0.33.0 → aws_python_helper-0.34.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.34.0
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -657,7 +657,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
657
657
|
|-------------|-------------------------|
|
|
658
658
|
| **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
659
|
| **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)
|
|
660
|
+
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
661
661
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
662
662
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
663
663
|
|
|
@@ -665,7 +665,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
665
665
|
|
|
666
666
|
| Downstream service | Propagation mechanism |
|
|
667
667
|
|--------------------|-----------------------|
|
|
668
|
-
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (JSON
|
|
668
|
+
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
|
|
669
669
|
| **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
|
|
670
670
|
|
|
671
671
|
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 +1131,7 @@ environment_variables = {
|
|
|
1131
1131
|
By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
|
|
1132
1132
|
|
|
1133
1133
|
**Constitution-state handling in SQS:**
|
|
1134
|
-
- **Single mode**: the framework extracts
|
|
1134
|
+
- **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
1135
|
- **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
1136
|
|
|
1137
1137
|
```python
|
|
@@ -1181,7 +1181,7 @@ class OrderConsumer(SQSConsumer):
|
|
|
1181
1181
|
|
|
1182
1182
|
### SNS Publisher - Batch Publishing
|
|
1183
1183
|
|
|
1184
|
-
The `SNSPublisher` automatically injects the current
|
|
1184
|
+
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
1185
|
|
|
1186
1186
|
```python
|
|
1187
1187
|
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.34.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.34.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.34.0
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -657,7 +657,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
657
657
|
|-------------|-------------------------|
|
|
658
658
|
| **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
659
|
| **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)
|
|
660
|
+
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
661
661
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
662
662
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
663
663
|
|
|
@@ -665,7 +665,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
665
665
|
|
|
666
666
|
| Downstream service | Propagation mechanism |
|
|
667
667
|
|--------------------|-----------------------|
|
|
668
|
-
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (JSON
|
|
668
|
+
| **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
|
|
669
669
|
| **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
|
|
670
670
|
|
|
671
671
|
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 +1131,7 @@ environment_variables = {
|
|
|
1131
1131
|
By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
|
|
1132
1132
|
|
|
1133
1133
|
**Constitution-state handling in SQS:**
|
|
1134
|
-
- **Single mode**: the framework extracts
|
|
1134
|
+
- **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
1135
|
- **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
1136
|
|
|
1137
1137
|
```python
|
|
@@ -1181,7 +1181,7 @@ class OrderConsumer(SQSConsumer):
|
|
|
1181
1181
|
|
|
1182
1182
|
### SNS Publisher - Batch Publishing
|
|
1183
1183
|
|
|
1184
|
-
The `SNSPublisher` automatically injects the current
|
|
1184
|
+
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
1185
|
|
|
1186
1186
|
```python
|
|
1187
1187
|
topic = TitleIndexedTopic()
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.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
|
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/auth_middleware.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.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.34.0}/aws_python_helper/database/__init__.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.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.34.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.34.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.34.0}/aws_python_helper/lambda_standalone/fetcher.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/handler.py
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.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.34.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.34.0}/aws_python_helper.egg-info/requires.txt
RENAMED
|
File without changes
|
{aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|