aws-python-helper 1.14.0__tar.gz → 1.15.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/PKG-INFO +49 -49
  2. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/README.md +48 -48
  3. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/__init__.py +2 -0
  4. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/auth_validators.py +9 -3
  5. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/base.py +3 -3
  6. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/dispatcher.py +14 -2
  7. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/context/session.py +2 -2
  8. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/database/external_database_proxy.py +4 -4
  9. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/database/external_mongo_manager.py +1 -1
  10. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/fargate/task_base.py +2 -2
  11. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/invoker/api_client.py +4 -4
  12. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/lambda_standalone/base.py +2 -2
  13. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/model_query/base.py +5 -5
  14. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/permissions/catalog.py +1 -1
  15. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/repository/audit.py +1 -1
  16. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/repository/base.py +4 -4
  17. aws_python_helper-1.15.1/aws_python_helper/settings.py +118 -0
  18. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/sqs/consumer_base.py +2 -2
  19. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper.egg-info/PKG-INFO +49 -49
  20. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper.egg-info/SOURCES.txt +2 -0
  21. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/pyproject.toml +1 -1
  22. aws_python_helper-1.15.1/tests/test_settings.py +92 -0
  23. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/__init__.py +0 -0
  24. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/auth_middleware.py +0 -0
  25. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/exceptions.py +0 -0
  26. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/fetcher.py +0 -0
  27. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/handler.py +0 -0
  28. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/api/jwt_keys.py +0 -0
  29. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/context/__init__.py +0 -0
  30. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/context/state_validator.py +0 -0
  31. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/database/__init__.py +0 -0
  32. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/database/database_proxy.py +0 -0
  33. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/database/mongo_manager.py +0 -0
  34. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/fargate/__init__.py +0 -0
  35. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/fargate/executor.py +0 -0
  36. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/fargate/fetcher.py +0 -0
  37. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/fargate/handler.py +0 -0
  38. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/invoker/__init__.py +0 -0
  39. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/invoker/exceptions.py +0 -0
  40. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/invoker/lambda_invoker.py +0 -0
  41. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  42. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  43. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/lambda_standalone/handler.py +0 -0
  44. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/model_index_sync/__init__.py +0 -0
  45. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/model_index_sync/base.py +0 -0
  46. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/model_query/__init__.py +0 -0
  47. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/monitoring/__init__.py +0 -0
  48. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/monitoring/run.py +0 -0
  49. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/permissions/__init__.py +0 -0
  50. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/repository/__init__.py +0 -0
  51. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/sns/__init__.py +0 -0
  52. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/sns/publisher.py +0 -0
  53. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/sqs/__init__.py +0 -0
  54. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/sqs/fetcher.py +0 -0
  55. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/sqs/handler.py +0 -0
  56. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/utils/__init__.py +0 -0
  57. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/utils/json_encoder.py +0 -0
  58. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/utils/response.py +0 -0
  59. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper/utils/serializer.py +0 -0
  60. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  61. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper.egg-info/requires.txt +0 -0
  62. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/aws_python_helper.egg-info/top_level.txt +0 -0
  63. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/setup.cfg +0 -0
  64. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/tests/test_audit_stamp.py +0 -0
  65. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/tests/test_jwt_keys.py +0 -0
  66. {aws_python_helper-1.14.0 → aws_python_helper-1.15.1}/tests/test_sns_publisher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.14.0
3
+ Version: 1.15.1
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -106,7 +106,7 @@ your-project/
106
106
  │
107
107
  ├── consumer/ # SQS Consumers (direct files)
108
108
  │ ├── user_created.py # user-created -> UserCreatedConsumer
109
- │ ├── title_indexed.py # title-indexed -> TitleIndexedConsumer
109
+ │ ├── order_created.py # title-indexed -> TitleIndexedConsumer
110
110
  │ └── order_processed.py # order-processed -> OrderProcessedConsumer
111
111
  │
112
112
  ├── lambda/ # Standalone Lambdas (folders)
@@ -151,22 +151,22 @@ The framework uses automatic class name detection based on your folder/file stru
151
151
 
152
152
  ### Create an Endpoint
153
153
 
154
- **1. Create your API class** in `src/api/constitutions/list.py`:
154
+ **1. Create your API class** in `src/api/orders/list.py`:
155
155
 
156
156
  ```python
157
157
  from aws_python_helper.api.base import API
158
158
 
159
- class ConstitutionListAPI(API):
159
+ class OrdersListAPI(API):
160
160
  async def process(self):
161
161
  # Direct access to MongoDB
162
- constitutions = await self.db.constitution_db.constitutions.find().to_list(100)
163
- self.set_body(constitutions)
162
+ orders = await self.db.app_db.orders.find().to_list(100)
163
+ self.set_body(orders)
164
164
  ```
165
165
 
166
166
  **2. The routing is automatic:**
