aws-python-helper 0.35.0__tar.gz → 0.37.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/PKG-INFO +317 -19
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/README.md +314 -18
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/__init__.py +22 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/base.py +17 -1
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/dispatcher.py +11 -3
- aws_python_helper-0.37.0/aws_python_helper/invoker/__init__.py +19 -0
- aws_python_helper-0.37.0/aws_python_helper/invoker/api_client.py +153 -0
- aws_python_helper-0.37.0/aws_python_helper/invoker/exceptions.py +23 -0
- aws_python_helper-0.37.0/aws_python_helper/invoker/lambda_invoker.py +96 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/lambda_standalone/base.py +36 -6
- aws_python_helper-0.37.0/aws_python_helper/model_query/__init__.py +3 -0
- aws_python_helper-0.37.0/aws_python_helper/model_query/base.py +120 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper.egg-info/PKG-INFO +317 -19
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper.egg-info/SOURCES.txt +6 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper.egg-info/requires.txt +2 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/pyproject.toml +3 -1
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/auth_middleware.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/auth_validators.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/exceptions.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/fetcher.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/api/handler.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/context/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/context/session.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/context/state_validator.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/database/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/database/database_proxy.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/database/external_database_proxy.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/database/mongo_manager.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/fargate/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/fargate/executor.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/fargate/fetcher.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/fargate/handler.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/fargate/task_base.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/repository/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/repository/base.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/sns/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/sns/publisher.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/sqs/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/sqs/consumer_base.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/sqs/fetcher.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/sqs/handler.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/utils/__init__.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/utils/json_encoder.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/utils/response.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper/utils/serializer.py +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/aws_python_helper.egg-info/top_level.txt +0 -0
- {aws_python_helper-0.35.0 → aws_python_helper-0.37.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-python-helper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.37.0
|
|
4
4
|
Summary: AWS Python Helper Framework
|
|
5
5
|
Author-email: Fabian Claros <neufabiae@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,6 +15,8 @@ Requires-Python: >=3.9
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
Requires-Dist: motor==3.3.2
|
|
17
17
|
Requires-Dist: pymongo==4.6.1
|
|
18
|
+
Requires-Dist: pydantic>=2.0
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
18
20
|
|
|
19
21
|
# AWS Python Framework
|
|
20
22
|
|
|
@@ -33,6 +35,10 @@ Mini-framework to create REST APIs, SQS Consumers, SNS Publishers, Fargate Tasks
|
|
|
33
35
|
- **Fargate Tasks**: Same pattern to run tasks in Fargate containers
|
|
34
36
|
- **Standalone Lambdas**: Create lambdas invocable directly with AWS SDK
|
|
35
37
|
- **Authentication middleware**: Built-in token-based authentication
|
|
38
|
+
- **Pydantic schema validation**: Declare a `schema` property on any `API` or `Lambda` and the framework validates automatically before `validate()` — returns 400 on failure
|
|
39
|
+
- **LambdaInvoker**: Invoke other Lambda functions (sync or async) with built-in error handling
|
|
40
|
+
- **ApiClient**: HTTP client for inter-service communication — resolves service URLs and auth token from environment variables automatically
|
|
41
|
+
- **ModelQueryLambda**: Base class for MongoDB query-proxy Lambdas — declare an `allowed_collections` whitelist and the framework handles `find` and `aggregate` operations generically
|
|
36
42
|
- **JSON utilities**: Automatic serialization of MongoDB types
|
|
37
43
|
- **Type hints**: Modern Python with type annotations
|
|
38
44
|
- **Async/await**: Full support for asynchronous operations
|
|
@@ -60,6 +66,14 @@ All available classes and functions:
|
|
|
60
66
|
| `FargateExecutor` | `aws_python_helper.fargate.executor` | Launches Fargate tasks from Lambda |
|
|
61
67
|
| `fargate_handler` | `aws_python_helper.fargate.handler` | Entry point handler for Fargate |
|
|
62
68
|
| `Repository` | `aws_python_helper.repository.base` | Base class for MongoDB repositories |
|
|
69
|
+
| `ModelQueryLambda` | `aws_python_helper` | Generic MongoDB query-proxy Lambda base class |
|
|
70
|
+
| `LambdaInvoker` | `aws_python_helper` | Invoke Lambda functions (sync/async) with error handling |
|
|
71
|
+
| `ApiClient` | `aws_python_helper` | HTTP client for inter-service communication |
|
|
72
|
+
| `LambdaInvocationError` | `aws_python_helper` | Raised when boto3 fails to invoke a Lambda |
|
|
73
|
+
| `LambdaResponseError` | `aws_python_helper` | Raised when a Lambda returns a FunctionError |
|
|
74
|
+
| `ServiceNotConfiguredError` | `aws_python_helper` | Raised when a service name is not in MICROSERVICE_URLS |
|
|
75
|
+
| `ApiClientError` | `aws_python_helper` | Raised when an HTTP request fails (network/timeout) |
|
|
76
|
+
| `ApiResponseError` | `aws_python_helper` | Raised when a remote API returns 4xx/5xx |
|
|
63
77
|
| `Session` | `aws_python_helper` | Request-scoped session object (state + user) |
|
|
64
78
|
| `get_session` | `aws_python_helper` | Read the current Session from async context |
|
|
65
79
|
| `set_session` | `aws_python_helper` | Set the current Session in async context |
|
|
@@ -897,19 +911,21 @@ Here's a complete example showing how an API can invoke a standalone lambda:
|
|
|
897
911
|
**1. The API endpoint** (`src/api/shippings/post.py`):
|
|
898
912
|
|
|
899
913
|
```python
|
|
900
|
-
from
|
|
901
|
-
import
|
|
902
|
-
|
|
914
|
+
from pydantic import BaseModel
|
|
915
|
+
from aws_python_helper import API, LambdaInvoker
|
|
916
|
+
|
|
917
|
+
class ShippingSchema(BaseModel):
|
|
918
|
+
customer_id: str
|
|
919
|
+
address: str
|
|
920
|
+
items: list[str]
|
|
903
921
|
|
|
904
922
|
class ShippingPostAPI(API):
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
raise ValueError(f"{field} is required")
|
|
923
|
+
|
|
924
|
+
@property
|
|
925
|
+
def schema(self):
|
|
926
|
+
return ShippingSchema # automatic validation → 400 on failure
|
|
910
927
|
|
|
911
928
|
async def process(self):
|
|
912
|
-
# Create shipping in database
|
|
913
929
|
shipping = {
|
|
914
930
|
'customer_id': self.data['customer_id'],
|
|
915
931
|
'address': self.data['address'],
|
|
@@ -921,15 +937,11 @@ class ShippingPostAPI(API):
|
|
|
921
937
|
result = await self.db.deliveries.shippings.insert_one(shipping)
|
|
922
938
|
shipping_id = str(result.inserted_id)
|
|
923
939
|
|
|
924
|
-
# Invoke standalone lambda asynchronously
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
Payload=json.dumps({
|
|
930
|
-
'data': {'shipping_id': shipping_id}
|
|
931
|
-
})
|
|
932
|
-
)
|
|
940
|
+
# Invoke standalone lambda asynchronously (fire-and-forget)
|
|
941
|
+
invoker = LambdaInvoker()
|
|
942
|
+
invoker.invoke_async('GenerateRouteLambda', payload={
|
|
943
|
+
'data': {'shipping_id': shipping_id}
|
|
944
|
+
})
|
|
933
945
|
|
|
934
946
|
self.set_code(201)
|
|
935
947
|
self.set_body({
|
|
@@ -1008,6 +1020,290 @@ __all__ = ['generate_route_handler']
|
|
|
1008
1020
|
- Can retry lambda independently if it fails
|
|
1009
1021
|
- Scalable architecture
|
|
1010
1022
|
|
|
1023
|
+
## ✅ Pydantic Schema Validation
|
|
1024
|
+
|
|
1025
|
+
The framework supports automatic request validation via [Pydantic](https://docs.pydantic.dev/) v2. Override the `schema` property on any `API` or `Lambda` subclass to declare the expected shape of `self.data`. The framework validates before calling `validate()` and replaces `self.data` with the coerced output — the property name stays the same.
|
|
1026
|
+
|
|
1027
|
+
### In an API endpoint
|
|
1028
|
+
|
|
1029
|
+
For `POST`/`PUT`, `self.data` is the request body. For `GET`/`LIST`, it is the query parameters. Both are validated the same way.
|
|
1030
|
+
|
|
1031
|
+
```python
|
|
1032
|
+
from pydantic import BaseModel
|
|
1033
|
+
from aws_python_helper import API
|
|
1034
|
+
|
|
1035
|
+
class SearchSchema(BaseModel):
|
|
1036
|
+
keys: list[str]
|
|
1037
|
+
state: str
|
|
1038
|
+
limit: int = 100
|
|
1039
|
+
|
|
1040
|
+
class SearchPostAPI(API):
|
|
1041
|
+
|
|
1042
|
+
@property
|
|
1043
|
+
def schema(self):
|
|
1044
|
+
return SearchSchema # ← that's all
|
|
1045
|
+
|
|
1046
|
+
async def process(self):
|
|
1047
|
+
keys = self.data["keys"] # self.data is validated and coerced
|
|
1048
|
+
limit = self.data["limit"] # default applied by Pydantic
|
|
1049
|
+
...
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
**On validation failure the framework automatically returns HTTP 400** — no extra code needed.
|
|
1053
|
+
|
|
1054
|
+
### In a Standalone Lambda
|
|
1055
|
+
|
|
1056
|
+
Same property, same behavior. On failure a `ValueError` is raised, which the handler returns as `{"success": False, "error": "..."}`.
|
|
1057
|
+
|
|
1058
|
+
```python
|
|
1059
|
+
from pydantic import BaseModel
|
|
1060
|
+
from aws_python_helper import Lambda
|
|
1061
|
+
|
|
1062
|
+
class SyncSchema(BaseModel):
|
|
1063
|
+
shipping_id: str
|
|
1064
|
+
carrier_id: str
|
|
1065
|
+
|
|
1066
|
+
class SyncCarrierLambda(Lambda):
|
|
1067
|
+
|
|
1068
|
+
@property
|
|
1069
|
+
def schema(self):
|
|
1070
|
+
return SyncSchema
|
|
1071
|
+
|
|
1072
|
+
async def process(self):
|
|
1073
|
+
shipping_id = self.data["shipping_id"] # validated
|
|
1074
|
+
...
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
### Without a schema
|
|
1078
|
+
|
|
1079
|
+
If `schema` is not overridden (returns `None`, the default), the behavior is identical to before — no change.
|
|
1080
|
+
|
|
1081
|
+
---
|
|
1082
|
+
|
|
1083
|
+
## 🔗 Inter-Service Communication
|
|
1084
|
+
|
|
1085
|
+
The framework provides two utilities for calling other services: `LambdaInvoker` for direct Lambda invocations and `ApiClient` for HTTP API calls.
|
|
1086
|
+
|
|
1087
|
+
### LambdaInvoker
|
|
1088
|
+
|
|
1089
|
+
Wraps `boto3` Lambda invocations with proper error handling. Never returns `None` — raises typed exceptions instead.
|
|
1090
|
+
|
|
1091
|
+
```python
|
|
1092
|
+
from aws_python_helper import LambdaInvoker, LambdaInvocationError, LambdaResponseError
|
|
1093
|
+
|
|
1094
|
+
invoker = LambdaInvoker()
|
|
1095
|
+
|
|
1096
|
+
# Synchronous — waits for result
|
|
1097
|
+
result = invoker.invoke("my-function-name", payload={"key": "value"})
|
|
1098
|
+
# result is the parsed dict returned by the Lambda
|
|
1099
|
+
|
|
1100
|
+
# Asynchronous fire-and-forget
|
|
1101
|
+
invoker.invoke_async("my-function-name", payload={"key": "value"})
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
**Exceptions:**
|
|
1105
|
+
|
|
1106
|
+
| Exception | When |
|
|
1107
|
+
|-----------|------|
|
|
1108
|
+
| `LambdaInvocationError` | boto3 could not reach the function (network, permissions, etc.) |
|
|
1109
|
+
| `LambdaResponseError` | The function itself raised an unhandled exception (`FunctionError` in the response) |
|
|
1110
|
+
|
|
1111
|
+
**Note:** `boto3` is part of the AWS Lambda Python runtime — no installation needed. For Fargate containers, add `boto3` to your `requirements.txt`.
|
|
1112
|
+
|
|
1113
|
+
### ApiClient
|
|
1114
|
+
|
|
1115
|
+
HTTP client for service-to-service calls. Resolves the target URL and auth token automatically from environment variables — no hardcoded URLs or tokens in code.
|
|
1116
|
+
|
|
1117
|
+
```python
|
|
1118
|
+
from aws_python_helper import ApiClient
|
|
1119
|
+
|
|
1120
|
+
client = ApiClient("dockets") # service name only
|
|
1121
|
+
result = await client.post("/search", body={"keys": [...]})
|
|
1122
|
+
result = await client.get("/dockets/123")
|
|
1123
|
+
result = await client.list("/dockets", params={"state": "CT"})
|
|
1124
|
+
result = await client.put("/dockets/123", body={"status": "active"})
|
|
1125
|
+
result = await client.patch("/dockets/123", body={"reviewed": True})
|
|
1126
|
+
result = await client.delete("/dockets/123")
|
|
1127
|
+
|
|
1128
|
+
# Extra headers merged with the defaults (token is always injected automatically)
|
|
1129
|
+
client = ApiClient("dockets", headers={"constitution-state": "CT"})
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
**How URL and token are resolved:**
|
|
1133
|
+
|
|
1134
|
+
| Env var | Purpose |
|
|
1135
|
+
|---------|---------|
|
|
1136
|
+
| `MICROSERVICE_URLS` | JSON map: `{"dockets": "https://api.example.com/", "title-search": "https://..."}` |
|
|
1137
|
+
| `INTER_SERVICE_TOKEN` | Bearer token injected as `Authorization: Bearer <token>` |
|
|
1138
|
+
| `AUTH_BYPASS_TOKEN` | Fallback if `INTER_SERVICE_TOKEN` is not set |
|
|
1139
|
+
|
|
1140
|
+
**Exceptions:**
|
|
1141
|
+
|
|
1142
|
+
| Exception | When |
|
|
1143
|
+
|-----------|------|
|
|
1144
|
+
| `ServiceNotConfiguredError` | Service name not found in `MICROSERVICE_URLS` |
|
|
1145
|
+
| `ApiClientError` | Network error or timeout |
|
|
1146
|
+
| `ApiResponseError` | Remote API returned 4xx or 5xx (has `.status_code` and `.response_body` attributes) |
|
|
1147
|
+
|
|
1148
|
+
**Terraform configuration** — set once per environment in Secrets Manager with keys `inter_service_token` and `microservice_urls`. The Lambda and Fargate modules pick them up automatically from infrastructure outputs; no extra variables needed in resource files beyond passing them through:
|
|
1149
|
+
|
|
1150
|
+
```hcl
|
|
1151
|
+
# In AWS Secrets Manager secret (JSON):
|
|
1152
|
+
# {
|
|
1153
|
+
# "inter_service_token": "my-secret-token",
|
|
1154
|
+
# "microservice_urls": "{\"dockets\": \"https://api-dockets.execute-api.us-east-2.amazonaws.com/\"}",
|
|
1155
|
+
# ...
|
|
1156
|
+
# }
|
|
1157
|
+
|
|
1158
|
+
# In Terraform module call (already handled by the modules):
|
|
1159
|
+
module "my_lambda" {
|
|
1160
|
+
source = "../modules/lambda"
|
|
1161
|
+
inter_service_token = data.terraform_remote_state.infrastructure.outputs.inter_service_token
|
|
1162
|
+
microservice_urls = data.terraform_remote_state.infrastructure.outputs.microservice_urls
|
|
1163
|
+
...
|
|
1164
|
+
}
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
---
|
|
1168
|
+
|
|
1169
|
+
## 🔍 ModelQueryLambda
|
|
1170
|
+
|
|
1171
|
+
`ModelQueryLambda` is a ready-to-use base class for creating MongoDB query-proxy Lambdas. Instead of writing a custom Lambda every time another microservice needs to read data from your MongoDB, you subclass `ModelQueryLambda`, declare an `allowed_collections` whitelist, and the framework exposes both `find` and `aggregate` operations automatically.
|
|
1172
|
+
|
|
1173
|
+
**Use case:** microservice A needs to query data owned by microservice B. Microservice B deploys one `model-query` Lambda; microservice A calls it via `LambdaInvoker` passing the collection name and query parameters.
|
|
1174
|
+
|
|
1175
|
+
### Creating a model-query Lambda
|
|
1176
|
+
|
|
1177
|
+
Create `src/lambda/model-query/main.py` in the microservice that owns the data:
|
|
1178
|
+
|
|
1179
|
+
```python
|
|
1180
|
+
from aws_python_helper import ModelQueryLambda
|
|
1181
|
+
|
|
1182
|
+
class ModelQueryLambda(ModelQueryLambda):
|
|
1183
|
+
|
|
1184
|
+
@property
|
|
1185
|
+
def allowed_collections(self) -> list:
|
|
1186
|
+
return ["orders", "customers"] # whitelist — unlisted collections are rejected
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
Register the handler in `src/handlers/lambda_handler.py`:
|
|
1190
|
+
|
|
1191
|
+
```python
|
|
1192
|
+
from aws_python_helper.lambda_standalone.handler import lambda_handler
|
|
1193
|
+
|
|
1194
|
+
model_query_handler = lambda_handler('model-query')
|
|
1195
|
+
|
|
1196
|
+
__all__ = ["model_query_handler"]
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
### Query modes
|
|
1200
|
+
|
|
1201
|
+
#### Aggregate pipeline
|
|
1202
|
+
|
|
1203
|
+
Pass a `pipeline` list to run a MongoDB aggregation:
|
|
1204
|
+
|
|
1205
|
+
```python
|
|
1206
|
+
invoker.invoke("ServiceModelQuery-dev", payload={
|
|
1207
|
+
"session": self.session.to_dict(),
|
|
1208
|
+
"collection": "orders",
|
|
1209
|
+
"pipeline": [
|
|
1210
|
+
{"$match": {"status": "pending"}},
|
|
1211
|
+
{"$sort": {"created_at": -1}},
|
|
1212
|
+
{"$limit": 50},
|
|
1213
|
+
],
|
|
1214
|
+
})
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
#### Find with filter
|
|
1218
|
+
|
|
1219
|
+
Pass `filter`, `fields`, `limit`, and/or `skip` to run a `find`:
|
|
1220
|
+
|
|
1221
|
+
```python
|
|
1222
|
+
invoker.invoke("ServiceModelQuery-dev", payload={
|
|
1223
|
+
"session": self.session.to_dict(),
|
|
1224
|
+
"collection": "orders",
|
|
1225
|
+
"filter": {"customer_id": "abc123", "status": "active"},
|
|
1226
|
+
"fields": {"_id": 1, "total": 1, "status": 1}, # projection
|
|
1227
|
+
"limit": 20,
|
|
1228
|
+
"skip": 0,
|
|
1229
|
+
})
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
**Note:** `pipeline` and `fields` are mutually exclusive — the framework rejects payloads that include both.
|
|
1233
|
+
|
|
1234
|
+
### Payload reference
|
|
1235
|
+
|
|
1236
|
+
| Field | Type | Required | Description |
|
|
1237
|
+
|-------|------|----------|-------------|
|
|
1238
|
+
| `session` | `dict` | Yes | Session dict from `self.session.to_dict()` — drives state-scoped DB routing |
|
|
1239
|
+
| `collection` | `str` | Yes | Collection name (must be in `allowed_collections`) |
|
|
1240
|
+
| `pipeline` | `list` | One of | MongoDB aggregation pipeline |
|
|
1241
|
+
| `filter` | `dict` | One of | MongoDB filter document (defaults to `{}`) |
|
|
1242
|
+
| `fields` | `dict` | No | MongoDB projection (only valid with `filter` mode) |
|
|
1243
|
+
| `limit` | `int` | No | Max documents to return |
|
|
1244
|
+
| `skip` | `int` | No | Documents to skip (default `0`) |
|
|
1245
|
+
|
|
1246
|
+
### Response format
|
|
1247
|
+
|
|
1248
|
+
The Lambda always returns the standard framework envelope:
|
|
1249
|
+
|
|
1250
|
+
```json
|
|
1251
|
+
{
|
|
1252
|
+
"success": true,
|
|
1253
|
+
"data": [ ... ]
|
|
1254
|
+
}
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
On validation error (unknown collection, invalid payload):
|
|
1258
|
+
|
|
1259
|
+
```json
|
|
1260
|
+
{
|
|
1261
|
+
"success": false,
|
|
1262
|
+
"error": "Collection 'unknown' is not allowed. Allowed: orders, customers"
|
|
1263
|
+
}
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
### Calling it from another microservice
|
|
1267
|
+
|
|
1268
|
+
```python
|
|
1269
|
+
import os
|
|
1270
|
+
from aws_python_helper import LambdaInvoker, LambdaInvocationError
|
|
1271
|
+
|
|
1272
|
+
class OrdersPostAPI(API):
|
|
1273
|
+
|
|
1274
|
+
async def process(self):
|
|
1275
|
+
try:
|
|
1276
|
+
response = LambdaInvoker().invoke(
|
|
1277
|
+
os.getenv("ORDERS_MODEL_QUERY_LAMBDA_NAME"),
|
|
1278
|
+
payload={
|
|
1279
|
+
"session": self.session.to_dict(),
|
|
1280
|
+
"collection": "orders",
|
|
1281
|
+
"filter": {"status": "pending"},
|
|
1282
|
+
"limit": 100,
|
|
1283
|
+
},
|
|
1284
|
+
)
|
|
1285
|
+
except LambdaInvocationError as e:
|
|
1286
|
+
self.logger.error(f"model-query invocation failed: {e}")
|
|
1287
|
+
raise ValueError("Could not retrieve orders. Please try again later.")
|
|
1288
|
+
|
|
1289
|
+
if not response.get("success"):
|
|
1290
|
+
raise ValueError("Could not retrieve orders. Please try again later.")
|
|
1291
|
+
|
|
1292
|
+
orders = response.get("data", [])
|
|
1293
|
+
self.set_body({"orders": orders})
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
### Session and database routing
|
|
1297
|
+
|
|
1298
|
+
The `session` field in the payload is required. The `ModelQueryLambda` uses `session.state` to resolve the target database — the same state-scoped routing used by all framework components. This means:
|
|
1299
|
+
|
|
1300
|
+
- `{"session": {"state": "connecticut"}, ...}` → queries the `connecticut` database
|
|
1301
|
+
- `{"session": {"state": "new_jersey"}, ...}` → queries the `new_jersey` database
|
|
1302
|
+
|
|
1303
|
+
Always pass `self.session.to_dict()` when calling from an API or Lambda to ensure state propagates correctly.
|
|
1304
|
+
|
|
1305
|
+
---
|
|
1306
|
+
|
|
1011
1307
|
## 🏗️ Architecture Overview
|
|
1012
1308
|
|
|
1013
1309
|
Typical flow for event-driven architectures using this framework:
|
|
@@ -1114,6 +1410,8 @@ environment_variables = {
|
|
|
1114
1410
|
| `REQUIRE_AUTH` | Optional | Enable authentication middleware (`true`/`false`) |
|
|
1115
1411
|
| `AUTH_DB_NAME` | If `REQUIRE_AUTH=true` | MongoDB database for token validation |
|
|
1116
1412
|
| `AUTH_BYPASS_TOKEN` | Optional | Master token to bypass authentication |
|
|
1413
|
+
| `INTER_SERVICE_TOKEN` | Optional | Bearer token for `ApiClient` service-to-service calls |
|
|
1414
|
+
| `MICROSERVICE_URLS` | Optional | JSON map of service name → base URL used by `ApiClient` |
|
|
1117
1415
|
| `ECS_CLUSTER` | Fargate only | ECS cluster name for `FargateExecutor` |
|
|
1118
1416
|
| `ECS_SUBNETS` | Fargate only | Comma-separated subnet IDs for Fargate tasks |
|
|
1119
1417
|
| `CONSTITUTION_STATE` | Fargate only (auto) | State injected automatically by `FargateExecutor` — do not set manually |
|