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.
Files changed (48) hide show
  1. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/PKG-INFO +5 -5
  2. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/README.md +4 -4
  3. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/__init__.py +3 -0
  4. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/dispatcher.py +12 -0
  5. aws_python_helper-0.34.0/aws_python_helper/context/__init__.py +4 -0
  6. aws_python_helper-0.34.0/aws_python_helper/context/state_validator.py +54 -0
  7. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/base.py +2 -0
  8. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sns/publisher.py +22 -6
  9. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/consumer_base.py +6 -13
  10. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/PKG-INFO +5 -5
  11. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/SOURCES.txt +1 -0
  12. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/pyproject.toml +1 -1
  13. aws_python_helper-0.33.0/aws_python_helper/context/__init__.py +0 -3
  14. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/__init__.py +0 -0
  15. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/auth_middleware.py +0 -0
  16. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/auth_validators.py +0 -0
  17. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/base.py +0 -0
  18. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/exceptions.py +0 -0
  19. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/fetcher.py +0 -0
  20. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/api/handler.py +0 -0
  21. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/context/session.py +0 -0
  22. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/__init__.py +0 -0
  23. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/database_proxy.py +0 -0
  24. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/external_database_proxy.py +0 -0
  25. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
  26. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/database/mongo_manager.py +0 -0
  27. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/__init__.py +0 -0
  28. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/executor.py +0 -0
  29. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/fetcher.py +0 -0
  30. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/handler.py +0 -0
  31. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/fargate/task_base.py +0 -0
  32. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  33. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  34. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
  35. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/repository/__init__.py +0 -0
  36. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/repository/base.py +0 -0
  37. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sns/__init__.py +0 -0
  38. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/__init__.py +0 -0
  39. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/fetcher.py +0 -0
  40. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/sqs/handler.py +0 -0
  41. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/__init__.py +0 -0
  42. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/json_encoder.py +0 -0
  43. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/response.py +0 -0
  44. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper/utils/serializer.py +0 -0
  45. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  46. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/requires.txt +0 -0
  47. {aws_python_helper-0.33.0 → aws_python_helper-0.34.0}/aws_python_helper.egg-info/top_level.txt +0 -0
  48. {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.33.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), falls back to legacy `body.constitution_state` |
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 string) on every published message |
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 `constitution-state` from each record automatically (from SNS `MessageAttributes`, then from `body.constitution_state`) and sets it in context before calling `process_record()`. You do not need to extract it yourself.
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 `constitution-state` as a `MessageAttribute` on every published message. SQS consumers built with this framework will then extract it automatically, ensuring the state flows end-to-end through the SNS → SQS chain without any manual code.
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), falls back to legacy `body.constitution_state` |
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 string) on every published message |
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 `constitution-state` from each record automatically (from SNS `MessageAttributes`, then from `body.constitution_state`) and sets it in context before calling `process_record()`. You do not need to extract it yourself.
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 `constitution-state` as a `MessageAttribute` on every published message. SQS consumers built with this framework will then extract it automatically, ensuring the state flows end-to-end through the SNS → SQS chain without any manual code.
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,4 @@
1
+ from .session import Session, get_session, set_session
2
+ from .state_validator import StateValidator, InvalidStateError
3
+
4
+ __all__ = ['Session', 'get_session', 'set_session', 'StateValidator', 'InvalidStateError']
@@ -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()
@@ -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
- result = self.sns_client.publish_batch(**params)
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['Successful']:
195
+ for message in result.get('Successful', []):
191
196
  message_ids_success.append(message['MessageId'])
192
197
 
193
- for message in result['Failed']:
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
- attributes['session'] = self._serialize_message(session_dict)
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),
@@ -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 first in SNS MessageAttributes for 'session' (injected automatically
281
- by SNSPublisher), then falls back to the parsed message body content for
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
- return Session.from_dict(json.loads(session_str))
307
- except (json.JSONDecodeError, TypeError):
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.33.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), falls back to legacy `body.constitution_state` |
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 string) on every published message |
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 `constitution-state` from each record automatically (from SNS `MessageAttributes`, then from `body.constitution_state`) and sets it in context before calling `process_record()`. You do not need to extract it yourself.
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 `constitution-state` as a `MessageAttribute` on every published message. SQS consumers built with this framework will then extract it automatically, ensuring the state flows end-to-end through the SNS → SQS chain without any manual code.
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()
@@ -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.33.0"
7
+ version = "0.34.0"
8
8
  description = "AWS Python Helper Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +0,0 @@
1
- from .session import Session, get_session, set_session
2
-
3
- __all__ = ['Session', 'get_session', 'set_session']