167
- - `GET /constitutions` → `src/api/constitutions/list.py`
168
- - `GET /constitutions/123` → `src/api/constitutions/get.py`
169
- - `POST /constitutions` → `src/api/constitutions/post.py`
167
+ - `GET /orders` → `src/api/orders/list.py`
168
+ - `GET /orders/123` → `src/api/orders/get.py`
169
+ - `POST /orders` → `src/api/orders/post.py`
170
170
 
171
171
  **3. Configure the generic handler** (`src/handlers/api_handler.py`):
172
172
 
@@ -177,7 +177,7 @@ handler = api_handler
177
177
 
178
178
  ### Create an SQS Consumer
179
179
 
180
- **1. Create your consumer** in `src/consumer/title_indexed.py`:
180
+ **1. Create your consumer** in `src/consumer/order_created.py`:
181
181
 
182
182
  ```python
183
183
  from aws_python_helper.sqs.consumer_base import SQSConsumer
@@ -186,7 +186,7 @@ class TitleIndexedConsumer(SQSConsumer):
186
186
  async def process_record(self, record):
187
187
  body = self.extract_content_message(record)
188
188
  # Your logic here
189
- await self.db.constitution_db.titles.insert_one(body)
189
+ await self.db.app_db.invoices.insert_one(body)
190
190
  ```
191
191
 
192
192
  **2. Configure the handler** in `src/handlers/sqs_handler.py`:
@@ -195,9 +195,9 @@ class TitleIndexedConsumer(SQSConsumer):
195
195
  from aws_python_helper.sqs.handler import sqs_handler
196
196
 
197
197
  # Create a handler for each consumer and export it
198
- title_indexed_handler = sqs_handler('title-indexed')
198
+ order_created_handler = sqs_handler('title-indexed')
199
199
 
200
- __all__ = ['title_indexed_handler']
200
+ __all__ = ['order_created_handler']
201
201
  ```
202
202
 
203
203
  ### Create a Standalone Lambda
@@ -352,7 +352,7 @@ lambda_client.invoke(
352
352
 
353
353
  ### Publish to SNS
354
354
 
355
- **1. Create your topic** in `src/topic/title_indexed.py`:
355
+ **1. Create your topic** in `src/topic/order_created.py`:
356
356
 
357
357
  ```python
358
358
  from aws_python_helper.sns.publisher import SNSPublisher
@@ -364,10 +364,10 @@ class TitleIndexedTopic(SNSPublisher):
364
364
  topic_arn=os.getenv('TITLE_INDEXED_SNS_TOPIC_ARN')
365
365
  )
366
366
 
367
- def build_message(self, constitution_id, title, event_type='title_indexed'):
367
+ def build_message(self, order_id, title, event_type='order_created'):
368
368
  return {
369
369
  'content': {
370
- 'constitution_id': constitution_id,
370
+ 'order_id': order_id,
371
371
  'title': title,
372
372
  'event_type': event_type
373
373
  },
@@ -380,18 +380,18 @@ class TitleIndexedTopic(SNSPublisher):
380
380
  **2. Use the topic** from anywhere:
381
381
 
382
382
  ```python
383
- from src.topic.title_indexed import TitleIndexedTopic
383
+ from src.topic.order_created import TitleIndexedTopic
384
384
 
385
385
  # In a consumer, API or task
386
386
  topic = TitleIndexedTopic()
387
387
 
388
388
  # Publish a single message
389
- await topic.publish(topic.build_message('123', 'My Constitution'))
389
+ await topic.publish(topic.build_message('123', 'Order 123'))
390
390
 
391
391
  # Publish multiple messages in batch
392
392
  messages = [
393
- topic.build_message('id1', 'Constitution A'),
394
- topic.build_message('id2', 'Constitution B'),
393
+ topic.build_message('id1', 'Order A'),
394
+ topic.build_message('id2', 'Order B'),
395
395
  ]
396
396
  await topic.publish(messages)
397
397
  ```
@@ -420,7 +420,7 @@ class SearchTaxByTownTask(FargateTask):
420
420
  self.logger.info(f"Processing town: {town}")
421
421
 
422
422
  # Access to DB
423
- docs = await self.db.smart_data.address.find({'town': town}).to_list()
423
+ docs = await self.db.analytics.address.find({'town': town}).to_list()
424
424
 
425
425
  # Your logic here
426
426
  for doc in docs:
@@ -490,8 +490,8 @@ class MyAPI(API):
490
490
  await self.db.analytics_db.logs.insert_one({'action': 'view'})
491
491
 
492
492
  # Multiple collections
493
- titles = await self.db.constitution_db.titles.find().to_list(100)
494
- articles = await self.db.constitution_db.articles.find().to_list(100)
493
+ titles = await self.db.app_db.invoices.find().to_list(100)
494
+ articles = await self.db.app_db.line_items.find().to_list(100)
495
495
  ```
496
496
 
497
497
  The pattern is always: `self.db.<database_name>.<collection_name>.<motor_operation>()`
@@ -502,7 +502,7 @@ Connect to additional MongoDB clusters using `EXTERNAL_MONGODB_CONNECTIONS`:
502
502
 
503
503
  ```bash
504
504
  EXTERNAL_MONGODB_CONNECTIONS='[
505
- {"name": "ClusterDockets", "connection_string": "mongodb+srv://cluster.mongodb.net"},
505
+ {"name": "AnalyticsCluster", "connection_string": "mongodb+srv://cluster.mongodb.net"},
506
506
  {"name": "ClusterAnalytics", "connection_string": "mongodb+srv://analytics.mongodb.net"}
507
507
  ]'
