clear-skies-aws 2.0.2__py3-none-any.whl → 2.0.3__py3-none-any.whl
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.
- {clear_skies_aws-2.0.2.dist-info → clear_skies_aws-2.0.3.dist-info}/METADATA +2 -2
- {clear_skies_aws-2.0.2.dist-info → clear_skies_aws-2.0.3.dist-info}/RECORD +23 -23
- {clear_skies_aws-2.0.2.dist-info → clear_skies_aws-2.0.3.dist-info}/WHEEL +1 -1
- clearskies_aws/__init__.py +14 -2
- clearskies_aws/contexts/__init__.py +5 -5
- clearskies_aws/contexts/cli_web_socket_mock.py +3 -2
- clearskies_aws/contexts/lambda_alb.py +8 -3
- clearskies_aws/contexts/lambda_api_gateway.py +13 -9
- clearskies_aws/contexts/lambda_api_gateway_web_socket.py +24 -2
- clearskies_aws/contexts/lambda_invoke.py +138 -0
- clearskies_aws/contexts/lambda_sns.py +110 -4
- clearskies_aws/contexts/lambda_sqs_standard.py +139 -0
- clearskies_aws/endpoints/__init__.py +0 -1
- clearskies_aws/endpoints/secrets_manager_rotation.py +0 -1
- clearskies_aws/input_outputs/__init__.py +2 -2
- clearskies_aws/input_outputs/cli_web_socket_mock.py +2 -0
- clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +9 -7
- clearskies_aws/input_outputs/lambda_input_output.py +4 -2
- clearskies_aws/input_outputs/{lambda_invocation.py → lambda_invoke.py} +10 -7
- clearskies_aws/input_outputs/lambda_sns.py +27 -18
- clearskies_aws/input_outputs/lambda_sqs_standard.py +32 -30
- clearskies_aws/models/__init__.py +1 -0
- clearskies_aws/contexts/lambda_invocation.py +0 -19
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +0 -29
- {clear_skies_aws-2.0.2.dist-info → clear_skies_aws-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clear-skies-aws
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: clearskies bindings for working in AWS
|
|
5
5
|
Project-URL: Repository, https://github.com/clearskies-py/clearskies-aws
|
|
6
6
|
Project-URL: Issues, https://github.com/clearskies-py/clearskies-aws/issues
|
|
@@ -16,7 +16,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Requires-Python: <4.0,>=3.11
|
|
18
18
|
Requires-Dist: boto3<2.0.0,>=1.26.148
|
|
19
|
-
Requires-Dist: clear-skies<3.0.0,>=2.0.
|
|
19
|
+
Requires-Dist: clear-skies<3.0.0,>=2.0.20
|
|
20
20
|
Requires-Dist: types-boto3[dynamodb,sns,sqs]<2.0.0,>=1.38.13
|
|
21
21
|
Provides-Extra: akeyless
|
|
22
22
|
Requires-Dist: akeyless-cloud-id<0.5.0,>=0.2.3; extra == 'akeyless'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
clearskies_aws/__init__.py,sha256
|
|
1
|
+
clearskies_aws/__init__.py,sha256=-vmIX17AJIxy3-2CO4iXofUcgIUAckfHfdrkHtkya8g,368
|
|
2
2
|
clearskies_aws/actions/__init__.py,sha256=YsIi3ZTdByu4R7I77uOAXDE5hzB31J_tRrIoTxe_bjo,371
|
|
3
3
|
clearskies_aws/actions/action_aws.py,sha256=JMogBFIrN72h5oBQLbyMy0LmfVLrqvWCYLQDml1qO4M,4674
|
|
4
4
|
clearskies_aws/actions/assume_role.py,sha256=XNxbVju460aBMS8NQhY80eoNVQgZRJ6-OydsNOx3neA,4319
|
|
@@ -13,39 +13,39 @@ clearskies_aws/backends/dynamo_db_condition_parser.py,sha256=OipIlFpcfS2GQiGpge6
|
|
|
13
13
|
clearskies_aws/backends/dynamo_db_parti_ql_backend.py,sha256=x1pLNTUsTjCxi1SDryr9_uhL-AQJS7ksP5bvQpnhaC8,46081
|
|
14
14
|
clearskies_aws/backends/sqs_backend.py,sha256=kHTzgBwpYzV31UcGaoSUcC_7eZEwm-GsCHvXFM1OLT0,2152
|
|
15
15
|
clearskies_aws/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
clearskies_aws/contexts/__init__.py,sha256=
|
|
17
|
-
clearskies_aws/contexts/cli_web_socket_mock.py,sha256=
|
|
18
|
-
clearskies_aws/contexts/lambda_alb.py,sha256=
|
|
19
|
-
clearskies_aws/contexts/lambda_api_gateway.py,sha256=
|
|
20
|
-
clearskies_aws/contexts/lambda_api_gateway_web_socket.py,sha256=
|
|
21
|
-
clearskies_aws/contexts/
|
|
22
|
-
clearskies_aws/contexts/lambda_sns.py,sha256=
|
|
23
|
-
clearskies_aws/contexts/
|
|
16
|
+
clearskies_aws/contexts/__init__.py,sha256=kBLUlkpIp88n5RgbE9gVi4rDnJou81vCE5g_aiv2v8Y,717
|
|
17
|
+
clearskies_aws/contexts/cli_web_socket_mock.py,sha256=XjsFcx0YlQqjb9Hh8q2NUW7T2Exu7_FHx9v_Am0Uq0g,784
|
|
18
|
+
clearskies_aws/contexts/lambda_alb.py,sha256=hNMWYePN_moC4waXdydAp7dJkGpSowxDZZzU39Sxc7I,3426
|
|
19
|
+
clearskies_aws/contexts/lambda_api_gateway.py,sha256=HWH-CAX_aYiDhQr8rBhR5TW99-wx2ODsyI_dqGJkcdw,3354
|
|
20
|
+
clearskies_aws/contexts/lambda_api_gateway_web_socket.py,sha256=gO0arcveIbvkCxrqjk8piVwcymuw_pgHwsSfdfElyWU,4583
|
|
21
|
+
clearskies_aws/contexts/lambda_invoke.py,sha256=Ouns3zIw0G1aCuB0yVS91PgNqFVyqT3Oe6NcDgaEcvY,4872
|
|
22
|
+
clearskies_aws/contexts/lambda_sns.py,sha256=_TyPqRWosfaJzreYGNabvR-XTLYYO4eVWMVINSIY-OE,4129
|
|
23
|
+
clearskies_aws/contexts/lambda_sqs_standard.py,sha256=uzENeXJaWsZoUxves8IXo8wFrcub_viQSKF9fmmPPvE,5293
|
|
24
24
|
clearskies_aws/di/__init__.py,sha256=pLHSIKxS1oELOgttRuwM0yXdJRxjZKXQ6tPxme2db0U,222
|
|
25
25
|
clearskies_aws/di/aws_additional_config_auto_import.py,sha256=qc9AUlFdF9jhAzS99esyvgY1MiW7mLz45fATK7Yp0rg,1271
|
|
26
26
|
clearskies_aws/di/inject/__init__.py,sha256=5_x5_BBQwC6J4k5YLdTm1DfIDM-95zXz1L5a1nMrlrY,186
|
|
27
27
|
clearskies_aws/di/inject/boto3.py,sha256=7qcn5N-RnUKiCd1U31JAjQHF6NDudS4KyBLT0krUD-Y,404
|
|
28
28
|
clearskies_aws/di/inject/boto3_session.py,sha256=11UYHz5kgrrx5lawoYaOFBm-QIoa45YUCMAOn4gT8Jo,383
|
|
29
29
|
clearskies_aws/di/inject/parameter_store.py,sha256=g0uAVwQEywLO9bCcYLbDKuyYnYgVyrfcYsOBJWYGads,475
|
|
30
|
-
clearskies_aws/endpoints/__init__.py,sha256=
|
|
31
|
-
clearskies_aws/endpoints/secrets_manager_rotation.py,sha256=
|
|
30
|
+
clearskies_aws/endpoints/__init__.py,sha256=OUL_nhtuNs62BvQeVtC9xP_e9Hs_-qjANvb81vdLdrc,61
|
|
31
|
+
clearskies_aws/endpoints/secrets_manager_rotation.py,sha256=wLJjid_K09rXGEd_MIgoHP84im4_Eo2m_lwAxn36Jf8,7763
|
|
32
32
|
clearskies_aws/endpoints/simple_body_routing.py,sha256=B3fnfxUEMuNpzc26Pzly6618DFU9fpaCk8KhTGSaptE,1181
|
|
33
|
-
clearskies_aws/input_outputs/__init__.py,sha256=
|
|
34
|
-
clearskies_aws/input_outputs/cli_web_socket_mock.py,sha256=
|
|
33
|
+
clearskies_aws/input_outputs/__init__.py,sha256=RTDFwhPWZ2S0tZQiIPH0Tkj2xF-9qBjZte_CA2cmGt8,743
|
|
34
|
+
clearskies_aws/input_outputs/cli_web_socket_mock.py,sha256=cp0MaJjVnsXE1rx5K44lpN9uHKo3MOAsNxVQ3AsJOi4,547
|
|
35
35
|
clearskies_aws/input_outputs/lambda_alb.py,sha256=iYooG5AlkrQQgzSt304A5_u_UhuqYh-9PeLvFdGM_Eg,2013
|
|
36
36
|
clearskies_aws/input_outputs/lambda_api_gateway.py,sha256=n5XiaxmaFKJ9wMQcNQwizpjcA-L_bdLk1aUk-QyO58w,4672
|
|
37
|
-
clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py,sha256=
|
|
38
|
-
clearskies_aws/input_outputs/lambda_input_output.py,sha256=
|
|
39
|
-
clearskies_aws/input_outputs/
|
|
40
|
-
clearskies_aws/input_outputs/lambda_sns.py,sha256=
|
|
41
|
-
clearskies_aws/input_outputs/lambda_sqs_standard.py,sha256=
|
|
37
|
+
clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py,sha256=B7LIXI_9HpquTtUuPVnb7aH1agJt9qTWiISWiNlobdo,2926
|
|
38
|
+
clearskies_aws/input_outputs/lambda_input_output.py,sha256=nJza0TFdAYOHdLvq6eFDW-TobpeF3EzuIsW9rdhrj3U,2819
|
|
39
|
+
clearskies_aws/input_outputs/lambda_invoke.py,sha256=9TvtxVn2MYxUq9luZ-o0DMboFulA5ff-G8RFo3_x-nA,2861
|
|
40
|
+
clearskies_aws/input_outputs/lambda_sns.py,sha256=nPvLxi5EUmgSeDYxFHnosG9mEzVKjt_mOFJvjsb1gQ4,3084
|
|
41
|
+
clearskies_aws/input_outputs/lambda_sqs_standard.py,sha256=rtOH75SKuT4ByOn4tfgDvvLog0IZVaZUh8zQ_E4Yygw,2945
|
|
42
42
|
clearskies_aws/mocks/__init__.py,sha256=mn764gINN667tYoJfnsM6HjAAhCsO_kZ6E-fUwdLY50,22
|
|
43
43
|
clearskies_aws/mocks/actions/__init__.py,sha256=to1r8B365Et2PRVfUWWnJGt7Hdr8vwwQuNyZvTSTP6g,152
|
|
44
44
|
clearskies_aws/mocks/actions/ses.py,sha256=KsII9ggU364BQoxgHfO2rxi6tjVsdnwJyMhtclOhWLQ,998
|
|
45
45
|
clearskies_aws/mocks/actions/sns.py,sha256=yGY3V0_htOZ3y8VTLdznoMSUQZvnjulQbrR5z_-0fXQ,706
|
|
46
46
|
clearskies_aws/mocks/actions/sqs.py,sha256=y0Vq7IMbjlfT5JMNHfbPsq9XVZhUF-G0kdXzQnPzyUA,710
|
|
47
47
|
clearskies_aws/mocks/actions/step_function.py,sha256=ENEVy8Ai3vPymbQre5aWa5z2McBjlnopfsLxdO7oEbc,937
|
|
48
|
-
clearskies_aws/models/__init__.py,sha256=
|
|
48
|
+
clearskies_aws/models/__init__.py,sha256=tAU5cPGRSzSClNVRCBxzwlBq6eZO8fftuI3bG1jEyVQ,87
|
|
49
49
|
clearskies_aws/models/web_socket_connection_model.py,sha256=5M1qfQHKuWMYPUDkwT48QPo2ROey7koizvWLfapsfow,7492
|
|
50
50
|
clearskies_aws/secrets/__init__.py,sha256=0mqYja2ETBHJh4b3jgRhhJV1uGdZc9S7cUvcV5QByPs,445
|
|
51
51
|
clearskies_aws/secrets/akeyless_with_ssm_cache.py,sha256=32HUS1KQ5F6Fu70HDtojqDL7VZvP_YDgbWLTTmNJvPA,2073
|
|
@@ -57,7 +57,7 @@ clearskies_aws/secrets/additional_configs/iam_db_auth.py,sha256=PwyiLaacpRfhBKzQ
|
|
|
57
57
|
clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py,sha256=ABY29X-YvrE6vvNo6kVdf4DqyRNq5cFR5SfK7MNkltE,3463
|
|
58
58
|
clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py,sha256=mLaplwvJLSbGh6oXgdOKL9Mv-6hLv5OUYCfEwHbHvLE,3700
|
|
59
59
|
clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py,sha256=2VHOwto4I9gBwrpd2HGpL-Wr0T2S-jFjUhe2Ib8hNJ8,6596
|
|
60
|
-
clear_skies_aws-2.0.
|
|
61
|
-
clear_skies_aws-2.0.
|
|
62
|
-
clear_skies_aws-2.0.
|
|
63
|
-
clear_skies_aws-2.0.
|
|
60
|
+
clear_skies_aws-2.0.3.dist-info/METADATA,sha256=ZQj_x9m1wlgaf4SH_gu1AJiGiykQzDjdnBRLOgh9Q1Y,8973
|
|
61
|
+
clear_skies_aws-2.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
62
|
+
clear_skies_aws-2.0.3.dist-info/licenses/LICENSE,sha256=MkEX8JF8kZxdyBpTTcB0YTd-xZpWnHvbRlw-pQh8u58,1069
|
|
63
|
+
clear_skies_aws-2.0.3.dist-info/RECORD,,
|
clearskies_aws/__init__.py
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from clearskies_aws import
|
|
3
|
+
from clearskies_aws import (
|
|
4
|
+
actions,
|
|
5
|
+
backends,
|
|
6
|
+
contexts,
|
|
7
|
+
di,
|
|
8
|
+
endpoints,
|
|
9
|
+
handlers,
|
|
10
|
+
input_outputs,
|
|
11
|
+
mocks,
|
|
12
|
+
models,
|
|
13
|
+
secrets,
|
|
14
|
+
)
|
|
4
15
|
|
|
5
16
|
__all__ = [
|
|
6
|
-
"models",
|
|
7
17
|
"actions",
|
|
8
18
|
"backends",
|
|
9
19
|
"contexts",
|
|
10
20
|
"di",
|
|
11
21
|
"endpoints",
|
|
22
|
+
"handlers",
|
|
12
23
|
"input_outputs",
|
|
13
24
|
"mocks",
|
|
25
|
+
"models",
|
|
14
26
|
"secrets",
|
|
15
27
|
]
|
|
@@ -6,10 +6,10 @@ from clearskies_aws.contexts.lambda_api_gateway import LambdaApiGateway
|
|
|
6
6
|
from clearskies_aws.contexts.lambda_api_gateway_web_socket import (
|
|
7
7
|
LambdaApiGatewayWebSocket,
|
|
8
8
|
)
|
|
9
|
-
from clearskies_aws.contexts.
|
|
9
|
+
from clearskies_aws.contexts.lambda_invoke import LambdaInvoke
|
|
10
10
|
from clearskies_aws.contexts.lambda_sns import LambdaSns
|
|
11
|
-
from clearskies_aws.contexts.
|
|
12
|
-
|
|
11
|
+
from clearskies_aws.contexts.lambda_sqs_standard import (
|
|
12
|
+
LambdaSqsStandard,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
@@ -17,7 +17,7 @@ __all__ = [
|
|
|
17
17
|
"LambdaAlb",
|
|
18
18
|
"LambdaApiGateway",
|
|
19
19
|
"LambdaApiGatewayWebSocket",
|
|
20
|
-
"
|
|
20
|
+
"LambdaInvoke",
|
|
21
21
|
"LambdaSns",
|
|
22
|
-
"
|
|
22
|
+
"LambdaSqsStandard",
|
|
23
23
|
]
|
|
@@ -12,8 +12,9 @@ class CliWebSocketMock(cli.Cli):
|
|
|
12
12
|
The LambdaApiGatewayWebSocket context makes it easy to run websocket applications, but testing
|
|
13
13
|
these locally is literally impossible. This context provides a close analogue to the way
|
|
14
14
|
the LambdaApiGatewayWebSocket context works to give some testing capabilities when running
|
|
15
|
-
locally.
|
|
15
|
+
locally. It works identically to `clearskies.contexts.Cli` but you have to provide a
|
|
16
|
+
`connection_id` property in the JSON body.
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
19
|
def __call__(self):
|
|
19
|
-
return self.execute_application(
|
|
20
|
+
return self.execute_application(CLIWebSocketMockInputOutput())
|
|
@@ -65,11 +65,16 @@ class LambdaAlb(Context):
|
|
|
65
65
|
return application(event, context)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
### Context
|
|
68
|
+
### Context Specifics
|
|
69
69
|
|
|
70
70
|
When using this context, two additional named arguments become available to any callables invoked by clearskies:
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
| Name | Type | Description |
|
|
74
|
+
|:-------------:|:----------------:|----------------------------------|
|
|
75
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
76
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
77
|
+
```
|
|
73
78
|
"""
|
|
74
79
|
|
|
75
80
|
def __call__(self, event: dict[str, Any], context: dict[str, Any]) -> Any: # type: ignore[override]
|
|
@@ -59,19 +59,23 @@ class LambdaApiGateway(Context):
|
|
|
59
59
|
return application(event, context)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
### Context
|
|
62
|
+
### Context Specifics
|
|
63
63
|
|
|
64
64
|
When using this context, a number of additional named arguments become available to any callables invoked by
|
|
65
65
|
clearskies:
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
```
|
|
68
|
+
| Name | Type | Description |
|
|
69
|
+
|:-------------:|:----------------:|----------------------------------|
|
|
70
|
+
| ` event` | `dict[str, Any]` | The lambda `event` object |
|
|
71
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
72
|
+
| `resource` | `str` | The route resource |
|
|
73
|
+
| `stage` | `str` | The stage of the lambda function |
|
|
74
|
+
| `request_id` | `str` | The AWS request id for the call |
|
|
75
|
+
| `api_id` | `str` | The id of the API |
|
|
76
|
+
| `api_version` | `str` | "v1" or "v2" |
|
|
77
|
+
```
|
|
74
78
|
"""
|
|
75
79
|
|
|
76
|
-
def __call__(self, event: dict[str, Any], context: dict[str, Any]) -> Any: # type: ignore[override]
|
|
80
|
+
def __call__(self, event: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: # type: ignore[override]
|
|
77
81
|
return self.execute_application(LambdaApiGatewayInputOutput(event, context))
|
|
@@ -37,7 +37,7 @@ class LambdaApiGatewayWebSocket(Context):
|
|
|
37
37
|
having to handle such things themselves, since the typical standards of web frameworks won't match up. In
|
|
38
38
|
the case of routing with an API Gateway, it has its own suggested standard of setting a routekey where the
|
|
39
39
|
API gateway will check for an application-defined route parameter in the request body and use this to route
|
|
40
|
-
to an appropriate lambda. With clearskies, you can also use the `clearskies.endpoints.
|
|
40
|
+
to an appropriate lambda. With clearskies, you can also use the `clearskies.endpoints.BodyParameterRouting`
|
|
41
41
|
to accomplish the same.
|
|
42
42
|
|
|
43
43
|
With a websocket through API Gateway, headers are available during the `on_connect` phase, so you can always
|
|
@@ -51,7 +51,29 @@ class LambdaApiGatewayWebSocket(Context):
|
|
|
51
51
|
there is a base model class in `clearskies_aws.models.WebSocketConnectionModel`. Check the documentation for
|
|
52
52
|
this class to understand how this is managed and see a "starter" websocket application.
|
|
53
53
|
|
|
54
|
+
### Context Specifics
|
|
55
|
+
|
|
56
|
+
The following parameters are made available by name to any function invoked by clearskies when using
|
|
57
|
+
this context:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
| Name | Type | Description |
|
|
61
|
+
|:---------------:|:----------------:|--------------------------------------------------|
|
|
62
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
63
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
64
|
+
| `connection_id` | `str` | The Connection ID |
|
|
65
|
+
| `route_key` | `str` | The value of the route key, as determined by AWS |
|
|
66
|
+
| `stage` | `str` | The stage of the lambda function |
|
|
67
|
+
| `request_id` | `str` | The AWS request id for the call |
|
|
68
|
+
| `api_id` | `str` | The id of the API |
|
|
69
|
+
| `domain_name` | `str` | The domain name |
|
|
70
|
+
| `event_type` | `str` | One of "MESSAGE", "CONNECT", or "DISCONNECT" |
|
|
71
|
+
| `connected_at` | `str` | The connection time |
|
|
72
|
+
```
|
|
73
|
+
|
|
54
74
|
"""
|
|
55
75
|
|
|
56
|
-
def __call__(
|
|
76
|
+
def __call__(
|
|
77
|
+
self, event: dict[str, Any], context: dict[str, Any], url: str = "", request_method: str = ""
|
|
78
|
+
) -> dict[str, Any]: # type: ignore[override]
|
|
57
79
|
return self.execute_application(LambdaApiGatewayWebSocketInputOutput(event, context, url))
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.authentication import Public
|
|
6
|
+
from clearskies.contexts.context import Context
|
|
7
|
+
|
|
8
|
+
from clearskies_aws.input_outputs import LambdaInvoke as LambdaInvokeInputOutput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LambdaInvoke(Context):
|
|
12
|
+
"""
|
|
13
|
+
Execute a lambda directly.
|
|
14
|
+
|
|
15
|
+
This context is used when your clearskies application is running in a lambda that is executed
|
|
16
|
+
directly by some variation of `aws lambda invoke`. For this context, the `event` object passed
|
|
17
|
+
to the lambda handler becomes the request body in the clearskies application. Note that, unlike
|
|
18
|
+
other lambda execution strategies (ALB, Api Gateway, etc...) the event object is exactly equal
|
|
19
|
+
to the body sent in with the lambda function.
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
|
|
23
|
+
Here's a simple example:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
import clearskies
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def my_function(request_data):
|
|
30
|
+
return request_data
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
lambda_invoke = clearskies_aws.contexts.LambdaInvoke(
|
|
34
|
+
clearskies.endpoints.Callable(
|
|
35
|
+
my_function,
|
|
36
|
+
return_standard_response=False,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def lambda_handler(event, context):
|
|
42
|
+
return lambda_invoke(event, context)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can attach this to a lambda and might invoke it like so, with the following response:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ aws lambda invoke --function-name [function_name] --cli-binary-format raw-in-base64-out --payload '{"some":"data"}' | jq
|
|
49
|
+
{ "Payload": {"some": "data"} }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Invoking a lambda doesn't happen from an http context, so there is no URL/request method/headers/etc.
|
|
53
|
+
This typically means that clearskies applications in this context don't do routing and don't have
|
|
54
|
+
authentication configured. If you wanted to though, you could use `clearskies.endpoints.BodyParameterRouting`
|
|
55
|
+
to setup some basic routing and let the invoking client choose a route by providing a parameter in the
|
|
56
|
+
payload passed to lambda invoke.
|
|
57
|
+
|
|
58
|
+
You can pass a URL/request method into the context when you invoke it, which is often used to simplify
|
|
59
|
+
configuration: you can setup a standard clearskies applications with multiple endpoints, and then
|
|
60
|
+
use that one application in multiple lambdas, specifying which endpoint to call for each lambda.
|
|
61
|
+
This can also be helpful if you already have a clearskies application prepared for a standard http context
|
|
62
|
+
and want to execute some subset of those endpoints in a lambda invoke context. It looks something like this:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
import clearskies
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def some_function(request_data):
|
|
69
|
+
return request_data
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def some_other_function(request_data):
|
|
73
|
+
return request_data
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def something_else(request_data):
|
|
77
|
+
return request_data
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
lambda_invoke = clearskies_aws.contexts.LambdaInvoke(
|
|
81
|
+
clearskies.endpoints.EndpointGroup(
|
|
82
|
+
[
|
|
83
|
+
clearskies.endpoints.Callable(
|
|
84
|
+
some_function,
|
|
85
|
+
url="some_function",
|
|
86
|
+
),
|
|
87
|
+
clearskies.endpoints.Callable(
|
|
88
|
+
some_other_function,
|
|
89
|
+
url="some_other_function",
|
|
90
|
+
),
|
|
91
|
+
clearskies.endpoints.Callable(
|
|
92
|
+
something_else,
|
|
93
|
+
url="something_else",
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def some_function_handler(event, context):
|
|
101
|
+
return lambda_invoke(event, context, url="some_function")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def some_other_function_handler(event, context):
|
|
105
|
+
return lambda_invoke(event, context, url="some_other_function")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def something_else_handler(event, context):
|
|
109
|
+
return lambda_invoke(event, context, url="something_else")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Context Specifics
|
|
113
|
+
|
|
114
|
+
When using the lambda_invoke context, it exposes a few context specific parameters which can be injected into
|
|
115
|
+
any function called by clearskies:
|
|
116
|
+
|
|
117
|
+
| Name | Type | Description |
|
|
118
|
+
|:------------------:|:----------------:|:-------------------------------:|
|
|
119
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
120
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
121
|
+
| `invocation_type` | `str` | Always `"direct"` |
|
|
122
|
+
| `function_name` | `str` | The name of the lambda function |
|
|
123
|
+
| `function_version` | `str` | The function version |
|
|
124
|
+
| `request_id` | `str` | The AWS request id for the call |
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __call__(
|
|
129
|
+
self, event: dict[str, Any], context: dict[str, Any], request_method: str = "", url: str = ""
|
|
130
|
+
) -> dict[str, Any]: # type: ignore[override]
|
|
131
|
+
return self.execute_application(
|
|
132
|
+
LambdaInvokeInputOutput(
|
|
133
|
+
event,
|
|
134
|
+
context,
|
|
135
|
+
request_method=request_method,
|
|
136
|
+
url=url,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
3
5
|
from clearskies.authentication import Public
|
|
4
6
|
from clearskies.contexts.context import Context
|
|
5
7
|
|
|
@@ -7,12 +9,116 @@ from clearskies_aws.input_outputs import LambdaSns as LambdaSnsInputOutput
|
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class LambdaSns(Context):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
"""
|
|
13
|
+
Execute a clearskies application when attached to a lambda triggered by SNS.
|
|
14
|
+
|
|
15
|
+
This one is very straight-forward: just attach your clearskies application to work with an
|
|
16
|
+
SNS-triggered lambda. `request_data` provided to the clearskies application will be the
|
|
17
|
+
message sent to the SNS. Since this is no longer an http context, the various http parameters
|
|
18
|
+
(url, request method, headers, and even responses) do not exist. Routing won't exist unless
|
|
19
|
+
you use `clearskies.endpoints.BodyParameterRouting` and include the route parameter in your
|
|
20
|
+
SNS message body.
|
|
21
|
+
|
|
22
|
+
### Usage
|
|
23
|
+
|
|
24
|
+
Here's a simple example:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
import clearskies
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def my_function(request_data):
|
|
31
|
+
print(request_data)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
lambda_invoke = clearskies_aws.contexts.LambdaInvoke(
|
|
35
|
+
clearskies.endpoints.Callable(
|
|
36
|
+
my_function,
|
|
37
|
+
return_standard_response=False,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def lambda_handler(event, context):
|
|
43
|
+
return lambda_invoke(event, context)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Note the lack of a return value. You can return a value if you want, but it will be ignored
|
|
47
|
+
because SNS has no concept of a return response.
|
|
48
|
+
|
|
49
|
+
If you have a number of Lambda/SNS handlers, you can bundle them together for ease-of-management
|
|
50
|
+
and specify the URL when you invoke them:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
import clearskies
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def some_function(request_data):
|
|
57
|
+
return request_data
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def some_other_function(request_data):
|
|
61
|
+
return request_data
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def something_else(request_data):
|
|
65
|
+
return request_data
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
lambda_invoke = clearskies_aws.contexts.LambdaSns(
|
|
69
|
+
clearskies.endpoints.EndpointGroup(
|
|
70
|
+
[
|
|
71
|
+
clearskies.endpoints.Callable(
|
|
72
|
+
some_function,
|
|
73
|
+
url="some_function",
|
|
74
|
+
),
|
|
75
|
+
clearskies.endpoints.Callable(
|
|
76
|
+
some_other_function,
|
|
77
|
+
url="some_other_function",
|
|
78
|
+
),
|
|
79
|
+
clearskies.endpoints.Callable(
|
|
80
|
+
something_else,
|
|
81
|
+
url="something_else",
|
|
82
|
+
),
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def some_function_handler(event, context):
|
|
89
|
+
return lambda_invoke(event, context, url="some_function")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def some_other_function_handler(event, context):
|
|
93
|
+
return lambda_invoke(event, context, url="some_other_function")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def something_else_handler(event, context):
|
|
97
|
+
return lambda_invoke(event, context, url="something_else")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Context Specifics
|
|
101
|
+
|
|
102
|
+
When you use the LambdaSns context, it makes the following named parameters available
|
|
103
|
+
to any callable that is invoked by clearskies:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
| Name | Type | Description |
|
|
107
|
+
|:------------:|:----------------:|------------------------------------------------|
|
|
108
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
109
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
110
|
+
| `message_id` | `str` | The AWS message id |
|
|
111
|
+
| `topic_arn` | `str` | The ARN of the SNS topic that sent the message |
|
|
112
|
+
| `subject` | `str` | Any subject attached to the SNS message |
|
|
113
|
+
| `timestamp` | `str` | The timestamp when the message was sent |
|
|
114
|
+
```
|
|
115
|
+
"""
|
|
13
116
|
|
|
117
|
+
def __call__(self, event: dict[str, Any], context: dict[str, Any], request_method: str = "", url: str = ""): # type: ignore[override]
|
|
14
118
|
try:
|
|
15
|
-
return self.execute_application(
|
|
119
|
+
return self.execute_application(
|
|
120
|
+
LambdaSnsInputOutput(event, context, request_method=request_method, url=url)
|
|
121
|
+
)
|
|
16
122
|
except Exception as e:
|
|
17
123
|
print("Failed message " + event["Records"][0]["Sns"]["MessageId"] + ". Error error: " + str(e))
|
|
18
124
|
raise e
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import traceback
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from clearskies.authentication import Public
|
|
7
|
+
from clearskies.contexts.context import Context
|
|
8
|
+
|
|
9
|
+
from clearskies_aws.input_outputs import LambdaSqsStandard as LambdaSqsStandardInputOutput
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LambdaSqsStandard(Context):
|
|
13
|
+
"""
|
|
14
|
+
Process messages from an SQS Standard Queue with Lambda.
|
|
15
|
+
|
|
16
|
+
Use this context when your application lives in a Lambda and is attached to an SQS standard
|
|
17
|
+
queue. Lambda always uses batch processing in this case, and will invoke your clearskies application
|
|
18
|
+
with a batch of messags. This clearskies context will then in turn invoke your application once
|
|
19
|
+
for every batched message. As a result, `request_data` will contain the contents of an individual message
|
|
20
|
+
from the queue, rather than the original group of batched events from Lambda. If any exception is thrown,
|
|
21
|
+
every other message in the queue will still be sent to your application, and clearskies will inform
|
|
22
|
+
AWS that the message and question failed to process.
|
|
23
|
+
|
|
24
|
+
### Usage
|
|
25
|
+
|
|
26
|
+
Here's a very simple example:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
import clearskies
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def some_function(request_data):
|
|
33
|
+
return print(request_data)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
lambda_sqs = clearskies_aws.contexts.LambdaSqsStandard(
|
|
37
|
+
clearskies.endpoints.Callable(
|
|
38
|
+
some_function,
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def lambda_handler(event, context):
|
|
44
|
+
return lambda_sqs(event, context)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`lambda_handler` would then be attached to your lambda function, which is attached to some standard SQS.
|
|
48
|
+
|
|
49
|
+
Like the other lambda contexts which don't exist in an HTTP world, you can also attach a clearskies application
|
|
50
|
+
with routing and hard-code the path to invoke inside the lambda handler itself. This is handy if you have
|
|
51
|
+
a few related lambdas with similar configuration (since you only have to build a single application) or if
|
|
52
|
+
you have an application that already exists and you want to invoke some specific endpoint with an SQS:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
import clearskies
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def some_function(request_data):
|
|
59
|
+
return request_data
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def some_other_function(request_data):
|
|
63
|
+
return request_data
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def something_else(request_data):
|
|
67
|
+
return request_data
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
lambda_invoke = clearskies_aws.contexts.LambdaSqsStandard(
|
|
71
|
+
clearskies.endpoints.EndpointGroup(
|
|
72
|
+
[
|
|
73
|
+
clearskies.endpoints.Callable(
|
|
74
|
+
some_function,
|
|
75
|
+
url="some_function",
|
|
76
|
+
),
|
|
77
|
+
clearskies.endpoints.Callable(
|
|
78
|
+
some_other_function,
|
|
79
|
+
url="some_other_function",
|
|
80
|
+
),
|
|
81
|
+
clearskies.endpoints.Callable(
|
|
82
|
+
something_else,
|
|
83
|
+
url="something_else",
|
|
84
|
+
),
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def some_function_handler(event, context):
|
|
91
|
+
return lambda_invoke(event, context, url="some_function")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def some_other_function_handler(event, context):
|
|
95
|
+
return lambda_invoke(event, context, url="some_other_function")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def something_else_handler(event, context):
|
|
99
|
+
return lambda_invoke(event, context, url="something_else")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Context Specifics
|
|
103
|
+
|
|
104
|
+
When using this context, the following named parameters become available to inject into any callable
|
|
105
|
+
invoked by clearskies:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
| Name | Type | Description |
|
|
109
|
+
|:---------------------------:|:----------------:|--------------------------------------------------------|
|
|
110
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
111
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
112
|
+
| `message_id` | `str` | The AWS message id |
|
|
113
|
+
| `receipt_handle` | `str` | The receipt handle |
|
|
114
|
+
| `source_arn` | `str` | The ARN of the SQS the lambda is receiving events from |
|
|
115
|
+
| `sent_timestamp` | `str` | The timestamp when the message was sent |
|
|
116
|
+
| `approximate_receive_count` | `str` | The approximate receive count |
|
|
117
|
+
| `message_attributes` | `dict[str, Any]` | The message attributes |
|
|
118
|
+
| `record` | `dict[str, Any]` | The full record of the message being processed |
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __call__(
|
|
124
|
+
self, event: dict[str, Any], context: dict[str, Any], url: str = "", request_method: str = ""
|
|
125
|
+
) -> dict[str, Any]: # type: ignore[override]
|
|
126
|
+
item_failures = []
|
|
127
|
+
for record in event["Records"]:
|
|
128
|
+
try:
|
|
129
|
+
self.execute_application(
|
|
130
|
+
LambdaSqsStandardInputOutput(record, event, context, url=url, request_method=request_method)
|
|
131
|
+
)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
item_failures.append({"itemIdentifier": record["messageId"]})
|
|
134
|
+
|
|
135
|
+
if item_failures:
|
|
136
|
+
return {
|
|
137
|
+
"batchItemFailures": item_failures,
|
|
138
|
+
}
|
|
139
|
+
return {}
|
|
@@ -6,7 +6,7 @@ from clearskies_aws.input_outputs.lambda_api_gateway import LambdaApiGateway
|
|
|
6
6
|
from clearskies_aws.input_outputs.lambda_api_gateway_web_socket import (
|
|
7
7
|
LambdaApiGatewayWebSocket,
|
|
8
8
|
)
|
|
9
|
-
from clearskies_aws.input_outputs.
|
|
9
|
+
from clearskies_aws.input_outputs.lambda_invoke import LambdaInvoke
|
|
10
10
|
from clearskies_aws.input_outputs.lambda_sns import LambdaSns
|
|
11
11
|
from clearskies_aws.input_outputs.lambda_sqs_standard import LambdaSqsStandard
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ __all__ = [
|
|
|
15
15
|
"LambdaApiGateway",
|
|
16
16
|
"LambdaApiGatewayWebSocket",
|
|
17
17
|
"LambdaAlb",
|
|
18
|
-
"
|
|
18
|
+
"LambdaInvoke",
|
|
19
19
|
"LambdaSns",
|
|
20
20
|
"LambdaSqsStandard",
|
|
21
21
|
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from clearskies.configs import String
|
|
@@ -24,7 +25,7 @@ class LambdaApiGatewayWebSocket(lambda_input_output.LambdaInputOutput):
|
|
|
24
25
|
request_context = event.get("requestContext", {})
|
|
25
26
|
|
|
26
27
|
# WebSocket uses route_key, but doesn't have either a route or a method
|
|
27
|
-
self.route_key = request_context.get("routeKey", "")
|
|
28
|
+
self.route_key = request_context.get("routeKey", "GET")
|
|
28
29
|
self.request_method = self.route_key.upper() # For compatibility
|
|
29
30
|
|
|
30
31
|
# WebSocket connection ID
|
|
@@ -47,12 +48,13 @@ class LambdaApiGatewayWebSocket(lambda_input_output.LambdaInputOutput):
|
|
|
47
48
|
|
|
48
49
|
raise ValueError("Unable to find the client ip inside the API Gateway")
|
|
49
50
|
|
|
50
|
-
def respond(self, body: Any, status_code: int = 200) ->
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
def respond(self, body: Any, status_code: int = 200) -> None:
|
|
52
|
+
# since there is no response to the client, we want to raise an exception for any non-200 status code so
|
|
53
|
+
# the lambda execution itself will be marked as a failure.
|
|
54
|
+
if status_code > 299:
|
|
55
|
+
if not isinstance(body, str):
|
|
56
|
+
body = json.dumps(body)
|
|
57
|
+
raise Exception(f"Non-200 Status code returned by application: {status_code}. Response: '{body}'")
|
|
56
58
|
|
|
57
59
|
def context_specifics(self) -> dict[str, Any]:
|
|
58
60
|
"""Provide WebSocket specific context data."""
|
|
@@ -20,7 +20,9 @@ class LambdaInputOutput(InputOutput):
|
|
|
20
20
|
_cached_body = None
|
|
21
21
|
_body_was_cached = False
|
|
22
22
|
|
|
23
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self, event: dict[str, Any], context: dict[str, Any], url: str | None = "", request_method: str | None = ""
|
|
25
|
+
):
|
|
24
26
|
# Store event and context
|
|
25
27
|
self.event = event
|
|
26
28
|
self.context = context
|
|
@@ -28,7 +30,7 @@ class LambdaInputOutput(InputOutput):
|
|
|
28
30
|
# Initialize the base class
|
|
29
31
|
super().__init__()
|
|
30
32
|
|
|
31
|
-
def respond(self, body: Any, status_code: int = 200) -> dict[str, Any]:
|
|
33
|
+
def respond(self, body: Any, status_code: int = 200) -> dict[str, Any] | None:
|
|
32
34
|
"""Create standard Lambda HTTP response format."""
|
|
33
35
|
if "content-type" not in self.response_headers:
|
|
34
36
|
self.response_headers.content_type = "application/json; charset=UTF-8"
|
|
@@ -9,25 +9,28 @@ from clearskies.input_outputs import Headers
|
|
|
9
9
|
from clearskies_aws.input_outputs import lambda_input_output
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class LambdaInvoke(lambda_input_output.LambdaInputOutput):
|
|
13
13
|
"""Direct Lambda invocation specific input/output handler."""
|
|
14
14
|
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
event: dict[str, Any],
|
|
18
18
|
context: dict[str, Any],
|
|
19
|
-
|
|
19
|
+
request_method: str = "",
|
|
20
20
|
url: str = "",
|
|
21
21
|
):
|
|
22
22
|
# Call parent constructor
|
|
23
23
|
super().__init__(event, context)
|
|
24
24
|
|
|
25
25
|
# Direct invocation specific initialization
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
if url:
|
|
27
|
+
self.path = url
|
|
28
|
+
else:
|
|
29
|
+
self.supports_url = True
|
|
30
|
+
if request_method:
|
|
31
|
+
self.request_method = request_method.upper()
|
|
32
|
+
else:
|
|
33
|
+
self.supports_request_method = False
|
|
31
34
|
|
|
32
35
|
# Direct invocations don't have headers
|
|
33
36
|
self.request_headers = Headers({})
|
|
@@ -12,16 +12,21 @@ from clearskies_aws.input_outputs import lambda_input_output
|
|
|
12
12
|
class LambdaSns(lambda_input_output.LambdaInputOutput):
|
|
13
13
|
"""SNS specific Lambda input/output handler."""
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
record: dict[str, Any]
|
|
16
|
+
|
|
17
|
+
def __init__(self, event: dict, context: dict[str, Any], url: str = "", request_method: str = ""):
|
|
16
18
|
# Call parent constructor
|
|
17
19
|
super().__init__(event, context)
|
|
18
20
|
|
|
19
21
|
# SNS specific initialization
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if url:
|
|
23
|
+
self.path = url
|
|
24
|
+
else:
|
|
25
|
+
self.supports_url = False
|
|
26
|
+
if request_method:
|
|
27
|
+
self.request_method = request_method.upper()
|
|
28
|
+
else:
|
|
29
|
+
self.supports_request_method = False
|
|
25
30
|
|
|
26
31
|
# SNS events don't have headers
|
|
27
32
|
self.request_headers = Headers({})
|
|
@@ -29,24 +34,29 @@ class LambdaSns(lambda_input_output.LambdaInputOutput):
|
|
|
29
34
|
# Extract SNS message from event
|
|
30
35
|
try:
|
|
31
36
|
record = event["Records"][0]["Sns"]["Message"]
|
|
32
|
-
self.
|
|
37
|
+
self.record = json.loads(record)
|
|
33
38
|
except (KeyError, IndexError, json.JSONDecodeError) as e:
|
|
34
39
|
raise ClientError(
|
|
35
40
|
"The message from AWS was not a valid SNS event with serialized JSON. "
|
|
36
41
|
"The lambda_sns context for clearskies only accepts serialized JSON."
|
|
37
42
|
)
|
|
38
43
|
|
|
39
|
-
def respond(self, body: Any, status_code: int = 200) ->
|
|
40
|
-
"""SNS
|
|
41
|
-
|
|
44
|
+
def respond(self, body: Any, status_code: int = 200) -> None:
|
|
45
|
+
"""Respond to the client, but SNS has no client."""
|
|
46
|
+
# since there is no response to the client, we want to raise an exception for any non-200 status code so
|
|
47
|
+
# the lambda execution itself will be marked as a failure.
|
|
48
|
+
if status_code > 299:
|
|
49
|
+
if not isinstance(body, str):
|
|
50
|
+
body = json.dumps(body)
|
|
51
|
+
raise Exception(f"Non-200 Status code returned by application: {status_code}. Response: '{body}'")
|
|
42
52
|
|
|
43
53
|
def get_body(self) -> str:
|
|
44
54
|
"""Get the SNS message as a JSON string."""
|
|
45
|
-
return json.dumps(self.
|
|
55
|
+
return json.dumps(self.record) if self.record else ""
|
|
46
56
|
|
|
47
57
|
def has_body(self) -> bool:
|
|
48
58
|
"""Check if SNS message exists."""
|
|
49
|
-
return bool(self.
|
|
59
|
+
return bool(self.record)
|
|
50
60
|
|
|
51
61
|
def get_client_ip(self) -> str:
|
|
52
62
|
"""SNS events don't have client IP information."""
|
|
@@ -66,14 +76,13 @@ class LambdaSns(lambda_input_output.LambdaInputOutput):
|
|
|
66
76
|
|
|
67
77
|
return {
|
|
68
78
|
**super().context_specifics(),
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"sns_message": self._record,
|
|
79
|
+
"message_id": sns_record.get("MessageId"),
|
|
80
|
+
"topic_arn": sns_record.get("TopicArn"),
|
|
81
|
+
"subject": sns_record.get("Subject"),
|
|
82
|
+
"timestamp": sns_record.get("Timestamp"),
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
@property
|
|
77
86
|
def request_data(self) -> dict[str, Any] | list[Any] | None:
|
|
78
87
|
"""Return the SNS message data directly."""
|
|
79
|
-
return self.
|
|
88
|
+
return self.record
|
|
@@ -17,31 +17,44 @@ class LambdaSqsStandard(lambda_input_output.LambdaInputOutput):
|
|
|
17
17
|
path = String(default="/")
|
|
18
18
|
|
|
19
19
|
def __init__(
|
|
20
|
-
self,
|
|
20
|
+
self,
|
|
21
|
+
record: dict[str, Any],
|
|
22
|
+
event: dict[str, Any],
|
|
23
|
+
context: dict[str, Any],
|
|
24
|
+
url: str = "",
|
|
25
|
+
request_method: str = "",
|
|
21
26
|
):
|
|
22
27
|
# Call parent constructor with the full event
|
|
23
28
|
super().__init__(event, context)
|
|
24
29
|
|
|
25
30
|
# Store the individual SQS record
|
|
26
|
-
self.record =
|
|
27
|
-
print("SQS record:", self.record)
|
|
31
|
+
self.record = record
|
|
28
32
|
# SQS specific initialization
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
if url:
|
|
34
|
+
self.path = url
|
|
35
|
+
else:
|
|
36
|
+
self.supports_url = False
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
if request_method:
|
|
39
|
+
self.request_method = request_method.upper()
|
|
40
|
+
else:
|
|
41
|
+
self.supports_request_method = False
|
|
34
42
|
|
|
35
43
|
# SQS events don't have headers
|
|
36
44
|
self.request_headers = Headers({})
|
|
37
45
|
|
|
38
|
-
def respond(self, body: Any, status_code: int = 200) ->
|
|
39
|
-
"""SQS
|
|
40
|
-
|
|
46
|
+
def respond(self, body: Any, status_code: int = 200) -> None:
|
|
47
|
+
"""Respond to the client, but SQS has no client."""
|
|
48
|
+
# since there is no response to the client, we want to raise an exception for any non-200 status code so
|
|
49
|
+
# the lambda execution itself will be marked as a failure.
|
|
50
|
+
if status_code > 299:
|
|
51
|
+
if not isinstance(body, str):
|
|
52
|
+
body = json.dumps(body)
|
|
53
|
+
raise Exception(f"Non-200 Status code returned by application: {status_code}. Response: '{body}'")
|
|
41
54
|
|
|
42
55
|
def get_body(self) -> str:
|
|
43
|
-
"""Get
|
|
44
|
-
return
|
|
56
|
+
"""Get request body with base64 decoding if needed."""
|
|
57
|
+
return self.record["body"]
|
|
45
58
|
|
|
46
59
|
def has_body(self) -> bool:
|
|
47
60
|
"""Check if SQS message has a body."""
|
|
@@ -63,22 +76,11 @@ class LambdaSqsStandard(lambda_input_output.LambdaInputOutput):
|
|
|
63
76
|
"""Provide SQS specific context data."""
|
|
64
77
|
return {
|
|
65
78
|
**super().context_specifics(),
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
79
|
+
"message_id": self.record.get("messageId"),
|
|
80
|
+
"receipt_handle": self.record.get("receiptHandle"),
|
|
81
|
+
"source_arn": self.record.get("eventSourceARN"),
|
|
82
|
+
"sent_timestamp": self.record.get("attributes", {}).get("SentTimestamp"),
|
|
83
|
+
"approximate_receive_count": self.record.get("attributes", {}).get("ApproximateReceiveCount"),
|
|
84
|
+
"message_attributes": self.record.get("messageAttributes", {}),
|
|
85
|
+
"record": self.record,
|
|
73
86
|
}
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def request_data(self) -> dict[str, Any]:
|
|
77
|
-
"""Return the SQS message body as parsed JSON."""
|
|
78
|
-
body = self.get_body()
|
|
79
|
-
if not body:
|
|
80
|
-
return {}
|
|
81
|
-
try:
|
|
82
|
-
return json.loads(body)
|
|
83
|
-
except json.JSONDecodeError:
|
|
84
|
-
raise ClientError("SQS message body was not valid JSON")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from clearskies_aws.models.web_socket_connection_model import WebSocketConnectionModel
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from clearskies.authentication import Public
|
|
6
|
-
from clearskies.contexts.context import Context
|
|
7
|
-
|
|
8
|
-
from clearskies_aws.input_outputs import LambdaInvocation as LambdaInvocationInputOutput
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class LambdaInvocation(Context):
|
|
12
|
-
|
|
13
|
-
def __call__(self, event: dict[str, Any], context: dict[str, Any]) -> Any: # type: ignore[override]
|
|
14
|
-
return self.execute_application(
|
|
15
|
-
LambdaInvocationInputOutput(
|
|
16
|
-
event,
|
|
17
|
-
context,
|
|
18
|
-
)
|
|
19
|
-
)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import traceback
|
|
4
|
-
|
|
5
|
-
from clearskies.authentication import Public
|
|
6
|
-
from clearskies.contexts.context import Context
|
|
7
|
-
|
|
8
|
-
from clearskies_aws.input_outputs import LambdaSqsStandard as LambdaSqsStandardInputOutput
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class LambdaSqsStandardPartialBatch(Context):
|
|
12
|
-
def __call__(self, event, context, url="", method="POST"):
|
|
13
|
-
item_failures = []
|
|
14
|
-
for record in event["Records"]:
|
|
15
|
-
print("Processing message " + record["messageId"], record["body"])
|
|
16
|
-
try:
|
|
17
|
-
self.execute_application(
|
|
18
|
-
LambdaSqsStandardInputOutput(record["body"], event, context, url=url, method=method)
|
|
19
|
-
)
|
|
20
|
-
except Exception as e:
|
|
21
|
-
print("Failed message " + record["messageId"] + " being returned for retry. Error error: " + str(e))
|
|
22
|
-
traceback.print_tb(e.__traceback__)
|
|
23
|
-
item_failures.append({"itemIdentifier": record["messageId"]})
|
|
24
|
-
|
|
25
|
-
if item_failures:
|
|
26
|
-
return {
|
|
27
|
-
"batchItemFailures": item_failures,
|
|
28
|
-
}
|
|
29
|
-
return {}
|
|
File without changes
|