aws-python-helper 1.0.0__tar.gz → 1.0.3__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.3}/PKG-INFO +156 -5
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/README.md +155 -4
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/__init__.py +5 -1
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/fetcher.py +4 -3
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/fargate/fetcher.py +4 -3
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/lambda_standalone/base.py +24 -5
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/lambda_standalone/fetcher.py +4 -3
- aws_python_helper-1.0.3/aws_python_helper/model_index_sync/__init__.py +3 -0
- aws_python_helper-1.0.3/aws_python_helper/model_index_sync/base.py +191 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/repository/base.py +108 -22
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/sqs/fetcher.py +4 -3
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper.egg-info/PKG-INFO +156 -5
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper.egg-info/SOURCES.txt +2 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/pyproject.toml +1 -1
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/auth_middleware.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/auth_validators.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/dispatcher.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/exceptions.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/api/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/context/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/context/session.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/context/state_validator.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/database/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/database/database_proxy.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/database/external_database_proxy.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/database/external_mongo_manager.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/database/mongo_manager.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/fargate/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/fargate/executor.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/fargate/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/fargate/task_base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/invoker/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/invoker/api_client.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/invoker/exceptions.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/invoker/lambda_invoker.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/lambda_standalone/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/lambda_standalone/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/model_query/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/model_query/base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/repository/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/sns/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/sns/publisher.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/sqs/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/sqs/consumer_base.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/sqs/handler.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/utils/__init__.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/utils/json_encoder.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/utils/response.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper/utils/serializer.py +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper.egg-info/dependency_links.txt +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper.egg-info/requires.txt +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/aws_python_helper.egg-info/top_level.txt +0 -0
- {aws_python_helper-1.0.0 → aws_python_helper-1.0.3}/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.3
|
|
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 the declared indexes **and prunes orphans** across the `core` database and all active state databases, idempotently and in parallel, 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 declared indexes + prunes orphans across `core` + all state DBs, in parallel) |
|
|
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,132 @@ 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 **synchronizing 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
|
+
It does a **two-way sync** per collection: it creates the declared indexes **and prunes orphans** — any index present in MongoDB that is no longer declared in the repository's `indexes` is dropped (the default `_id_` index and special text/geo/hashed indexes are always preserved).
|
|
1368
|
+
|
|
1369
|
+
> ⚠️ **Orphan pruning drops indexes not declared in code.** Indexes created out-of-band (by a DBA, Atlas, or another tool) that aren't in any repository's `indexes` will be removed on every run. Declare every index you want to keep.
|
|
1370
|
+
|
|
1371
|
+
**Concurrency & fault tolerance:** each `(collection, database)` unit is synced **in parallel** (bounded by `max_concurrency`, default 10); index work *within* a unit stays sequential. A failure in one unit is **captured and isolated** — it never aborts the others, so a single broken collection doesn't stop the rest of the sync. The top-level response is always `success: true`; per-unit outcomes are reported in `results[].success`.
|
|
1372
|
+
|
|
1373
|
+
### Creating an index-sync Lambda
|
|
1374
|
+
|
|
1375
|
+
Create `src/lambda/model-index-sync/main.py` and declare which repositories to sync:
|
|
1376
|
+
|
|
1377
|
+
```python
|
|
1378
|
+
import aws_python_helper
|
|
1379
|
+
|
|
1380
|
+
from repositories.parcels import ParcelsRepository # state-scoped (database_key = None)
|
|
1381
|
+
from repositories.users import UsersRepository # core (database_key = "core")
|
|
1382
|
+
from repositories.tokens import TokensRepository # core
|
|
1383
|
+
|
|
1384
|
+
class OrdersModelIndexSyncLambda(aws_python_helper.ModelIndexSyncLambda):
|
|
1385
|
+
|
|
1386
|
+
@property
|
|
1387
|
+
def repositories(self) -> list:
|
|
1388
|
+
return [ParcelsRepository, UsersRepository, TokensRepository]
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1392
|
+
|
|
1393
|
+
```python
|
|
1394
|
+
from aws_python_helper.lambda_standalone.handler import lambda_handler
|
|
1395
|
+
|
|
1396
|
+
index_sync_handler = lambda_handler('model-index-sync')
|
|
1397
|
+
|
|
1398
|
+
__all__ = ["index_sync_handler"]
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
> 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.
|
|
1402
|
+
|
|
1403
|
+
### How it routes each repository
|
|
1404
|
+
|
|
1405
|
+
For every repository in the list, the Lambda decides where to create its indexes:
|
|
1406
|
+
|
|
1407
|
+
| Repository kind | `database_key` | Target databases |
|
|
1408
|
+
|-----------------|----------------|------------------|
|
|
1409
|
+
| **Core / fixed** | `"core"` (or any string) | that single database |
|
|
1410
|
+
| **State-scoped** | `None` | **every active state**, read from `core.states` where `is_active = true` (e.g. `connecticut`, `new_jersey`, …) |
|
|
1411
|
+
|
|
1412
|
+
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.
|
|
1413
|
+
|
|
1414
|
+
This Lambda sets `requires_state = False`: it is not tied to a single state and does not require a `session` in the event.
|
|
1415
|
+
|
|
1416
|
+
### Response format
|
|
1417
|
+
|
|
1418
|
+
The top-level `success` is **always `true`** (a failing collection no longer aborts the run). Inspect `data.summary` for the OK/failed counts and `data.results[].success` for each unit; failed units include an `error` field.
|
|
1419
|
+
|
|
1420
|
+
```json
|
|
1421
|
+
{
|
|
1422
|
+
"success": true,
|
|
1423
|
+
"data": {
|
|
1424
|
+
"summary": { "total": 4, "succeeded": 3, "failed": 1 },
|
|
1425
|
+
"results": [
|
|
1426
|
+
{"repository": "ParcelsRepository", "database": "connecticut", "collection": "parcels", "indexes": ["parcel_id_1", "..."], "orphan_indexes_dropped": ["old_field_1"], "success": true},
|
|
1427
|
+
{"repository": "ParcelsRepository", "database": "new_jersey", "collection": "parcels", "indexes": ["parcel_id_1", "..."], "orphan_indexes_dropped": [], "success": true},
|
|
1428
|
+
{"repository": "UsersRepository", "database": "core", "collection": "users", "indexes": ["email_1"], "orphan_indexes_dropped": [], "success": true},
|
|
1429
|
+
{"repository": "TokensRepository", "database": "core", "collection": "tokens", "indexes": [], "orphan_indexes_dropped": [], "success": false, "error": "..."}
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
### Properties to override / configure
|
|
1436
|
+
|
|
1437
|
+
| Property | Default | Purpose |
|
|
1438
|
+
|----------|---------|---------|
|
|
1439
|
+
| `repositories` | `[]` | **Required.** List of `Repository` subclasses (the classes, not instances) to sync. Empty raises a validation error |
|
|
1440
|
+
| `states_database` | `"core"` | Database holding the states collection |
|
|
1441
|
+
| `states_collection` | `"states"` | Collection listing the states (filtered by `is_active = true`, field `name`) |
|
|
1442
|
+
| `max_concurrency` | `10` | Max `(collection, database)` units synced in parallel. `0` (or negative) = unlimited |
|
|
1443
|
+
| `requires_state` | `False` | Inherited override — the Lambda runs across all states, no session required |
|
|
1444
|
+
|
|
1445
|
+
### Running it once per deploy (Terraform)
|
|
1446
|
+
|
|
1447
|
+
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).
|
|
1448
|
+
|
|
1449
|
+
> **Detecting failures in the deploy.** The top-level `success` is now **always `true`** — a single failed collection no longer aborts the run, so checking `success` alone won't catch partial failures. If you want the `apply` to fail when any collection failed, point the `postcondition` at `data.summary.failed` instead (shown below). If you'd rather the deploy never fail on index errors and only review them afterward (in the response/logs), simply omit the `lifecycle` block.
|
|
1450
|
+
|
|
1451
|
+
```hcl
|
|
1452
|
+
module "lambda" {
|
|
1453
|
+
source = "../../../../../modules/lambda"
|
|
1454
|
+
function_name = "${...service_name}${var.lambda_name}-${...env}"
|
|
1455
|
+
source_handler = "handlers.lambda_handler.index_sync_handler"
|
|
1456
|
+
timeout = 300
|
|
1457
|
+
memory_size = 512
|
|
1458
|
+
# ... mongo + vpc config (same as any other Lambda)
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
resource "aws_lambda_invocation" "sync_on_deploy" {
|
|
1462
|
+
function_name = module.lambda.lambda_function_name
|
|
1463
|
+
input = jsonencode({ _invoked_at = timestamp() }) # always changes → always re-runs
|
|
1464
|
+
|
|
1465
|
+
depends_on = [module.lambda] # run after the code is updated
|
|
1466
|
+
|
|
1467
|
+
# Optional: fail the apply if any collection failed to sync.
|
|
1468
|
+
# Omit this whole lifecycle block if you prefer the deploy to never fail on
|
|
1469
|
+
# index errors (review data.results[].success / logs instead).
|
|
1470
|
+
lifecycle {
|
|
1471
|
+
postcondition {
|
|
1472
|
+
condition = try(jsondecode(self.result).data.summary.failed, 1) == 0
|
|
1473
|
+
error_message = "Index sync had failures during apply: ${self.result}"
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
You can also invoke it manually at any time (e.g. after activating a new state without a code deploy):
|
|
1480
|
+
|
|
1481
|
+
```bash
|
|
1482
|
+
aws lambda invoke --function-name MyServiceModelIndexSync-dev out.json
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
---
|
|
1486
|
+
|
|
1336
1487
|
## 🏗️ Architecture Overview
|
|
1337
1488
|
|
|
1338
1489
|
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 the declared indexes **and prunes orphans** across the `core` database and all active state databases, idempotently and in parallel, 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 declared indexes + prunes orphans across `core` + all state DBs, in parallel) |
|
|
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,132 @@ 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 **synchronizing 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
|
+
It does a **two-way sync** per collection: it creates the declared indexes **and prunes orphans** — any index present in MongoDB that is no longer declared in the repository's `indexes` is dropped (the default `_id_` index and special text/geo/hashed indexes are always preserved).
|
|
1348
|
+
|
|
1349
|
+
> ⚠️ **Orphan pruning drops indexes not declared in code.** Indexes created out-of-band (by a DBA, Atlas, or another tool) that aren't in any repository's `indexes` will be removed on every run. Declare every index you want to keep.
|
|
1350
|
+
|
|
1351
|
+
**Concurrency & fault tolerance:** each `(collection, database)` unit is synced **in parallel** (bounded by `max_concurrency`, default 10); index work *within* a unit stays sequential. A failure in one unit is **captured and isolated** — it never aborts the others, so a single broken collection doesn't stop the rest of the sync. The top-level response is always `success: true`; per-unit outcomes are reported in `results[].success`.
|
|
1352
|
+
|
|
1353
|
+
### Creating an index-sync Lambda
|
|
1354
|
+
|
|
1355
|
+
Create `src/lambda/model-index-sync/main.py` and declare which repositories to sync:
|
|
1356
|
+
|
|
1357
|
+
```python
|
|
1358
|
+
import aws_python_helper
|
|
1359
|
+
|
|
1360
|
+
from repositories.parcels import ParcelsRepository # state-scoped (database_key = None)
|
|
1361
|
+
from repositories.users import UsersRepository # core (database_key = "core")
|
|
1362
|
+
from repositories.tokens import TokensRepository # core
|
|
1363
|
+
|
|
1364
|
+
class OrdersModelIndexSyncLambda(aws_python_helper.ModelIndexSyncLambda):
|
|
1365
|
+
|
|
1366
|
+
@property
|
|
1367
|
+
def repositories(self) -> list:
|
|
1368
|
+
return [ParcelsRepository, UsersRepository, TokensRepository]
|
|
1369
|
+
```
|
|
1370
|
+
|
|
1371
|
+
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1372
|
+
|
|
1373
|
+
```python
|
|
1374
|
+
from aws_python_helper.lambda_standalone.handler import lambda_handler
|
|
1375
|
+
|
|
1376
|
+
index_sync_handler = lambda_handler('model-index-sync')
|
|
1377
|
+
|
|
1378
|
+
__all__ = ["index_sync_handler"]
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
> 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.
|
|
1382
|
+
|
|
1383
|
+
### How it routes each repository
|
|
1384
|
+
|
|
1385
|
+
For every repository in the list, the Lambda decides where to create its indexes:
|
|
1386
|
+
|
|
1387
|
+
| Repository kind | `database_key` | Target databases |
|
|
1388
|
+
|-----------------|----------------|------------------|
|
|
1389
|
+
| **Core / fixed** | `"core"` (or any string) | that single database |
|
|
1390
|
+
| **State-scoped** | `None` | **every active state**, read from `core.states` where `is_active = true` (e.g. `connecticut`, `new_jersey`, …) |
|
|
1391
|
+
|
|
1392
|
+
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.
|
|
1393
|
+
|
|
1394
|
+
This Lambda sets `requires_state = False`: it is not tied to a single state and does not require a `session` in the event.
|
|
1395
|
+
|
|
1396
|
+
### Response format
|
|
1397
|
+
|
|
1398
|
+
The top-level `success` is **always `true`** (a failing collection no longer aborts the run). Inspect `data.summary` for the OK/failed counts and `data.results[].success` for each unit; failed units include an `error` field.
|
|
1399
|
+
|
|
1400
|
+
```json
|
|
1401
|
+
{
|
|
1402
|
+
"success": true,
|
|
1403
|
+
"data": {
|
|
1404
|
+
"summary": { "total": 4, "succeeded": 3, "failed": 1 },
|
|
1405
|
+
"results": [
|
|
1406
|
+
{"repository": "ParcelsRepository", "database": "connecticut", "collection": "parcels", "indexes": ["parcel_id_1", "..."], "orphan_indexes_dropped": ["old_field_1"], "success": true},
|
|
1407
|
+
{"repository": "ParcelsRepository", "database": "new_jersey", "collection": "parcels", "indexes": ["parcel_id_1", "..."], "orphan_indexes_dropped": [], "success": true},
|
|
1408
|
+
{"repository": "UsersRepository", "database": "core", "collection": "users", "indexes": ["email_1"], "orphan_indexes_dropped": [], "success": true},
|
|
1409
|
+
{"repository": "TokensRepository", "database": "core", "collection": "tokens", "indexes": [], "orphan_indexes_dropped": [], "success": false, "error": "..."}
|
|
1410
|
+
]
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
### Properties to override / configure
|
|
1416
|
+
|
|
1417
|
+
| Property | Default | Purpose |
|
|
1418
|
+
|----------|---------|---------|
|
|
1419
|
+
| `repositories` | `[]` | **Required.** List of `Repository` subclasses (the classes, not instances) to sync. Empty raises a validation error |
|
|
1420
|
+
| `states_database` | `"core"` | Database holding the states collection |
|
|
1421
|
+
| `states_collection` | `"states"` | Collection listing the states (filtered by `is_active = true`, field `name`) |
|
|
1422
|
+
| `max_concurrency` | `10` | Max `(collection, database)` units synced in parallel. `0` (or negative) = unlimited |
|
|
1423
|
+
| `requires_state` | `False` | Inherited override — the Lambda runs across all states, no session required |
|
|
1424
|
+
|
|
1425
|
+
### Running it once per deploy (Terraform)
|
|
1426
|
+
|
|
1427
|
+
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).
|
|
1428
|
+
|
|
1429
|
+
> **Detecting failures in the deploy.** The top-level `success` is now **always `true`** — a single failed collection no longer aborts the run, so checking `success` alone won't catch partial failures. If you want the `apply` to fail when any collection failed, point the `postcondition` at `data.summary.failed` instead (shown below). If you'd rather the deploy never fail on index errors and only review them afterward (in the response/logs), simply omit the `lifecycle` block.
|
|
1430
|
+
|
|
1431
|
+
```hcl
|
|
1432
|
+
module "lambda" {
|
|
1433
|
+
source = "../../../../../modules/lambda"
|
|
1434
|
+
function_name = "${...service_name}${var.lambda_name}-${...env}"
|
|
1435
|
+
source_handler = "handlers.lambda_handler.index_sync_handler"
|
|
1436
|
+
timeout = 300
|
|
1437
|
+
memory_size = 512
|
|
1438
|
+
# ... mongo + vpc config (same as any other Lambda)
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
resource "aws_lambda_invocation" "sync_on_deploy" {
|
|
1442
|
+
function_name = module.lambda.lambda_function_name
|
|
1443
|
+
input = jsonencode({ _invoked_at = timestamp() }) # always changes → always re-runs
|
|
1444
|
+
|
|
1445
|
+
depends_on = [module.lambda] # run after the code is updated
|
|
1446
|
+
|
|
1447
|
+
# Optional: fail the apply if any collection failed to sync.
|
|
1448
|
+
# Omit this whole lifecycle block if you prefer the deploy to never fail on
|
|
1449
|
+
# index errors (review data.results[].success / logs instead).
|
|
1450
|
+
lifecycle {
|
|
1451
|
+
postcondition {
|
|
1452
|
+
condition = try(jsondecode(self.result).data.summary.failed, 1) == 0
|
|
1453
|
+
error_message = "Index sync had failures during apply: ${self.result}"
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
```
|
|
1458
|
+
|
|
1459
|
+
You can also invoke it manually at any time (e.g. after activating a new state without a code deploy):
|
|
1460
|
+
|
|
1461
|
+
```bash
|
|
1462
|
+
aws lambda invoke --function-name MyServiceModelIndexSync-dev out.json
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
---
|
|
1466
|
+
|
|
1316
1467
|
## 🏗️ Architecture Overview
|
|
1317
1468
|
|
|
1318
1469
|
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.3}/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.3}/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
|
|