508
508
  ```
@@ -515,7 +515,7 @@ Access external clusters via `self.external_db`:
515
515
  class AddressAPI(API):
516
516
  async def process(self):
517
517
  # Access external cluster: self.external_db.<ClusterName>.<database>.<collection>
518
- addresses = await self.external_db.ClusterDockets.smart_data.addresses.find(
518
+ addresses = await self.external_db.AnalyticsCluster.analytics.addresses.find(
519
519
  {'town': self.data['town']}
520
520
  ).to_list(100)
521
521
 
@@ -646,7 +646,7 @@ class AddressRepository(Repository):
646
646
 
647
647
  @property
648
648
  def database_key(self):
649
- return "smart_data"
649
+ return "analytics"
650
650
 
651
651
  @property
652
652
  def collection_name(self):
@@ -658,7 +658,7 @@ class AddressRepository(Repository):
658
658
 
659
659
  @property
660
660
  def cluster_name(self):
661
- return "ClusterDockets" # Must match a name in EXTERNAL_MONGODB_CONNECTIONS
661
+ return "AnalyticsCluster" # Must match a name in EXTERNAL_MONGODB_CONNECTIONS
662
662
 
663
663
  async def find_by_query(self, query, limit=None):
664
664
  cursor = self.collection.find(query)
@@ -693,7 +693,7 @@ The framework propagates a `Session` object automatically across the entire asyn
693
693
 
694
694
  | Entry point | How the session is read |
695
695
  |-------------|-------------------------|
696
- | **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 |
696
+ | **API Gateway** | `STATE_HEADER` header → `session.state` (when `AUTHORIZATION` includes `state`); auth middleware → `session.user` (when includes `user`). Returns `400` if required header is missing |
697
697
  | **Standalone Lambda** | `session` dict in the event payload — **required by default** (must include `state`), raises `ValueError` if missing. A Lambda can opt out via `requires_state = False` (e.g. `ModelIndexSyncLambda`), making the session optional |
698
698
  | **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
699
699
  | **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
@@ -706,7 +706,7 @@ The framework propagates a `Session` object automatically across the entire asyn
706
706
  | **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
707
707
  | **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
708
708
 
709
- 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.
709
+ This means that an API call with `x-state: connecticut` will automatically carry the full session (state + user) through SNS → SQS → Fargate without any code changes in your consumers or tasks.
710
710
 
711
711
  ### Accessing the session in handlers
712
712
 
@@ -739,11 +739,11 @@ session.user # authenticated user dict, or None
739
739
  set_session(Session(state="new_jersey")) # set manually (the framework does this automatically)
740
740
  ```
741
741
 
742
- ### API example — `constitution-state` header
742
+ ### API example — the state header
743
743
 
744
744
  ```
745
- GET /constitutions HTTP/1.1
746
- constitution-state: connecticut
745
+ GET /orders HTTP/1.1
746
+ x-state: connecticut
747
747
  Authorization: Bearer <token>
748
748
  ```
749
749
 
@@ -888,10 +888,10 @@ class AdminOnlyAPI(API):
888
888
  ## 🎯 Complete Example
889
889
 
890
890
  ```python
891
- # src/api/constitutions/list.py
891
+ # src/api/orders/list.py
892
892
  from aws_python_helper.api.base import API
893
893
 
894
- class ConstitutionListAPI(API):
894
+ class OrdersListAPI(API):
895
895
  async def validate(self):
896
896
  if 'limit' in self.data:
897
897
  limit = int(self.data['limit'])
@@ -906,12 +906,12 @@ class ConstitutionListAPI(API):
906
906
 
907
907
  # Query MongoDB
908
908
  limit = int(self.data.get('limit', 100))
909
- results = await self.db.constitution_db.constitutions.find(
909
+ results = await self.db.app_db.orders.find(
910
910
  filters
911
911
  ).limit(limit).to_list(limit)
912
912
 
913
913
  # Count total
914
- total = await self.db.constitution_db.constitutions.count_documents(filters)
914
+ total = await self.db.app_db.orders.count_documents(filters)
915
915
 
916
916
  # Register in analytics
917
917
  await self.db.analytics_db.searches.insert_one({
@@ -1142,23 +1142,23 @@ HTTP client for service-to-service calls. Resolves the target URL and auth token
1142
1142
  ```python
