aws-python-helper 1.0.0__tar.gz → 1.0.2__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-1.0.0 → aws_python_helper-1.0.2}/PKG-INFO +138 -5
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/README.md +137 -4
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/__init__.py +5 -1
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/fetcher.py +4 -3
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/fargate/fetcher.py +4 -3
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/base.py +24 -5
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/fetcher.py +4 -3
- aws_python_helper-1.0.2/aws_python_helper/model_index_sync/__init__.py +3 -0
- aws_python_helper-1.0.2/aws_python_helper/model_index_sync/base.py +114 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/repository/base.py +40 -22
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/sqs/fetcher.py +4 -3
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/PKG-INFO +138 -5
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/SOURCES.txt +2 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/pyproject.toml +1 -1
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/auth_middleware.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/auth_validators.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/dispatcher.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/exceptions.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/context/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/context/session.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/context/state_validator.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/database_proxy.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/external_database_proxy.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/external_mongo_manager.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/mongo_manager.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/fargate/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/fargate/executor.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/fargate/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/fargate/task_base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/invoker/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/invoker/api_client.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/invoker/exceptions.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/invoker/lambda_invoker.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/model_query/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/model_query/base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/repository/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/sns/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/sns/publisher.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/sqs/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/sqs/consumer_base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/sqs/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/utils/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/utils/json_encoder.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/utils/response.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/utils/serializer.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/dependency_links.txt +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/requires.txt +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/top_level.txt +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-python-helper
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,6 +39,7 @@ Mini-framework to create REST APIs, SQS Consumers, SNS Publishers, Fargate Tasks
|
|
|
39
39
|
- **LambdaInvoker**: Invoke other Lambda functions (sync or async) with built-in error handling
|
|
40
40
|
- **ApiClient**: HTTP client for inter-service communication — resolves service URLs and auth token from environment variables automatically
|
|
41
41
|
- **ModelQueryLambda**: Base class for MongoDB query-proxy Lambdas — declare an `allowed_collections` whitelist and the framework handles `find` and `aggregate` operations generically
|
|
42
|
+
- **ModelIndexSyncLambda**: Base class for index-synchronization Lambdas — declare your `repositories` and the framework creates every declared index across the `core` database and all active state databases, idempotently, in a single run (run once per deploy)
|
|
42
43
|
- **JSON utilities**: Automatic serialization of MongoDB types
|
|
43
44
|
- **Type hints**: Modern Python with type annotations
|
|
44
45
|
- **Async/await**: Full support for asynchronous operations
|
|
@@ -67,6 +68,7 @@ All available classes and functions:
|
|
|
67
68
|
| `fargate_handler` | `aws_python_helper.fargate.handler` | Entry point handler for Fargate |
|
|
68
69
|
| `Repository` | `aws_python_helper.repository.base` | Base class for MongoDB repositories |
|
|
69
70
|
| `ModelQueryLambda` | `aws_python_helper` | Generic MongoDB query-proxy Lambda base class |
|
|
71
|
+
| `ModelIndexSyncLambda` | `aws_python_helper` | Index-sync Lambda base class (creates indexes across `core` + all state DBs) |
|
|
70
72
|
| `LambdaInvoker` | `aws_python_helper` | Invoke Lambda functions (sync/async) with error handling |
|
|
71
73
|
| `ApiClient` | `aws_python_helper` | HTTP client for inter-service communication |
|
|
72
74
|
| `LambdaInvocationError` | `aws_python_helper` | Raised when boto3 fails to invoke a Lambda |
|
|
@@ -77,6 +79,8 @@ All available classes and functions:
|
|
|
77
79
|
| `Session` | `aws_python_helper` | Request-scoped session object (state + user) |
|
|
78
80
|
| `get_session` | `aws_python_helper` | Read the current Session from async context |
|
|
79
81
|
| `set_session` | `aws_python_helper` | Set the current Session in async context |
|
|
82
|
+
| `StateValidator` | `aws_python_helper` | Validates a state against `core.states` (cached, active states only) |
|
|
83
|
+
| `InvalidStateError` | `aws_python_helper` | Raised when a state is not found or not active |
|
|
80
84
|
| `MongoJSONEncoder` | `aws_python_helper.utils.json_encoder` | JSON encoder for MongoDB types |
|
|
81
85
|
| `mongo_json_dumps` | `aws_python_helper.utils.json_encoder` | Helper to serialize MongoDB types |
|
|
82
86
|
| `serialize_mongo_types` | `aws_python_helper.utils.serializer` | Recursively serialize MongoDB types |
|
|
@@ -250,6 +254,15 @@ class GenerateRouteLambda(Lambda):
|
|
|
250
254
|
}
|
|
251
255
|
```
|
|
252
256
|
|
|
257
|
+
> **`requires_state` (session requirement)** — By default (`requires_state = True`) the framework requires the event to carry a `session` with a valid `state` and validates it against `core.states` before running. Override `requires_state` to return `False` for Lambdas that operate across **all** states and must not be tied to a single one (e.g. index synchronization) — the `session`/`state` then becomes optional. See [ModelIndexSyncLambda](#-modelindexsynclambda).
|
|
258
|
+
>
|
|
259
|
+
> ```python
|
|
260
|
+
> class SyncAllStatesLambda(Lambda):
|
|
261
|
+
> @property
|
|
262
|
+
> def requires_state(self) -> bool:
|
|
263
|
+
> return False # no session.state required; runs across every state
|
|
264
|
+
> ```
|
|
265
|
+
|
|
253
266
|
**2. Configure the handler** in `src/handlers/lambda_handler.py`:
|
|
254
267
|
|
|
255
268
|
```python
|
|
@@ -511,7 +524,9 @@ class AddressAPI(API):
|
|
|
511
524
|
|
|
512
525
|
## 🗂️ Repository Pattern
|
|
513
526
|
|
|
514
|
-
The framework provides a `Repository` base class that eliminates repetitive boilerplate in data access layers. Each repository only declares what collection it uses, whether it belongs to an external cluster, and what indexes
|
|
527
|
+
The framework provides a `Repository` base class that eliminates repetitive boilerplate in data access layers. Each repository only declares what collection it uses, whether it belongs to an external cluster, and what indexes it needs. The base class handles the MongoDB connection automatically.
|
|
528
|
+
|
|
529
|
+
> **Index creation is not triggered at request time.** Declaring `indexes` only *describes* the indexes; it does not create them on collection access (this avoids per-request overhead and half-finished index builds in short-lived Lambda/API runtimes). Indexes are created by the [`ModelIndexSyncLambda`](#-modelindexsynclambda) — run once per deploy — or by calling `await repo.ensure_indexes()` explicitly.
|
|
515
530
|
|
|
516
531
|
### Properties to override
|
|
517
532
|
|
|
@@ -542,7 +557,15 @@ def indexes(self):
|
|
|
542
557
|
]
|
|
543
558
|
```
|
|
544
559
|
|
|
545
|
-
|
|
560
|
+
These definitions are consumed by `ensure_indexes()`, which the [`ModelIndexSyncLambda`](#-modelindexsynclambda) runs across the relevant databases at deploy time. `ensure_indexes()` is idempotent (safe to re-run) and accepts an explicit `database_name` so the same state-scoped repository can be materialized in every state database:
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
# Create this repository's indexes in a specific database (what the index-sync Lambda does):
|
|
564
|
+
await repo.ensure_indexes(database_name="connecticut")
|
|
565
|
+
|
|
566
|
+
# Or, with no argument, in the repository's resolved database (database_key or session.state):
|
|
567
|
+
await repo.ensure_indexes()
|
|
568
|
+
```
|
|
546
569
|
|
|
547
570
|
### Repository with a fixed database
|
|
548
571
|
|
|
@@ -669,7 +692,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
669
692
|
| Entry point | How the session is read |
|
|
670
693
|
|-------------|-------------------------|
|
|
671
694
|
| **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 |
|
|
672
|
-
| **Standalone Lambda** | `session` dict in the event payload — **required** (must include `state`), raises `ValueError` if missing |
|
|
695
|
+
| **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 |
|
|
673
696
|
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
674
697
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
675
698
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
@@ -1179,13 +1202,15 @@ Create `src/lambda/model-query/main.py` in the microservice that owns the data:
|
|
|
1179
1202
|
```python
|
|
1180
1203
|
from aws_python_helper import ModelQueryLambda
|
|
1181
1204
|
|
|
1182
|
-
class
|
|
1205
|
+
class OrdersModelQueryLambda(ModelQueryLambda):
|
|
1183
1206
|
|
|
1184
1207
|
@property
|
|
1185
1208
|
def allowed_collections(self) -> list:
|
|
1186
1209
|
return ["orders", "customers"] # whitelist — unlisted collections are rejected
|
|
1187
1210
|
```
|
|
1188
1211
|
|
|
1212
|
+
> **Class discovery:** name your subclass distinctly from the imported base (e.g. `OrdersModelQueryLambda`, not `ModelQueryLambda`). The framework loads the class **defined in the file** — not the imported base — so subclassing an imported concrete base (`ModelQueryLambda`, `ModelIndexSyncLambda`) works without name collisions.
|
|
1213
|
+
|
|
1189
1214
|
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1190
1215
|
|
|
1191
1216
|
```python
|
|
@@ -1333,6 +1358,114 @@ Always pass `self.session.to_dict()` when calling from an API or Lambda to ensur
|
|
|
1333
1358
|
|
|
1334
1359
|
---
|
|
1335
1360
|
|
|
1361
|
+
## 🧱 ModelIndexSyncLambda
|
|
1362
|
+
|
|
1363
|
+
`ModelIndexSyncLambda` is a ready-to-use base class for **creating all of a service's MongoDB indexes deterministically**, in one run. Because index creation is no longer triggered at request time (see the [Repository Pattern](#️-repository-pattern) note), this Lambda is the single source of truth for materializing indexes — typically invoked **once per deploy**.
|
|
1364
|
+
|
|
1365
|
+
**Why:** in short-lived Lambda/API runtimes, creating indexes lazily on first access often never finishes (the function responds and freezes). This Lambda creates every declared index up front, `await`-ing each one, idempotently — and across **every** active state database, not just the one of the current request.
|
|
1366
|
+
|
|
1367
|
+
### Creating an index-sync Lambda
|
|
1368
|
+
|
|
1369
|
+
Create `src/lambda/model-index-sync/main.py` and declare which repositories to sync:
|
|
1370
|
+
|
|
1371
|
+
```python
|
|
1372
|
+
import aws_python_helper
|
|
1373
|
+
|
|
1374
|
+
from repositories.parcels import ParcelsRepository # state-scoped (database_key = None)
|
|
1375
|
+
from repositories.users import UsersRepository # core (database_key = "core")
|
|
1376
|
+
from repositories.tokens import TokensRepository # core
|
|
1377
|
+
|
|
1378
|
+
class OrdersModelIndexSyncLambda(aws_python_helper.ModelIndexSyncLambda):
|
|
1379
|
+
|
|
1380
|
+
@property
|
|
1381
|
+
def repositories(self) -> list:
|
|
1382
|
+
return [ParcelsRepository, UsersRepository, TokensRepository]
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1386
|
+
|
|
1387
|
+
```python
|
|
1388
|
+
from aws_python_helper.lambda_standalone.handler import lambda_handler
|
|
1389
|
+
|
|
1390
|
+
index_sync_handler = lambda_handler('model-index-sync')
|
|
1391
|
+
|
|
1392
|
+
__all__ = ["index_sync_handler"]
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
> Declare repositories **explicitly** (don't auto-discover) so the synced set is intentional. List your own collections; **external** repositories (`is_external = True`) belong to another cluster/service and are typically excluded.
|
|
1396
|
+
|
|
1397
|
+
### How it routes each repository
|
|
1398
|
+
|
|
1399
|
+
For every repository in the list, the Lambda decides where to create its indexes:
|
|
1400
|
+
|
|
1401
|
+
| Repository kind | `database_key` | Target databases |
|
|
1402
|
+
|-----------------|----------------|------------------|
|
|
1403
|
+
| **Core / fixed** | `"core"` (or any string) | that single database |
|
|
1404
|
+
| **State-scoped** | `None` | **every active state**, read from `core.states` where `is_active = true` (e.g. `connecticut`, `new_jersey`, …) |
|
|
1405
|
+
|
|
1406
|
+
A state-scoped collection such as `parcels` therefore gets its indexes created in `connecticut`, `new_jersey`, and any other active state — automatically. Creation is idempotent, so re-running it is safe.
|
|
1407
|
+
|
|
1408
|
+
This Lambda sets `requires_state = False`: it is not tied to a single state and does not require a `session` in the event.
|
|
1409
|
+
|
|
1410
|
+
### Response format
|
|
1411
|
+
|
|
1412
|
+
```json
|
|
1413
|
+
{
|
|
1414
|
+
"success": true,
|
|
1415
|
+
"data": [
|
|
1416
|
+
{"repository": "ParcelsRepository", "database": "connecticut", "collection": "parcels", "indexes": ["parcel_id_1", "..."]},
|
|
1417
|
+
{"repository": "ParcelsRepository", "database": "new_jersey", "collection": "parcels", "indexes": ["parcel_id_1", "..."]},
|
|
1418
|
+
{"repository": "UsersRepository", "database": "core", "collection": "users", "indexes": ["email_1"]}
|
|
1419
|
+
]
|
|
1420
|
+
}
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
### Properties to override / configure
|
|
1424
|
+
|
|
1425
|
+
| Property | Default | Purpose |
|
|
1426
|
+
|----------|---------|---------|
|
|
1427
|
+
| `repositories` | `[]` | **Required.** List of `Repository` subclasses (the classes, not instances) to sync. Empty raises a validation error |
|
|
1428
|
+
| `states_database` | `"core"` | Database holding the states collection |
|
|
1429
|
+
| `states_collection` | `"states"` | Collection listing the states (filtered by `is_active = true`, field `name`) |
|
|
1430
|
+
| `requires_state` | `False` | Inherited override — the Lambda runs across all states, no session required |
|
|
1431
|
+
|
|
1432
|
+
### Running it once per deploy (Terraform)
|
|
1433
|
+
|
|
1434
|
+
Since deploys are applied manually with Terraform, attach an `aws_lambda_invocation` that runs **on every `apply`**, right after the function code is updated. `timestamp()` changes on every plan, so it always re-runs (creation is idempotent and also picks up any newly-activated state). The `postcondition` fails the `apply` if the sync reports `success = false` (the framework handler returns HTTP 200 with `success = false` on internal errors, so this check is required to surface failures):
|
|
1435
|
+
|
|
1436
|
+
```hcl
|
|
1437
|
+
module "lambda" {
|
|
1438
|
+
source = "../../../../../modules/lambda"
|
|
1439
|
+
function_name = "${...service_name}${var.lambda_name}-${...env}"
|
|
1440
|
+
source_handler = "handlers.lambda_handler.index_sync_handler"
|
|
1441
|
+
timeout = 300
|
|
1442
|
+
memory_size = 512
|
|
1443
|
+
# ... mongo + vpc config (same as any other Lambda)
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
resource "aws_lambda_invocation" "sync_on_deploy" {
|
|
1447
|
+
function_name = module.lambda.lambda_function_name
|
|
1448
|
+
input = jsonencode({ _invoked_at = timestamp() }) # always changes → always re-runs
|
|
1449
|
+
|
|
1450
|
+
depends_on = [module.lambda] # run after the code is updated
|
|
1451
|
+
|
|
1452
|
+
lifecycle {
|
|
1453
|
+
postcondition {
|
|
1454
|
+
condition = try(jsondecode(self.result).success, false) == true
|
|
1455
|
+
error_message = "Index sync failed during apply: ${self.result}"
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
You can also invoke it manually at any time (e.g. after activating a new state without a code deploy):
|
|
1462
|
+
|
|
1463
|
+
```bash
|
|
1464
|
+
aws lambda invoke --function-name MyServiceModelIndexSync-dev out.json
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
---
|
|
1468
|
+
|
|
1336
1469
|
## 🏗️ Architecture Overview
|
|
1337
1470
|
|
|
1338
1471
|
Typical flow for event-driven architectures using this framework:
|
|
@@ -19,6 +19,7 @@ Mini-framework to create REST APIs, SQS Consumers, SNS Publishers, Fargate Tasks
|
|
|
19
19
|
- **LambdaInvoker**: Invoke other Lambda functions (sync or async) with built-in error handling
|
|
20
20
|
- **ApiClient**: HTTP client for inter-service communication — resolves service URLs and auth token from environment variables automatically
|
|
21
21
|
- **ModelQueryLambda**: Base class for MongoDB query-proxy Lambdas — declare an `allowed_collections` whitelist and the framework handles `find` and `aggregate` operations generically
|
|
22
|
+
- **ModelIndexSyncLambda**: Base class for index-synchronization Lambdas — declare your `repositories` and the framework creates every declared index across the `core` database and all active state databases, idempotently, in a single run (run once per deploy)
|
|
22
23
|
- **JSON utilities**: Automatic serialization of MongoDB types
|
|
23
24
|
- **Type hints**: Modern Python with type annotations
|
|
24
25
|
- **Async/await**: Full support for asynchronous operations
|
|
@@ -47,6 +48,7 @@ All available classes and functions:
|
|
|
47
48
|
| `fargate_handler` | `aws_python_helper.fargate.handler` | Entry point handler for Fargate |
|
|
48
49
|
| `Repository` | `aws_python_helper.repository.base` | Base class for MongoDB repositories |
|
|
49
50
|
| `ModelQueryLambda` | `aws_python_helper` | Generic MongoDB query-proxy Lambda base class |
|
|
51
|
+
| `ModelIndexSyncLambda` | `aws_python_helper` | Index-sync Lambda base class (creates indexes across `core` + all state DBs) |
|
|
50
52
|
| `LambdaInvoker` | `aws_python_helper` | Invoke Lambda functions (sync/async) with error handling |
|
|
51
53
|
| `ApiClient` | `aws_python_helper` | HTTP client for inter-service communication |
|
|
52
54
|
| `LambdaInvocationError` | `aws_python_helper` | Raised when boto3 fails to invoke a Lambda |
|
|
@@ -57,6 +59,8 @@ All available classes and functions:
|
|
|
57
59
|
| `Session` | `aws_python_helper` | Request-scoped session object (state + user) |
|
|
58
60
|
| `get_session` | `aws_python_helper` | Read the current Session from async context |
|
|
59
61
|
| `set_session` | `aws_python_helper` | Set the current Session in async context |
|
|
62
|
+
| `StateValidator` | `aws_python_helper` | Validates a state against `core.states` (cached, active states only) |
|
|
63
|
+
| `InvalidStateError` | `aws_python_helper` | Raised when a state is not found or not active |
|
|
60
64
|
| `MongoJSONEncoder` | `aws_python_helper.utils.json_encoder` | JSON encoder for MongoDB types |
|
|
61
65
|
| `mongo_json_dumps` | `aws_python_helper.utils.json_encoder` | Helper to serialize MongoDB types |
|
|
62
66
|
| `serialize_mongo_types` | `aws_python_helper.utils.serializer` | Recursively serialize MongoDB types |
|
|
@@ -230,6 +234,15 @@ class GenerateRouteLambda(Lambda):
|
|
|
230
234
|
}
|
|
231
235
|
```
|
|
232
236
|
|
|
237
|
+
> **`requires_state` (session requirement)** — By default (`requires_state = True`) the framework requires the event to carry a `session` with a valid `state` and validates it against `core.states` before running. Override `requires_state` to return `False` for Lambdas that operate across **all** states and must not be tied to a single one (e.g. index synchronization) — the `session`/`state` then becomes optional. See [ModelIndexSyncLambda](#-modelindexsynclambda).
|
|
238
|
+
>
|
|
239
|
+
> ```python
|
|
240
|
+
> class SyncAllStatesLambda(Lambda):
|
|
241
|
+
> @property
|
|
242
|
+
> def requires_state(self) -> bool:
|
|
243
|
+
> return False # no session.state required; runs across every state
|
|
244
|
+
> ```
|
|
245
|
+
|
|
233
246
|
**2. Configure the handler** in `src/handlers/lambda_handler.py`:
|
|
234
247
|
|
|
235
248
|
```python
|
|
@@ -491,7 +504,9 @@ class AddressAPI(API):
|
|
|
491
504
|
|
|
492
505
|
## 🗂️ Repository Pattern
|
|
493
506
|
|
|
494
|
-
The framework provides a `Repository` base class that eliminates repetitive boilerplate in data access layers. Each repository only declares what collection it uses, whether it belongs to an external cluster, and what indexes
|
|
507
|
+
The framework provides a `Repository` base class that eliminates repetitive boilerplate in data access layers. Each repository only declares what collection it uses, whether it belongs to an external cluster, and what indexes it needs. The base class handles the MongoDB connection automatically.
|
|
508
|
+
|
|
509
|
+
> **Index creation is not triggered at request time.** Declaring `indexes` only *describes* the indexes; it does not create them on collection access (this avoids per-request overhead and half-finished index builds in short-lived Lambda/API runtimes). Indexes are created by the [`ModelIndexSyncLambda`](#-modelindexsynclambda) — run once per deploy — or by calling `await repo.ensure_indexes()` explicitly.
|
|
495
510
|
|
|
496
511
|
### Properties to override
|
|
497
512
|
|
|
@@ -522,7 +537,15 @@ def indexes(self):
|
|
|
522
537
|
]
|
|
523
538
|
```
|
|
524
539
|
|
|
525
|
-
|
|
540
|
+
These definitions are consumed by `ensure_indexes()`, which the [`ModelIndexSyncLambda`](#-modelindexsynclambda) runs across the relevant databases at deploy time. `ensure_indexes()` is idempotent (safe to re-run) and accepts an explicit `database_name` so the same state-scoped repository can be materialized in every state database:
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
# Create this repository's indexes in a specific database (what the index-sync Lambda does):
|
|
544
|
+
await repo.ensure_indexes(database_name="connecticut")
|
|
545
|
+
|
|
546
|
+
# Or, with no argument, in the repository's resolved database (database_key or session.state):
|
|
547
|
+
await repo.ensure_indexes()
|
|
548
|
+
```
|
|
526
549
|
|
|
527
550
|
### Repository with a fixed database
|
|
528
551
|
|
|
@@ -649,7 +672,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
649
672
|
| Entry point | How the session is read |
|
|
650
673
|
|-------------|-------------------------|
|
|
651
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 |
|
|
652
|
-
| **Standalone Lambda** | `session` dict in the event payload — **required** (must include `state`), raises `ValueError` if missing |
|
|
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 |
|
|
653
676
|
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
654
677
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
655
678
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
@@ -1159,13 +1182,15 @@ Create `src/lambda/model-query/main.py` in the microservice that owns the data:
|
|
|
1159
1182
|
```python
|
|
1160
1183
|
from aws_python_helper import ModelQueryLambda
|
|
1161
1184
|
|
|
1162
|
-
class
|
|
1185
|
+
class OrdersModelQueryLambda(ModelQueryLambda):
|
|
1163
1186
|
|
|
1164
1187
|
@property
|
|
1165
1188
|
def allowed_collections(self) -> list:
|
|
1166
1189
|
return ["orders", "customers"] # whitelist — unlisted collections are rejected
|
|
1167
1190
|
```
|
|
1168
1191
|
|
|
1192
|
+
> **Class discovery:** name your subclass distinctly from the imported base (e.g. `OrdersModelQueryLambda`, not `ModelQueryLambda`). The framework loads the class **defined in the file** — not the imported base — so subclassing an imported concrete base (`ModelQueryLambda`, `ModelIndexSyncLambda`) works without name collisions.
|
|
1193
|
+
|
|
1169
1194
|
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1170
1195
|
|
|
1171
1196
|
```python
|
|
@@ -1313,6 +1338,114 @@ Always pass `self.session.to_dict()` when calling from an API or Lambda to ensur
|
|
|
1313
1338
|
|
|
1314
1339
|
---
|
|
1315
1340
|
|
|
1341
|
+
## 🧱 ModelIndexSyncLambda
|
|
1342
|
+
|
|
1343
|
+
`ModelIndexSyncLambda` is a ready-to-use base class for **creating all of a service's MongoDB indexes deterministically**, in one run. Because index creation is no longer triggered at request time (see the [Repository Pattern](#️-repository-pattern) note), this Lambda is the single source of truth for materializing indexes — typically invoked **once per deploy**.
|
|
1344
|
+
|
|
1345
|
+
**Why:** in short-lived Lambda/API runtimes, creating indexes lazily on first access often never finishes (the function responds and freezes). This Lambda creates every declared index up front, `await`-ing each one, idempotently — and across **every** active state database, not just the one of the current request.
|
|
1346
|
+
|
|
1347
|
+
### Creating an index-sync Lambda
|
|
1348
|
+
|
|
1349
|
+
Create `src/lambda/model-index-sync/main.py` and declare which repositories to sync:
|
|
1350
|
+
|
|
1351
|
+
```python
|
|
1352
|
+
import aws_python_helper
|
|
1353
|
+
|
|
1354
|
+
from repositories.parcels import ParcelsRepository # state-scoped (database_key = None)
|
|
1355
|
+
from repositories.users import UsersRepository # core (database_key = "core")
|
|
1356
|
+
from repositories.tokens import TokensRepository # core
|
|
1357
|
+
|
|
1358
|
+
class OrdersModelIndexSyncLambda(aws_python_helper.ModelIndexSyncLambda):
|
|
1359
|
+
|
|
1360
|
+
@property
|
|
1361
|
+
def repositories(self) -> list:
|
|
1362
|
+
return [ParcelsRepository, UsersRepository, TokensRepository]
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1366
|
+
|
|
1367
|
+
```python
|
|
1368
|
+
from aws_python_helper.lambda_standalone.handler import lambda_handler
|
|
1369
|
+
|
|
1370
|
+
index_sync_handler = lambda_handler('model-index-sync')
|
|
1371
|
+
|
|
1372
|
+
__all__ = ["index_sync_handler"]
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
> Declare repositories **explicitly** (don't auto-discover) so the synced set is intentional. List your own collections; **external** repositories (`is_external = True`) belong to another cluster/service and are typically excluded.
|
|
1376
|
+
|
|
1377
|
+
### How it routes each repository
|
|
1378
|
+
|
|
1379
|
+
For every repository in the list, the Lambda decides where to create its indexes:
|
|
1380
|
+
|
|
1381
|
+
| Repository kind | `database_key` | Target databases |
|
|
1382
|
+
|-----------------|----------------|------------------|
|
|
1383
|
+
| **Core / fixed** | `"core"` (or any string) | that single database |
|
|
1384
|
+
| **State-scoped** | `None` | **every active state**, read from `core.states` where `is_active = true` (e.g. `connecticut`, `new_jersey`, …) |
|
|
1385
|
+
|
|
1386
|
+
A state-scoped collection such as `parcels` therefore gets its indexes created in `connecticut`, `new_jersey`, and any other active state — automatically. Creation is idempotent, so re-running it is safe.
|
|
1387
|
+
|
|
1388
|
+
This Lambda sets `requires_state = False`: it is not tied to a single state and does not require a `session` in the event.
|
|
1389
|
+
|
|
1390
|
+
### Response format
|
|
1391
|
+
|
|
1392
|
+
```json
|
|
1393
|
+
{
|
|
1394
|
+
"success": true,
|
|
1395
|
+
"data": [
|
|
1396
|
+
{"repository": "ParcelsRepository", "database": "connecticut", "collection": "parcels", "indexes": ["parcel_id_1", "..."]},
|
|
1397
|
+
{"repository": "ParcelsRepository", "database": "new_jersey", "collection": "parcels", "indexes": ["parcel_id_1", "..."]},
|
|
1398
|
+
{"repository": "UsersRepository", "database": "core", "collection": "users", "indexes": ["email_1"]}
|
|
1399
|
+
]
|
|
1400
|
+
}
|
|
1401
|
+
```
|
|
1402
|
+
|
|
1403
|
+
### Properties to override / configure
|
|
1404
|
+
|
|
1405
|
+
| Property | Default | Purpose |
|
|
1406
|
+
|----------|---------|---------|
|
|
1407
|
+
| `repositories` | `[]` | **Required.** List of `Repository` subclasses (the classes, not instances) to sync. Empty raises a validation error |
|
|
1408
|
+
| `states_database` | `"core"` | Database holding the states collection |
|
|
1409
|
+
| `states_collection` | `"states"` | Collection listing the states (filtered by `is_active = true`, field `name`) |
|
|
1410
|
+
| `requires_state` | `False` | Inherited override — the Lambda runs across all states, no session required |
|
|
1411
|
+
|
|
1412
|
+
### Running it once per deploy (Terraform)
|
|
1413
|
+
|
|
1414
|
+
Since deploys are applied manually with Terraform, attach an `aws_lambda_invocation` that runs **on every `apply`**, right after the function code is updated. `timestamp()` changes on every plan, so it always re-runs (creation is idempotent and also picks up any newly-activated state). The `postcondition` fails the `apply` if the sync reports `success = false` (the framework handler returns HTTP 200 with `success = false` on internal errors, so this check is required to surface failures):
|
|
1415
|
+
|
|
1416
|
+
```hcl
|
|
1417
|
+
module "lambda" {
|
|
1418
|
+
source = "../../../../../modules/lambda"
|
|
1419
|
+
function_name = "${...service_name}${var.lambda_name}-${...env}"
|
|
1420
|
+
source_handler = "handlers.lambda_handler.index_sync_handler"
|
|
1421
|
+
timeout = 300
|
|
1422
|
+
memory_size = 512
|
|
1423
|
+
# ... mongo + vpc config (same as any other Lambda)
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
resource "aws_lambda_invocation" "sync_on_deploy" {
|
|
1427
|
+
function_name = module.lambda.lambda_function_name
|
|
1428
|
+
input = jsonencode({ _invoked_at = timestamp() }) # always changes → always re-runs
|
|
1429
|
+
|
|
1430
|
+
depends_on = [module.lambda] # run after the code is updated
|
|
1431
|
+
|
|
1432
|
+
lifecycle {
|
|
1433
|
+
postcondition {
|
|
1434
|
+
condition = try(jsondecode(self.result).success, false) == true
|
|
1435
|
+
error_message = "Index sync failed during apply: ${self.result}"
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
You can also invoke it manually at any time (e.g. after activating a new state without a code deploy):
|
|
1442
|
+
|
|
1443
|
+
```bash
|
|
1444
|
+
aws lambda invoke --function-name MyServiceModelIndexSync-dev out.json
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
---
|
|
1448
|
+
|
|
1316
1449
|
## 🏗️ Architecture Overview
|
|
1317
1450
|
|
|
1318
1451
|
Typical flow for event-driven architectures using this framework:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
AWS Python Framework
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "
|
|
5
|
+
__version__ = "1.0.2"
|
|
6
6
|
|
|
7
7
|
# All classes
|
|
8
8
|
from .api.base import API
|
|
@@ -34,6 +34,9 @@ from .context.state_validator import StateValidator, InvalidStateError
|
|
|
34
34
|
# Model Query
|
|
35
35
|
from .model_query import ModelQueryLambda
|
|
36
36
|
|
|
37
|
+
# Model Index Sync
|
|
38
|
+
from .model_index_sync import ModelIndexSyncLambda
|
|
39
|
+
|
|
37
40
|
# Invoker
|
|
38
41
|
from .invoker import (
|
|
39
42
|
LambdaInvoker,
|
|
@@ -69,6 +72,7 @@ __all__ = [
|
|
|
69
72
|
'StateValidator',
|
|
70
73
|
'InvalidStateError',
|
|
71
74
|
'ModelQueryLambda',
|
|
75
|
+
'ModelIndexSyncLambda',
|
|
72
76
|
'LambdaInvoker',
|
|
73
77
|
'ApiClient',
|
|
74
78
|
'LambdaInvocationError',
|
|
@@ -190,9 +190,10 @@ class Fetcher:
|
|
|
190
190
|
controller_class = None
|
|
191
191
|
for item_name in dir(module):
|
|
192
192
|
item = getattr(module, item_name)
|
|
193
|
-
if (isinstance(item, type) and
|
|
194
|
-
hasattr(item, 'process') and
|
|
195
|
-
item.__name__ not in ['API', 'ABC']
|
|
193
|
+
if (isinstance(item, type) and
|
|
194
|
+
hasattr(item, 'process') and
|
|
195
|
+
item.__name__ not in ['API', 'ABC'] and
|
|
196
|
+
item.__module__ == module.__name__):
|
|
196
197
|
controller_class = item
|
|
197
198
|
break
|
|
198
199
|
|
|
@@ -98,9 +98,10 @@ class FargateTaskFetcher:
|
|
|
98
98
|
task_class = None
|
|
99
99
|
for item_name in dir(module):
|
|
100
100
|
item = getattr(module, item_name)
|
|
101
|
-
if (isinstance(item, type) and
|
|
102
|
-
hasattr(item, 'execute') and
|
|
103
|
-
item.__name__ not in ['FargateTask', 'ABC']
|
|
101
|
+
if (isinstance(item, type) and
|
|
102
|
+
hasattr(item, 'execute') and
|
|
103
|
+
item.__name__ not in ['FargateTask', 'ABC'] and
|
|
104
|
+
item.__module__ == module.__name__):
|
|
104
105
|
task_class = item
|
|
105
106
|
break
|
|
106
107
|
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/base.py
RENAMED
|
@@ -47,6 +47,21 @@ class Lambda(ABC):
|
|
|
47
47
|
"""
|
|
48
48
|
return get_session()
|
|
49
49
|
|
|
50
|
+
@property
|
|
51
|
+
def requires_state(self) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Whether this Lambda requires a valid 'state' in the session.
|
|
54
|
+
|
|
55
|
+
Default True: run() enforces that the event carries session.state and
|
|
56
|
+
validates it against core.states before processing.
|
|
57
|
+
|
|
58
|
+
Override to return False for Lambdas that operate across all states and
|
|
59
|
+
must not be tied to a single one (e.g. index synchronization). When
|
|
60
|
+
False, a session is still set if provided, but state is not required
|
|
61
|
+
nor validated.
|
|
62
|
+
"""
|
|
63
|
+
return True
|
|
64
|
+
|
|
50
65
|
@property
|
|
51
66
|
def schema(self):
|
|
52
67
|
"""
|
|
@@ -188,11 +203,15 @@ class Lambda(ABC):
|
|
|
188
203
|
try:
|
|
189
204
|
# Step 0: Setup session context
|
|
190
205
|
session_data = self.event.get('session')
|
|
191
|
-
if
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
if self.requires_state:
|
|
207
|
+
if not session_data or not session_data.get('state'):
|
|
208
|
+
raise ValueError("'session' with 'state' is required in the event")
|
|
209
|
+
session = Session.from_dict(session_data)
|
|
210
|
+
await StateValidator.validate(session.state)
|
|
211
|
+
set_session(session)
|
|
212
|
+
elif session_data:
|
|
213
|
+
# state not required, but honor a session if one was provided
|
|
214
|
+
set_session(Session.from_dict(session_data))
|
|
196
215
|
|
|
197
216
|
# Step 1: Schema validation (automatic, if schema property is defined)
|
|
198
217
|
if self.schema is not None:
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/fetcher.py
RENAMED
|
@@ -100,9 +100,10 @@ class LambdaFetcher:
|
|
|
100
100
|
lambda_class = None
|
|
101
101
|
for item_name in dir(module):
|
|
102
102
|
item = getattr(module, item_name)
|
|
103
|
-
if (isinstance(item, type) and
|
|
104
|
-
issubclass(item, Lambda) and
|
|
105
|
-
item.__name__ not in ['Lambda', 'ABC']
|
|
103
|
+
if (isinstance(item, type) and
|
|
104
|
+
issubclass(item, Lambda) and
|
|
105
|
+
item.__name__ not in ['Lambda', 'ABC'] and
|
|
106
|
+
item.__module__ == module.__name__):
|
|
106
107
|
lambda_class = item
|
|
107
108
|
break
|
|
108
109
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ModelIndexSyncLambda - Synchronizes MongoDB indexes for a service's repositories.
|
|
3
|
+
|
|
4
|
+
Creates (idempotently) every index declared in the service's repositories, in
|
|
5
|
+
the correct databases:
|
|
6
|
+
- Repositories with a fixed database_key (e.g. 'core') → that database.
|
|
7
|
+
- State-scoped repositories (database_key is None) → every active state
|
|
8
|
+
database, read from core.states (is_active=True). A state-scoped collection
|
|
9
|
+
(e.g. 'parcels') therefore gets its indexes created in 'connecticut',
|
|
10
|
+
'new_jersey', etc.
|
|
11
|
+
|
|
12
|
+
Designed to run once per deploy (and on demand), guaranteeing index creation
|
|
13
|
+
without the per-request overhead of creating indexes at runtime.
|
|
14
|
+
|
|
15
|
+
Usage — subclass and declare the repositories to sync:
|
|
16
|
+
|
|
17
|
+
import aws_python_helper
|
|
18
|
+
from repositories.parcels import ParcelsRepository
|
|
19
|
+
from repositories.users import UsersRepository
|
|
20
|
+
|
|
21
|
+
class PropertiesModelIndexSyncLambda(aws_python_helper.ModelIndexSyncLambda):
|
|
22
|
+
@property
|
|
23
|
+
def repositories(self) -> list:
|
|
24
|
+
return [ParcelsRepository, UsersRepository]
|
|
25
|
+
|
|
26
|
+
This Lambda does not require a session state (requires_state = False); it
|
|
27
|
+
operates across all active states.
|
|
28
|
+
|
|
29
|
+
Response (via Lambda base run()):
|
|
30
|
+
{"success": True, "data": [
|
|
31
|
+
{"repository": "ParcelsRepository", "database": "connecticut",
|
|
32
|
+
"collection": "parcels", "indexes": ["parcel_id_1", ...]},
|
|
33
|
+
{"repository": "UsersRepository", "database": "core",
|
|
34
|
+
"collection": "users", "indexes": ["email_1"]},
|
|
35
|
+
...
|
|
36
|
+
]}
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from typing import Any, List, Type
|
|
40
|
+
|
|
41
|
+
from ..lambda_standalone.base import Lambda
|
|
42
|
+
from ..repository.base import Repository
|
|
43
|
+
from ..database.mongo_manager import MongoManager
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ModelIndexSyncLambda(Lambda):
|
|
47
|
+
"""
|
|
48
|
+
Base class for index-synchronization Lambdas.
|
|
49
|
+
|
|
50
|
+
Subclass and override `repositories` to declare which repositories to sync.
|
|
51
|
+
Everything else (resolving core vs state-scoped, reading active states and
|
|
52
|
+
creating the indexes) is handled automatically.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def requires_state(self) -> bool:
|
|
57
|
+
# Operates across all states; not tied to a single one.
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def repositories(self) -> List[Type[Repository]]:
|
|
62
|
+
"""
|
|
63
|
+
Repositories whose indexes should be synchronized.
|
|
64
|
+
|
|
65
|
+
Override returning a list of Repository subclasses (the classes, not
|
|
66
|
+
instances). Declare them explicitly rather than auto-discovering, so the
|
|
67
|
+
synced set is intentional and nothing is silently missed or added.
|
|
68
|
+
"""
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def states_database(self) -> str:
|
|
73
|
+
"""Database holding the states collection. Default: 'core'."""
|
|
74
|
+
return "core"
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def states_collection(self) -> str:
|
|
78
|
+
"""Collection listing the states. Default: 'states'."""
|
|
79
|
+
return "states"
|
|
80
|
+
|
|
81
|
+
async def validate(self):
|
|
82
|
+
if not self.repositories:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"repositories is not configured on this ModelIndexSyncLambda"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
async def _active_states(self) -> List[str]:
|
|
88
|
+
"""Names of active states — i.e. the state-scoped database names."""
|
|
89
|
+
db = MongoManager.get_database(self.states_database)
|
|
90
|
+
cursor = db[self.states_collection].find(
|
|
91
|
+
{"is_active": True}, {"name": 1, "_id": 0}
|
|
92
|
+
)
|
|
93
|
+
return [doc["name"] async for doc in cursor if doc.get("name")]
|
|
94
|
+
|
|
95
|
+
async def process(self) -> Any:
|
|
96
|
+
active_states = await self._active_states()
|
|
97
|
+
report: List[dict] = []
|
|
98
|
+
|
|
99
|
+
for repo_cls in self.repositories:
|
|
100
|
+
repo = repo_cls()
|
|
101
|
+
# database_key is None -> state-scoped (collection repeats per state db).
|
|
102
|
+
is_state_scoped = repo.database_key is None and not repo.is_external
|
|
103
|
+
target_databases = active_states if is_state_scoped else [repo.database_name]
|
|
104
|
+
|
|
105
|
+
for db_name in target_databases:
|
|
106
|
+
created = await repo.ensure_indexes(database_name=db_name)
|
|
107
|
+
report.append({
|
|
108
|
+
"repository": repo_cls.__name__,
|
|
109
|
+
"database": db_name,
|
|
110
|
+
"collection": repo.collection_name,
|
|
111
|
+
"indexes": created,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return report
|
|
@@ -6,7 +6,6 @@ collection access, and index creation without requiring the user to
|
|
|
6
6
|
pass a database connection or call any initialization method.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import asyncio
|
|
10
9
|
import logging
|
|
11
10
|
from abc import ABC, abstractmethod
|
|
12
11
|
from typing import Any, Dict, List, Optional, Tuple
|
|
@@ -183,8 +182,9 @@ class Repository(ABC):
|
|
|
183
182
|
so state-scoped repositories resolve the correct collection for each
|
|
184
183
|
request state without cross-contamination between requests.
|
|
185
184
|
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
Index creation is NOT triggered here: indexes are created exclusively
|
|
186
|
+
by the index-sync Lambda (see ensure_indexes), so request-time access
|
|
187
|
+
carries no index-creation overhead.
|
|
188
188
|
"""
|
|
189
189
|
key = (self.database_name, self.collection_name)
|
|
190
190
|
|
|
@@ -200,29 +200,45 @@ class Repository(ABC):
|
|
|
200
200
|
|
|
201
201
|
self._collection_cache[key] = db[self.collection_name]
|
|
202
202
|
|
|
203
|
-
# Schedule index creation as a background task on the running event loop.
|
|
204
|
-
if self.indexes and not self._indexes_created.get(key):
|
|
205
|
-
try:
|
|
206
|
-
asyncio.get_running_loop().create_task(self.ensure_indexes())
|
|
207
|
-
except RuntimeError:
|
|
208
|
-
pass # No running event loop (e.g. synchronous test context)
|
|
209
|
-
|
|
210
203
|
return self._collection_cache[key]
|
|
211
204
|
|
|
212
|
-
async def ensure_indexes(self):
|
|
205
|
+
async def ensure_indexes(self, database_name: Optional[str] = None):
|
|
213
206
|
"""
|
|
214
|
-
Creates all indexes defined in the `indexes` property.
|
|
207
|
+
Creates all indexes defined in the `indexes` property, blocking until done.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
database_name: Target database to create the indexes in.
|
|
211
|
+
- If None (default): uses the resolved database_name
|
|
212
|
+
(database_key or the current session state).
|
|
213
|
+
- If set: creates the indexes in that specific database. This is
|
|
214
|
+
used by the index-sync Lambda to create the indexes of a
|
|
215
|
+
state-scoped repository across every active state database
|
|
216
|
+
(e.g. 'connecticut', 'new_jersey', ...).
|
|
217
|
+
|
|
218
|
+
Index creation is no longer triggered automatically at request time;
|
|
219
|
+
call this explicitly (the index-sync Lambda does) to guarantee creation.
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
Can also be called explicitly at the start of a method when index
|
|
218
|
-
creation must be guaranteed to complete before proceeding.
|
|
221
|
+
Idempotent per (database, collection): safe to call multiple times.
|
|
219
222
|
|
|
220
|
-
|
|
223
|
+
Returns:
|
|
224
|
+
List of created index names (as returned by create_index).
|
|
221
225
|
"""
|
|
222
|
-
|
|
226
|
+
db_name = database_name or self.database_name
|
|
227
|
+
key = (db_name, self.collection_name)
|
|
223
228
|
if self._indexes_created.get(key):
|
|
224
|
-
return
|
|
225
|
-
|
|
229
|
+
return []
|
|
230
|
+
|
|
231
|
+
if self.is_external:
|
|
232
|
+
if not self.cluster_name:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"{self.__class__.__name__}: 'cluster_name' is required when is_external=True"
|
|
235
|
+
)
|
|
236
|
+
db = ExternalMongoManager.get_database(self.cluster_name, db_name)
|
|
237
|
+
else:
|
|
238
|
+
db = MongoManager.get_database(db_name)
|
|
239
|
+
collection = db[self.collection_name]
|
|
240
|
+
|
|
241
|
+
created: List[str] = []
|
|
226
242
|
for index_def in self.indexes:
|
|
227
243
|
index_key = index_def.get("key")
|
|
228
244
|
if not index_key:
|
|
@@ -231,9 +247,11 @@ class Repository(ABC):
|
|
|
231
247
|
options = {k: v for k, v in index_def.items() if k != "key"}
|
|
232
248
|
options.setdefault("background", True)
|
|
233
249
|
try:
|
|
234
|
-
await
|
|
235
|
-
|
|
250
|
+
name = await collection.create_index(index_key, **options)
|
|
251
|
+
created.append(name)
|
|
252
|
+
self.logger.debug(f"Index created on '{db_name}.{self.collection_name}': {index_key}")
|
|
236
253
|
except Exception as e:
|
|
237
|
-
self.logger.error(f"Error creating index {index_key}: {e}")
|
|
254
|
+
self.logger.error(f"Error creating index {index_key} on '{db_name}': {e}")
|
|
238
255
|
|
|
239
256
|
self._indexes_created[key] = True
|
|
257
|
+
return created
|
|
@@ -91,9 +91,10 @@ class SQSFetcher:
|
|
|
91
91
|
consumer_class = None
|
|
92
92
|
for item_name in dir(module):
|
|
93
93
|
item = getattr(module, item_name)
|
|
94
|
-
if (isinstance(item, type) and
|
|
95
|
-
hasattr(item, 'process_record') and
|
|
96
|
-
item.__name__ not in ['SQSConsumer', 'ABC']
|
|
94
|
+
if (isinstance(item, type) and
|
|
95
|
+
hasattr(item, 'process_record') and
|
|
96
|
+
item.__name__ not in ['SQSConsumer', 'ABC'] and
|
|
97
|
+
item.__module__ == module.__name__):
|
|
97
98
|
consumer_class = item
|
|
98
99
|
break
|
|
99
100
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-python-helper
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,6 +39,7 @@ Mini-framework to create REST APIs, SQS Consumers, SNS Publishers, Fargate Tasks
|
|
|
39
39
|
- **LambdaInvoker**: Invoke other Lambda functions (sync or async) with built-in error handling
|
|
40
40
|
- **ApiClient**: HTTP client for inter-service communication — resolves service URLs and auth token from environment variables automatically
|
|
41
41
|
- **ModelQueryLambda**: Base class for MongoDB query-proxy Lambdas — declare an `allowed_collections` whitelist and the framework handles `find` and `aggregate` operations generically
|
|
42
|
+
- **ModelIndexSyncLambda**: Base class for index-synchronization Lambdas — declare your `repositories` and the framework creates every declared index across the `core` database and all active state databases, idempotently, in a single run (run once per deploy)
|
|
42
43
|
- **JSON utilities**: Automatic serialization of MongoDB types
|
|
43
44
|
- **Type hints**: Modern Python with type annotations
|
|
44
45
|
- **Async/await**: Full support for asynchronous operations
|
|
@@ -67,6 +68,7 @@ All available classes and functions:
|
|
|
67
68
|
| `fargate_handler` | `aws_python_helper.fargate.handler` | Entry point handler for Fargate |
|
|
68
69
|
| `Repository` | `aws_python_helper.repository.base` | Base class for MongoDB repositories |
|
|
69
70
|
| `ModelQueryLambda` | `aws_python_helper` | Generic MongoDB query-proxy Lambda base class |
|
|
71
|
+
| `ModelIndexSyncLambda` | `aws_python_helper` | Index-sync Lambda base class (creates indexes across `core` + all state DBs) |
|
|
70
72
|
| `LambdaInvoker` | `aws_python_helper` | Invoke Lambda functions (sync/async) with error handling |
|
|
71
73
|
| `ApiClient` | `aws_python_helper` | HTTP client for inter-service communication |
|
|
72
74
|
| `LambdaInvocationError` | `aws_python_helper` | Raised when boto3 fails to invoke a Lambda |
|
|
@@ -77,6 +79,8 @@ All available classes and functions:
|
|
|
77
79
|
| `Session` | `aws_python_helper` | Request-scoped session object (state + user) |
|
|
78
80
|
| `get_session` | `aws_python_helper` | Read the current Session from async context |
|
|
79
81
|
| `set_session` | `aws_python_helper` | Set the current Session in async context |
|
|
82
|
+
| `StateValidator` | `aws_python_helper` | Validates a state against `core.states` (cached, active states only) |
|
|
83
|
+
| `InvalidStateError` | `aws_python_helper` | Raised when a state is not found or not active |
|
|
80
84
|
| `MongoJSONEncoder` | `aws_python_helper.utils.json_encoder` | JSON encoder for MongoDB types |
|
|
81
85
|
| `mongo_json_dumps` | `aws_python_helper.utils.json_encoder` | Helper to serialize MongoDB types |
|
|
82
86
|
| `serialize_mongo_types` | `aws_python_helper.utils.serializer` | Recursively serialize MongoDB types |
|
|
@@ -250,6 +254,15 @@ class GenerateRouteLambda(Lambda):
|
|
|
250
254
|
}
|
|
251
255
|
```
|
|
252
256
|
|
|
257
|
+
> **`requires_state` (session requirement)** — By default (`requires_state = True`) the framework requires the event to carry a `session` with a valid `state` and validates it against `core.states` before running. Override `requires_state` to return `False` for Lambdas that operate across **all** states and must not be tied to a single one (e.g. index synchronization) — the `session`/`state` then becomes optional. See [ModelIndexSyncLambda](#-modelindexsynclambda).
|
|
258
|
+
>
|
|
259
|
+
> ```python
|
|
260
|
+
> class SyncAllStatesLambda(Lambda):
|
|
261
|
+
> @property
|
|
262
|
+
> def requires_state(self) -> bool:
|
|
263
|
+
> return False # no session.state required; runs across every state
|
|
264
|
+
> ```
|
|
265
|
+
|
|
253
266
|
**2. Configure the handler** in `src/handlers/lambda_handler.py`:
|
|
254
267
|
|
|
255
268
|
```python
|
|
@@ -511,7 +524,9 @@ class AddressAPI(API):
|
|
|
511
524
|
|
|
512
525
|
## 🗂️ Repository Pattern
|
|
513
526
|
|
|
514
|
-
The framework provides a `Repository` base class that eliminates repetitive boilerplate in data access layers. Each repository only declares what collection it uses, whether it belongs to an external cluster, and what indexes
|
|
527
|
+
The framework provides a `Repository` base class that eliminates repetitive boilerplate in data access layers. Each repository only declares what collection it uses, whether it belongs to an external cluster, and what indexes it needs. The base class handles the MongoDB connection automatically.
|
|
528
|
+
|
|
529
|
+
> **Index creation is not triggered at request time.** Declaring `indexes` only *describes* the indexes; it does not create them on collection access (this avoids per-request overhead and half-finished index builds in short-lived Lambda/API runtimes). Indexes are created by the [`ModelIndexSyncLambda`](#-modelindexsynclambda) — run once per deploy — or by calling `await repo.ensure_indexes()` explicitly.
|
|
515
530
|
|
|
516
531
|
### Properties to override
|
|
517
532
|
|
|
@@ -542,7 +557,15 @@ def indexes(self):
|
|
|
542
557
|
]
|
|
543
558
|
```
|
|
544
559
|
|
|
545
|
-
|
|
560
|
+
These definitions are consumed by `ensure_indexes()`, which the [`ModelIndexSyncLambda`](#-modelindexsynclambda) runs across the relevant databases at deploy time. `ensure_indexes()` is idempotent (safe to re-run) and accepts an explicit `database_name` so the same state-scoped repository can be materialized in every state database:
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
# Create this repository's indexes in a specific database (what the index-sync Lambda does):
|
|
564
|
+
await repo.ensure_indexes(database_name="connecticut")
|
|
565
|
+
|
|
566
|
+
# Or, with no argument, in the repository's resolved database (database_key or session.state):
|
|
567
|
+
await repo.ensure_indexes()
|
|
568
|
+
```
|
|
546
569
|
|
|
547
570
|
### Repository with a fixed database
|
|
548
571
|
|
|
@@ -669,7 +692,7 @@ The framework propagates a `Session` object automatically across the entire asyn
|
|
|
669
692
|
| Entry point | How the session is read |
|
|
670
693
|
|-------------|-------------------------|
|
|
671
694
|
| **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 |
|
|
672
|
-
| **Standalone Lambda** | `session` dict in the event payload — **required** (must include `state`), raises `ValueError` if missing |
|
|
695
|
+
| **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 |
|
|
673
696
|
| **SQS Consumer (single mode)** | Per-record: reads `session` from SNS `MessageAttributes` (Base64-encoded JSON) |
|
|
674
697
|
| **SQS Consumer (batch mode)** | Groups records by `session.state`; calls `process_batch()` once per group with the correct session in context |
|
|
675
698
|
| **Fargate Task** | `SESSION` env var (JSON) — auto-injected by `FargateExecutor` |
|
|
@@ -1179,13 +1202,15 @@ Create `src/lambda/model-query/main.py` in the microservice that owns the data:
|
|
|
1179
1202
|
```python
|
|
1180
1203
|
from aws_python_helper import ModelQueryLambda
|
|
1181
1204
|
|
|
1182
|
-
class
|
|
1205
|
+
class OrdersModelQueryLambda(ModelQueryLambda):
|
|
1183
1206
|
|
|
1184
1207
|
@property
|
|
1185
1208
|
def allowed_collections(self) -> list:
|
|
1186
1209
|
return ["orders", "customers"] # whitelist — unlisted collections are rejected
|
|
1187
1210
|
```
|
|
1188
1211
|
|
|
1212
|
+
> **Class discovery:** name your subclass distinctly from the imported base (e.g. `OrdersModelQueryLambda`, not `ModelQueryLambda`). The framework loads the class **defined in the file** — not the imported base — so subclassing an imported concrete base (`ModelQueryLambda`, `ModelIndexSyncLambda`) works without name collisions.
|
|
1213
|
+
|
|
1189
1214
|
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1190
1215
|
|
|
1191
1216
|
```python
|
|
@@ -1333,6 +1358,114 @@ Always pass `self.session.to_dict()` when calling from an API or Lambda to ensur
|
|
|
1333
1358
|
|
|
1334
1359
|
---
|
|
1335
1360
|
|
|
1361
|
+
## 🧱 ModelIndexSyncLambda
|
|
1362
|
+
|
|
1363
|
+
`ModelIndexSyncLambda` is a ready-to-use base class for **creating all of a service's MongoDB indexes deterministically**, in one run. Because index creation is no longer triggered at request time (see the [Repository Pattern](#️-repository-pattern) note), this Lambda is the single source of truth for materializing indexes — typically invoked **once per deploy**.
|
|
1364
|
+
|
|
1365
|
+
**Why:** in short-lived Lambda/API runtimes, creating indexes lazily on first access often never finishes (the function responds and freezes). This Lambda creates every declared index up front, `await`-ing each one, idempotently — and across **every** active state database, not just the one of the current request.
|
|
1366
|
+
|
|
1367
|
+
### Creating an index-sync Lambda
|
|
1368
|
+
|
|
1369
|
+
Create `src/lambda/model-index-sync/main.py` and declare which repositories to sync:
|
|
1370
|
+
|
|
1371
|
+
```python
|
|
1372
|
+
import aws_python_helper
|
|
1373
|
+
|
|
1374
|
+
from repositories.parcels import ParcelsRepository # state-scoped (database_key = None)
|
|
1375
|
+
from repositories.users import UsersRepository # core (database_key = "core")
|
|
1376
|
+
from repositories.tokens import TokensRepository # core
|
|
1377
|
+
|
|
1378
|
+
class OrdersModelIndexSyncLambda(aws_python_helper.ModelIndexSyncLambda):
|
|
1379
|
+
|
|
1380
|
+
@property
|
|
1381
|
+
def repositories(self) -> list:
|
|
1382
|
+
return [ParcelsRepository, UsersRepository, TokensRepository]
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1386
|
+
|
|
1387
|
+
```python
|
|
1388
|
+
from aws_python_helper.lambda_standalone.handler import lambda_handler
|
|
1389
|
+
|
|
1390
|
+
index_sync_handler = lambda_handler('model-index-sync')
|
|
1391
|
+
|
|
1392
|
+
__all__ = ["index_sync_handler"]
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
> Declare repositories **explicitly** (don't auto-discover) so the synced set is intentional. List your own collections; **external** repositories (`is_external = True`) belong to another cluster/service and are typically excluded.
|
|
1396
|
+
|
|
1397
|
+
### How it routes each repository
|
|
1398
|
+
|
|
1399
|
+
For every repository in the list, the Lambda decides where to create its indexes:
|
|
1400
|
+
|
|
1401
|
+
| Repository kind | `database_key` | Target databases |
|
|
1402
|
+
|-----------------|----------------|------------------|
|
|
1403
|
+
| **Core / fixed** | `"core"` (or any string) | that single database |
|
|
1404
|
+
| **State-scoped** | `None` | **every active state**, read from `core.states` where `is_active = true` (e.g. `connecticut`, `new_jersey`, …) |
|
|
1405
|
+
|
|
1406
|
+
A state-scoped collection such as `parcels` therefore gets its indexes created in `connecticut`, `new_jersey`, and any other active state — automatically. Creation is idempotent, so re-running it is safe.
|
|
1407
|
+
|
|
1408
|
+
This Lambda sets `requires_state = False`: it is not tied to a single state and does not require a `session` in the event.
|
|
1409
|
+
|
|
1410
|
+
### Response format
|
|
1411
|
+
|
|
1412
|
+
```json
|
|
1413
|
+
{
|
|
1414
|
+
"success": true,
|
|
1415
|
+
"data": [
|
|
1416
|
+
{"repository": "ParcelsRepository", "database": "connecticut", "collection": "parcels", "indexes": ["parcel_id_1", "..."]},
|
|
1417
|
+
{"repository": "ParcelsRepository", "database": "new_jersey", "collection": "parcels", "indexes": ["parcel_id_1", "..."]},
|
|
1418
|
+
{"repository": "UsersRepository", "database": "core", "collection": "users", "indexes": ["email_1"]}
|
|
1419
|
+
]
|
|
1420
|
+
}
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
### Properties to override / configure
|
|
1424
|
+
|
|
1425
|
+
| Property | Default | Purpose |
|
|
1426
|
+
|----------|---------|---------|
|
|
1427
|
+
| `repositories` | `[]` | **Required.** List of `Repository` subclasses (the classes, not instances) to sync. Empty raises a validation error |
|
|
1428
|
+
| `states_database` | `"core"` | Database holding the states collection |
|
|
1429
|
+
| `states_collection` | `"states"` | Collection listing the states (filtered by `is_active = true`, field `name`) |
|
|
1430
|
+
| `requires_state` | `False` | Inherited override — the Lambda runs across all states, no session required |
|
|
1431
|
+
|
|
1432
|
+
### Running it once per deploy (Terraform)
|
|
1433
|
+
|
|
1434
|
+
Since deploys are applied manually with Terraform, attach an `aws_lambda_invocation` that runs **on every `apply`**, right after the function code is updated. `timestamp()` changes on every plan, so it always re-runs (creation is idempotent and also picks up any newly-activated state). The `postcondition` fails the `apply` if the sync reports `success = false` (the framework handler returns HTTP 200 with `success = false` on internal errors, so this check is required to surface failures):
|
|
1435
|
+
|
|
1436
|
+
```hcl
|
|
1437
|
+
module "lambda" {
|
|
1438
|
+
source = "../../../../../modules/lambda"
|
|
1439
|
+
function_name = "${...service_name}${var.lambda_name}-${...env}"
|
|
1440
|
+
source_handler = "handlers.lambda_handler.index_sync_handler"
|
|
1441
|
+
timeout = 300
|
|
1442
|
+
memory_size = 512
|
|
1443
|
+
# ... mongo + vpc config (same as any other Lambda)
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
resource "aws_lambda_invocation" "sync_on_deploy" {
|
|
1447
|
+
function_name = module.lambda.lambda_function_name
|
|
1448
|
+
input = jsonencode({ _invoked_at = timestamp() }) # always changes → always re-runs
|
|
1449
|
+
|
|
1450
|
+
depends_on = [module.lambda] # run after the code is updated
|
|
1451
|
+
|
|
1452
|
+
lifecycle {
|
|
1453
|
+
postcondition {
|
|
1454
|
+
condition = try(jsondecode(self.result).success, false) == true
|
|
1455
|
+
error_message = "Index sync failed during apply: ${self.result}"
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
You can also invoke it manually at any time (e.g. after activating a new state without a code deploy):
|
|
1462
|
+
|
|
1463
|
+
```bash
|
|
1464
|
+
aws lambda invoke --function-name MyServiceModelIndexSync-dev out.json
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
---
|
|
1468
|
+
|
|
1336
1469
|
## 🏗️ Architecture Overview
|
|
1337
1470
|
|
|
1338
1471
|
Typical flow for event-driven architectures using this framework:
|
|
@@ -35,6 +35,8 @@ aws_python_helper/lambda_standalone/__init__.py
|
|
|
35
35
|
aws_python_helper/lambda_standalone/base.py
|
|
36
36
|
aws_python_helper/lambda_standalone/fetcher.py
|
|
37
37
|
aws_python_helper/lambda_standalone/handler.py
|
|
38
|
+
aws_python_helper/model_index_sync/__init__.py
|
|
39
|
+
aws_python_helper/model_index_sync/base.py
|
|
38
40
|
aws_python_helper/model_query/__init__.py
|
|
39
41
|
aws_python_helper/model_query/base.py
|
|
40
42
|
aws_python_helper/repository/__init__.py
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/api/auth_middleware.py
RENAMED
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/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
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/context/state_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/database_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/database/mongo_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/invoker/lambda_invoker.py
RENAMED
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/__init__.py
RENAMED
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/lambda_standalone/handler.py
RENAMED
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper/model_query/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.0.0 → aws_python_helper-1.0.2}/aws_python_helper.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|