1143
1143
  from aws_python_helper import ApiClient
1144
1144
 
1145
- client = ApiClient("dockets") # service name only
1145
+ client = ApiClient("billing") # service name only
1146
1146
  result = await client.post("/search", body={"keys": [...]})
1147
- result = await client.get("/dockets/123")
1148
- result = await client.list("/dockets", params={"state": "CT"})
1149
- result = await client.put("/dockets/123", body={"status": "active"})
1150
- result = await client.patch("/dockets/123", body={"reviewed": True})
1151
- result = await client.delete("/dockets/123")
1147
+ result = await client.get("/invoices/123")
1148
+ result = await client.list("/invoices", params={"state": "CT"})
1149
+ result = await client.put("/invoices/123", body={"status": "active"})
1150
+ result = await client.patch("/invoices/123", body={"reviewed": True})
1151
+ result = await client.delete("/invoices/123")
1152
1152
 
1153
1153
  # Extra headers merged with the defaults (token is always injected automatically)
1154
- client = ApiClient("dockets", headers={"constitution-state": "CT"})
1154
+ client = ApiClient("billing", headers={"x-tenant": "acme"})
1155
1155
  ```
1156
1156
 
1157
1157
  **How URL and token are resolved:**
1158
1158
 
1159
1159
  | Env var | Purpose |
1160
1160
  |---------|---------|
1161
- | `MICROSERVICE_URLS` | JSON map: `{"dockets": "https://api.example.com/", "title-search": "https://..."}` |
1161
+ | `MICROSERVICE_URLS` | JSON map: `{"billing": "https://api.example.com/", "catalog": "https://..."}` |
1162
1162
  | `INTER_SERVICE_TOKEN` | Bearer token injected as `Authorization: Bearer <token>` |
1163
1163
  | `AUTH_BYPASS_TOKEN` | Fallback if `INTER_SERVICE_TOKEN` is not set |
1164
1164
 
@@ -1176,7 +1176,7 @@ client = ApiClient("dockets", headers={"constitution-state": "CT"})
1176
1176
  # In AWS Secrets Manager secret (JSON):
1177
1177
  # {
1178
1178
  # "inter_service_token": "my-secret-token",
1179
- # "microservice_urls": "{\"dockets\": \"https://api-dockets.execute-api.us-east-2.amazonaws.com/\"}",
1179
+ # "microservice_urls": "{\"billing\": \"https://api.example.com/\"}",
1180
1180
  # ...
1181
1181
  # }
1182
1182
 
@@ -1596,7 +1596,7 @@ environment_variables = {
1596
1596
  | `MICROSERVICE_URLS` | Optional | JSON map of service name → base URL used by `ApiClient` |
1597
1597
  | `ECS_CLUSTER` | Fargate only | ECS cluster name for `FargateExecutor` |
1598
1598
  | `ECS_SUBNETS` | Fargate only | Comma-separated subnet IDs for Fargate tasks |
1599
- | `CONSTITUTION_STATE` | Fargate only (auto) | State injected automatically by `FargateExecutor` — do not set manually |
1599
+ | `SESSION` | Fargate only (auto) | The whole session, injected by `FargateExecutor` — do not set manually |
1600
1600
  | `AWS_REGION` | Fargate/SNS/SQS | AWS region |
1601
1601
  | `AWS_ACCOUNT_ID` | SQS `get_queue_url` | AWS account ID |
1602
1602
  | `SERVICE_NAME` | SQS `get_queue_url` | Service name prefix for queue name |
@@ -1609,9 +1609,9 @@ environment_variables = {
1609
1609
 
1610
1610
  By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
1611
1611
 
1612
- **Constitution-state handling in SQS:**
1612
+ **State handling in SQS:**
1613
1613
  - **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.
1614
- - **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.
1614
+ - **Batch mode**: the framework groups the incoming records by 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.
1615
1615
 
1616
1616
  ```python
1617
1617
  from aws_python_helper.sqs.consumer_base import SQSConsumer
@@ -1666,7 +1666,7 @@ The `SNSPublisher` automatically injects the current session as a Base64-encoded
1666
1666
  topic = TitleIndexedTopic()
1667
1667
 
1668
1668
  # Publish multiple messages in a single call
1669
- # constitution-state is auto-injected as a MessageAttribute on each message
1669
+ # The state travels as a MessageAttribute on each message, injected automatically
1670
1670
  await topic.publish([
1671
1671
  {'content': {'id': 'id1', 'title': 'Title 1'}, 'attributes': {'type': 'created'}},
1672
1672
  {'content': {'id': 'id2', 'title': 'Title 2'}, 'attributes': {'type': 'updated'}},
@@ -84,7 +84,7 @@ your-project/
84
84
  │
85
85
  ├── consumer/ # SQS Consumers (direct files)
86
86
  │ ├── user_created.py # user-created -> UserCreatedConsumer
87
- │ ├── title_indexed.py # title-indexed -> TitleIndexedConsumer
87
+ │ ├── order_created.py # title-indexed -> TitleIndexedConsumer
88
88
  │ └── order_processed.py # order-processed -> OrderProcessedConsumer
89
89
  │
90
90
  ├── lambda/ # Standalone Lambdas (folders)
@@ -129,22 +129,22 @@ The framework uses automatic class name detection based on your folder/file stru
129
129
 
130
130
  ### Create an Endpoint
131
131
 
132
- **1. Create your API class** in `src/api/constitutions/list.py`:
132
+ **1. Create your API class** in `src/api/orders/list.py`:
133
133
 
134
134
  ```python
135
135
  from aws_python_helper.api.base import API
136
136
 
137
- class ConstitutionListAPI(API):
137
+ class OrdersListAPI(API):
138
138
  async def process(self):
139
139
  # Direct access to MongoDB
140
- constitutions = await self.db.constitution_db.constitutions.find().to_list(100)
141
- self.set_body(constitutions)
140
+ orders = await self.db.app_db.orders.find().to_list(100)
141
+ self.set_body(orders)
142
142
  ```
143
143
 
144
144
  **2. The routing is automatic:**
145
- - `GET /constitutions` → `src/api/constitutions/list.py`
146
- - `GET /constitutions/123` → `src/api/constitutions/get.py`
147
- - `POST /constitutions` → `src/api/constitutions/post.py`
145
+ - `GET /orders` → `src/api/orders/list.py`
146
+ - `GET /orders/123` → `src/api/orders/get.py`
147
+ - `POST /orders` → `src/api/orders/post.py`
148
148
 
149
149
  **3. Configure the generic handler** (`src/handlers/api_handler.py`):
150
150
 
@@ -155,7 +155,7 @@ handler = api_handler
155
155
 
156
156
  ### Create an SQS Consumer
157
157
 
158
- **1. Create your consumer** in `src/consumer/title_indexed.py`:
158
+ **1. Create your consumer** in `src/consumer/order_created.py`:
159
159
 
160
160
  ```python
161
161
  from aws_python_helper.sqs.consumer_base import SQSConsumer
@@ -164,7 +164,7 @@ class TitleIndexedConsumer(SQSConsumer):
164
164
  async def process_record(self, record):
165
165
  body = self.extract_content_message(record)
166
166
  # Your logic here
167
- await self.db.constitution_db.titles.insert_one(body)
167
+ await self.db.app_db.invoices.insert_one(body)
168
168
  ```
169
169
 
170
170
  **2. Configure the handler** in `src/handlers/sqs_handler.py`:
@@ -173,9 +173,9 @@ class TitleIndexedConsumer(SQSConsumer):
173
173
  from aws_python_helper.sqs.handler import sqs_handler
174
174
 
175
175
  # Create a handler for each consumer and export it
176
- title_indexed_handler = sqs_handler('title-indexed')
176
+ order_created_handler = sqs_handler('title-indexed')
177
177
 
178
- __all__ = ['title_indexed_handler']
178
+ __all__ = ['order_created_handler']
179
179
  ```
180
180
 
181
181
  ### Create a Standalone Lambda
@@ -330,7 +330,7 @@ lambda_client.invoke(
330
330
 
331
331
  ### Publish to SNS
332
332
 
333
- **1. Create your topic** in `src/topic/title_indexed.py`:
333
+ **1. Create your topic** in `src/topic/order_created.py`:
334
334
 
335
335
  ```python
336
336
  from aws_python_helper.sns.publisher import SNSPublisher
@@ -342,10 +342,10 @@ class TitleIndexedTopic(SNSPublisher):
342
342
  topic_arn=os.getenv('TITLE_INDEXED_SNS_TOPIC_ARN')
343
343
  )
344
344
 
345
- def build_message(self, constitution_id, title, event_type='title_indexed'):
345
+ def build_message(self, order_id, title, event_type='order_created'):
346
346
  return {
347
347
  'content': {
348
- 'constitution_id': constitution_id,
348
+ 'order_id': order_id,
349
349
  'title': title,
350
350
  'event_type': event_type
351
351
  },
@@ -358,18 +358,18 @@ class TitleIndexedTopic(SNSPublisher):
358
358
  **2. Use the topic** from anywhere:
359
359
 
360
360
  ```python
361
- from src.topic.title_indexed import TitleIndexedTopic
361
+ from src.topic.order_created import TitleIndexedTopic
362
362
 
363
363
  # In a consumer, API or task
364
364
  topic = TitleIndexedTopic()
365
365
 
366
366
  # Publish a single message
367
- await topic.publish(topic.build_message('123', 'My Constitution'))
367
+ await topic.publish(topic.build_message('123', 'Order 123'))
368
368
 
369
369
  # Publish multiple messages in batch
370
370
  messages = [
371
- topic.build_message('id1', 'Constitution A'),
372
- topic.build_message('id2', 'Constitution B'),
371
+ topic.build_message('id1', 'Order A'),
372
+ topic.build_message('id2', 'Order B'),
373
373
  ]
374
374
  await topic.publish(messages)
375
375
  ```
@@ -398,7 +398,7 @@ class SearchTaxByTownTask(FargateTask):
398
398
  self.logger.info(f"Processing town: {town}")
399
399
 
400
400
  # Access to DB
401
- docs = await self.db.smart_data.address.find({'town': town}).to_list()
401
+ docs = await self.db.analytics.address.find({'town': town}).to_list()
402
402
 
403
403
  # Your logic here
404
404
  for doc in docs:
@@ -468,8 +468,8 @@ class MyAPI(API):
468
468
  await self.db.analytics_db.logs.insert_one({'action': 'view'})
469
469
 
470
470
  # Multiple collections
471
- titles = await self.db.constitution_db.titles.find().to_list(100)
472
- articles = await self.db.constitution_db.articles.find().to_list(100)
471
+ titles = await self.db.app_db.invoices.find().to_list(100)
472
+ articles = await self.db.app_db.line_items.find().to_list(100)
473
473
  ```
474
474
 
475
475
  The pattern is always: `self.db.<database_name>.<collection_name>.<motor_operation>()`
@@ -480,7 +480,7 @@ Connect to additional MongoDB clusters using `EXTERNAL_MONGODB_CONNECTIONS`:
480
480
 
481
481
  ```bash
482
482
  EXTERNAL_MONGODB_CONNECTIONS='[
483
- {"name": "ClusterDockets", "connection_string": "mongodb+srv://cluster.mongodb.net"},
483
+ {"name": "AnalyticsCluster", "connection_string": "mongodb+srv://cluster.mongodb.net"},
484
484
  {"name": "ClusterAnalytics", "connection_string": "mongodb+srv://analytics.mongodb.net"}
485
485
  ]'
486
486
  ```
@@ -493,7 +493,7 @@ Access external clusters via `self.external_db`:
493
493
  class AddressAPI(API):
494
494
  async def process(self):
495
495
  # Access external cluster: self.external_db.<ClusterName>.<database>.<collection>
496
- addresses = await self.external_db.ClusterDockets.smart_data.addresses.find(
496
+ addresses = await self.external_db.AnalyticsCluster.analytics.addresses.find(
497
497
  {'town': self.data['town']}
498
498
  ).to_list(100)
499
499
 
@@ -624,7 +624,7 @@ class AddressRepository(Repository):
624
624
 
625
625
  @property
626
626
  def database_key(self):
627
- return "smart_data"
627
+ return "analytics"
628
628
 
629
629
  @property
630
630
  def collection_name(self):
@@ -636,7 +636,7 @@ class AddressRepository(Repository):
636
636
 
637
637
  @property
638
638
  def cluster_name(self):
639
- return "ClusterDockets" # Must match a name in EXTERNAL_MONGODB_CONNECTIONS
639
+ return "AnalyticsCluster" # Must match a name in EXTERNAL_MONGODB_CONNECTIONS
640
640
 
641
641
  async def find_by_query(self, query, limit=None):
642
642
  cursor = self.collection.find(query)
@@ -671,7 +671,7 @@ The framework propagates a `Session` object automatically across the entire asyn
671
671
 
672
672
  | Entry point | How the session is read |
673
673
  |-------------|-------------------------|
674
- | **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 |
674
+ | **API Gateway** | `STATE_HEADER` header → `session.state` (when `AUTHORIZATION` includes `state`); auth middleware → `session.user` (when includes `user`). Returns `400` if required header is missing |
675
675
  | **Standalone Lambda** | `session` dict in the event payload — **required by default** (must include `state`), raises `ValueError` if missing. A Lambda can opt out via `requires_state = False` (e.g. `ModelIndexSyncLambda`), making the session optional |
676
676
  | **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
677
677
  | **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
@@ -684,7 +684,7 @@ The framework propagates a `Session` object automatically across the entire asyn
684
684
  | **SNS Publisher** | Auto-injects the full session as a `session` `MessageAttribute` (Base64-encoded JSON) on every published message |
685
685
  | **FargateExecutor** | Auto-injects `SESSION` as a JSON env var when launching Fargate containers |
686
686
 
687
- 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.
687
+ This means that an API call with `x-state: connecticut` will automatically carry the full session (state + user) through SNS → SQS → Fargate without any code changes in your consumers or tasks.
688
688
 
689
689
  ### Accessing the session in handlers
690
690
 
@@ -717,11 +717,11 @@ session.user # authenticated user dict, or None
717
717
  set_session(Session(state="new_jersey")) # set manually (the framework does this automatically)
718
718
  ```
719
719
 
720
- ### API example — `constitution-state` header
720
+ ### API example — the state header
721
721
 
722
722
  ```
723
- GET /constitutions HTTP/1.1
724
- constitution-state: connecticut
723
+ GET /orders HTTP/1.1
724
+ x-state: connecticut
725
725
  Authorization: Bearer <token>
726
726
  ```
727
727
 
@@ -866,10 +866,10 @@ class AdminOnlyAPI(API):
866
866
  ## 🎯 Complete Example
867
867
 
868
868
  ```python
869
- # src/api/constitutions/list.py
869
+ # src/api/orders/list.py
870
870
  from aws_python_helper.api.base import API
871
871
 
872
- class ConstitutionListAPI(API):
872
+ class OrdersListAPI(API):
873
873
  async def validate(self):
874
874
  if 'limit' in self.data:
875
875
  limit = int(self.data['limit'])
@@ -884,12 +884,12 @@ class ConstitutionListAPI(API):
884
884
 
885
885
  # Query MongoDB
886
886
  limit = int(self.data.get('limit', 100))
887
- results = await self.db.constitution_db.constitutions.find(
887
+ results = await self.db.app_db.orders.find(
888
888
  filters
889
889
  ).limit(limit).to_list(limit)
890
890
 
891
891
  # Count total
892
- total = await self.db.constitution_db.constitutions.count_documents(filters)
892
+ total = await self.db.app_db.orders.count_documents(filters)
893
893
 
894
894
  # Register in analytics
895
895
  await self.db.analytics_db.searches.insert_one({
@@ -1120,23 +1120,23 @@ HTTP client for service-to-service calls. Resolves the target URL and auth token
1120
1120
  ```python
1121
1121
  from aws_python_helper import ApiClient
1122
1122
 
1123
- client = ApiClient("dockets") # service name only
1123
+ client = ApiClient("billing") # service name only
1124
1124
  result = await client.post("/search", body={"keys": [...]})
1125
- result = await client.get("/dockets/123")
1126
- result = await client.list("/dockets", params={"state": "CT"})
1127
- result = await client.put("/dockets/123", body={"status": "active"})
1128
- result = await client.patch("/dockets/123", body={"reviewed": True})
1129
- result = await client.delete("/dockets/123")
1125
+ result = await client.get("/invoices/123")
1126
+ result = await client.list("/invoices", params={"state": "CT"})
1127
+ result = await client.put("/invoices/123", body={"status": "active"})
1128
+ result = await client.patch("/invoices/123", body={"reviewed": True})
1129
+ result = await client.delete("/invoices/123")
1130
1130
 
1131
1131
  # Extra headers merged with the defaults (token is always injected automatically)
1132
- client = ApiClient("dockets", headers={"constitution-state": "CT"})
1132
+ client = ApiClient("billing", headers={"x-tenant": "acme"})
1133
1133
  ```
1134
1134
 
1135
1135
  **How URL and token are resolved:**
1136
1136
 
1137
1137
  | Env var | Purpose |
1138
1138
  |---------|---------|
1139
- | `MICROSERVICE_URLS` | JSON map: `{"dockets": "https://api.example.com/", "title-search": "https://..."}` |
1139
+ | `MICROSERVICE_URLS` | JSON map: `{"billing": "https://api.example.com/", "catalog": "https://..."}` |
1140
1140
  | `INTER_SERVICE_TOKEN` | Bearer token injected as `Authorization: Bearer <token>` |
1141
1141
  | `AUTH_BYPASS_TOKEN` | Fallback if `INTER_SERVICE_TOKEN` is not set |
1142
1142
 
@@ -1154,7 +1154,7 @@ client = ApiClient("dockets", headers={"constitution-state": "CT"})
1154
1154
  # In AWS Secrets Manager secret (JSON):
1155
1155
  # {
1156
1156
  # "inter_service_token": "my-secret-token",
1157
- # "microservice_urls": "{\"dockets\": \"https://api-dockets.execute-api.us-east-2.amazonaws.com/\"}",
1157
+ # "microservice_urls": "{\"billing\": \"https://api.example.com/\"}",
1158
1158
  # ...
1159
1159
  # }
1160
1160
 
@@ -1574,7 +1574,7 @@ environment_variables = {
1574
1574
  | `MICROSERVICE_URLS` | Optional | JSON map of service name → base URL used by `ApiClient` |
1575
1575
  | `ECS_CLUSTER` | Fargate only | ECS cluster name for `FargateExecutor` |
1576
1576
  | `ECS_SUBNETS` | Fargate only | Comma-separated subnet IDs for Fargate tasks |
1577
- | `CONSTITUTION_STATE` | Fargate only (auto) | State injected automatically by `FargateExecutor` — do not set manually |
1577
+ | `SESSION` | Fargate only (auto) | The whole session, injected by `FargateExecutor` — do not set manually |
1578
1578
  | `AWS_REGION` | Fargate/SNS/SQS | AWS region |
1579
1579
  | `AWS_ACCOUNT_ID` | SQS `get_queue_url` | AWS account ID |
1580
1580
  | `SERVICE_NAME` | SQS `get_queue_url` | Service name prefix for queue name |
@@ -1587,9 +1587,9 @@ environment_variables = {
1587
1587
 
1588
1588
  By default, consumers process messages one by one (`"single"` mode). Use `"batch"` mode when you need to group or bulk-process messages.
1589
1589
 
1590
- **Constitution-state handling in SQS:**
1590
+ **State handling in SQS:**
1591
1591
  - **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.
1592
- - **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.
1592
+ - **Batch mode**: the framework groups the incoming records by 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.
1593
1593
 
1594
1594
  ```python
1595
1595
  from aws_python_helper.sqs.consumer_base import SQSConsumer
@@ -1644,7 +1644,7 @@ The `SNSPublisher` automatically injects the current session as a Base64-encoded
1644
1644
  topic = TitleIndexedTopic()
1645
1645
 
1646
1646
  # Publish multiple messages in a single call
1647
- # constitution-state is auto-injected as a MessageAttribute on each message
1647
+ # The state travels as a MessageAttribute on each message, injected automatically
1648
1648
  await topic.publish([
1649
1649
  {'content': {'id': 'id1', 'title': 'Title 1'}, 'attributes': {'type': 'created'}},
1650
1650
  {'content': {'id': 'id2', 'title': 'Title 2'}, 'attributes': {'type': 'updated'}},
@@ -5,6 +5,7 @@ AWS Python Framework
5
5
  __version__ = "1.0.2"
6
6
 
7
7
  # All classes
8
+ from . import settings
8
9
  from .api.base import API
9
10
  from .sqs.consumer_base import SQSConsumer
10
11
  from .database.mongo_manager import MongoManager
@@ -60,6 +61,7 @@ from .invoker import (
60
61
  from .monitoring import process_run, resolve_code, RunReporter, ProcessRunPublisher
61
62
 
62
63
  __all__ = [
64
+ "settings",
63
65
  'API',
64
66
  'process_run',
65
67
  'resolve_code',
@@ -7,6 +7,12 @@ from typing import Dict, Any, Optional
7
7
  import os
8
8
  import logging
9
9
  from datetime import datetime
10
+ # Quién firma los tokens que este servicio verifica. Es un default histórico y
11
+ # no un nombre que el framework imponga: el `iss` de un token ya emitido no se
12
+ # puede cambiar sin invalidarlo, así que se configura con JWT_ISSUER y esto
13
+ # queda como el valor que venía de antes.
14
+ ISSUER_DEFAULT = 'constitution-core'
15
+
10
16
  from . import jwt_keys
11
17
  from .exceptions import UnauthorizedError
12
18
  from ..database.mongo_manager import MongoManager
@@ -186,7 +192,7 @@ class TokenValidator(AuthValidator):
186
192
 
187
193
  class JWTValidator(AuthValidator):
188
194
  """
189
- Validates RS256 JWTs issued by constitution-core.
195
+ Validates RS256 JWTs issued by the identity service.
190
196
 
191
197
  Stateless: the signature and the claims are enough, so no database is
192
198
  touched. That is what lets any microservice authenticate a request without
@@ -198,7 +204,7 @@ class JWTValidator(AuthValidator):
198
204
  Environment:
199
205
  CORE_API_URL: where core answers. How the key is found.
200
206
  JWT_PUBLIC_KEY: pins the key instead of reading it from core.
201
- JWT_ISSUER: expected `iss`. Defaults to 'constitution-core'.
207
+ JWT_ISSUER: expected `iss`. Must match what the issuer signs.
202
208
  JWT_AUDIENCE: expected `aud`. Only verified when set.
203
209
  AUTH_BYPASS_TOKEN: still honoured, same as TokenValidator.
204
210
  """
@@ -235,7 +241,7 @@ class JWTValidator(AuthValidator):
235
241
  token,
236
242
  key,
237
243
  algorithms=['RS256'],
238
- issuer=os.getenv('JWT_ISSUER', 'constitution-core'),
244
+ issuer=os.getenv('JWT_ISSUER', ISSUER_DEFAULT),
239
245
  audience=audience,
240
246
  options={'verify_aud': bool(audience)},
241
247
  )