Skip to content

Commit 3c41f36

Browse files
author
Michael Brewer
committed
feat(trigger): Application load balancer event
Create BaseProxyEvent for some reusable code for the http proxy events Add support for ALB events
1 parent 3811356 commit 3c41f36

File tree

6 files changed

+141
-143
lines changed

6 files changed

+141
-143
lines changed

aws_lambda_powertools/utilities/trigger/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .alb_event import ALBEvent
12
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
23
from .cloud_watch_logs_event import CloudWatchLogsEvent
34
from .dynamo_db_stream_event import DynamoDBStreamEvent
@@ -11,6 +12,7 @@
1112
__all__ = [
1213
"APIGatewayProxyEvent",
1314
"APIGatewayProxyEventV2",
15+
"ALBEvent",
1416
"CloudWatchLogsEvent",
1517
"DynamoDBStreamEvent",
1618
"EventBridgeEvent",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from typing import Dict, List, Optional
2+
3+
from aws_lambda_powertools.utilities.trigger.common import BaseProxyEvent, DictWrapper
4+
5+
6+
class ALBEventRequestContext(DictWrapper):
7+
@property
8+
def elb_target_group_arn(self) -> str:
9+
return self["requestContext"]["elb"]["targetGroupArn"]
10+
11+
12+
class ALBEvent(BaseProxyEvent):
13+
"""Application load balancer event
14+
15+
Documentation:
16+
--------------
17+
- https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html
18+
"""
19+
20+
@property
21+
def request_context(self) -> ALBEventRequestContext:
22+
return ALBEventRequestContext(self)
23+
24+
@property
25+
def http_method(self) -> str:
26+
return self["httpMethod"]
27+
28+
@property
29+
def path(self) -> str:
30+
return self["path"]
31+
32+
@property
33+
def multi_value_query_string_parameters(self) -> Optional[Dict[str, List[str]]]:
34+
return self.get("multiValueQueryStringParameters")
35+
36+
@property
37+
def multi_value_headers(self) -> Optional[Dict[str, List[str]]]:
38+
return self.get("multiValueHeaders")

aws_lambda_powertools/utilities/trigger/api_gateway_proxy_event.py

Lines changed: 3 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Dict, List, Optional
22

3-
from aws_lambda_powertools.utilities.trigger.common import DictWrapper
3+
from aws_lambda_powertools.utilities.trigger.common import BaseProxyEvent, DictWrapper
44

55

66
class APIGatewayEventIdentity(DictWrapper):
@@ -196,7 +196,7 @@ def route_key(self) -> Optional[str]:
196196
return self["requestContext"].get("routeKey")
197197

198198

199-
class APIGatewayProxyEvent(dict):
199+
class APIGatewayProxyEvent(BaseProxyEvent):
200200
"""AWS Lambda proxy V1
201201
202202
Documentation:
@@ -221,18 +221,10 @@ def http_method(self) -> str:
221221
"""The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT."""
222222
return self["httpMethod"]
223223

224-
@property
225-
def headers(self) -> Dict[str, str]:
226-
return self["headers"]
227-
228224
@property
229225
def multi_value_headers(self) -> Dict[str, List[str]]:
230226
return self["multiValueHeaders"]
231227

232-
@property
233-
def query_string_parameters(self) -> Optional[Dict[str, str]]:
234-
return self.get("queryStringParameters")
235-
236228
@property
237229
def multi_value_query_string_parameters(self) -> Optional[Dict[str, List[str]]]:
238230
return self.get("multiValueQueryStringParameters")
@@ -249,47 +241,6 @@ def path_parameters(self) -> Optional[Dict[str, str]]:
249241
def stage_variables(self) -> Optional[Dict[str, str]]:
250242
return self.get("stageVariables")
251243

252-
@property
253-
def body(self) -> Optional[str]:
254-
return self.get("body")
255-
256-
@property
257-
def is_base64_encoded(self) -> bool:
258-
return self["isBase64Encoded"]
259-
260-
def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
261-
"""Get query string value by name
262-
263-
Parameters
264-
----------
265-
name: str
266-
Query string parameter name
267-
default_value: str, optional
268-
Default value if no value was found by name
269-
Returns
270-
-------
271-
str, optional
272-
Query string parameter value
273-
"""
274-
params = self.query_string_parameters
275-
return default_value if params is None else params.get(name, default_value)
276-
277-
def get_header_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
278-
"""Get header value by name
279-
280-
Parameters
281-
----------
282-
name: str
283-
Header name
284-
default_value: str, optional
285-
Default value if no value was found by name
286-
Returns
287-
-------
288-
str, optional
289-
Header value
290-
"""
291-
return self.headers.get(name, default_value)
292-
293244

294245
class RequestContextV2Http(DictWrapper):
295246
@property
@@ -381,7 +332,7 @@ def time_epoch(self) -> int:
381332
return self["requestContext"]["timeEpoch"]
382333

383334

384-
class APIGatewayProxyEventV2(dict):
335+
class APIGatewayProxyEventV2(BaseProxyEvent):
385336
"""AWS Lambda proxy V2 event
386337
387338
Notes:
@@ -418,63 +369,14 @@ def raw_query_string(self) -> str:
418369
def cookies(self) -> Optional[List[str]]:
419370
return self.get("cookies")
420371

421-
@property
422-
def headers(self) -> Dict[str, str]:
423-
return self["headers"]
424-
425-
@property
426-
def query_string_parameters(self) -> Optional[Dict[str, str]]:
427-
return self.get("queryStringParameters")
428-
429372
@property
430373
def request_context(self) -> RequestContextV2:
431374
return RequestContextV2(self)
432375

433-
@property
434-
def body(self) -> Optional[str]:
435-
return self.get("body")
436-
437376
@property
438377
def path_parameters(self) -> Optional[Dict[str, str]]:
439378
return self.get("pathParameters")
440379

441-
@property
442-
def is_base64_encoded(self) -> bool:
443-
return self["isBase64Encoded"]
444-
445380
@property
446381
def stage_variables(self) -> Optional[Dict[str, str]]:
447382
return self.get("stageVariables")
448-
449-
def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
450-
"""Get query string value by name
451-
452-
Parameters
453-
----------
454-
name: str
455-
Query string parameter name
456-
default_value: str, optional
457-
Default value if no value was found by name
458-
Returns
459-
-------
460-
str, optional
461-
Query string parameter value
462-
"""
463-
params = self.query_string_parameters
464-
return default_value if params is None else params.get(name, default_value)
465-
466-
def get_header_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
467-
"""Get header value by name
468-
469-
Parameters
470-
----------
471-
name: str
472-
Header name
473-
default_value: str, optional
474-
Default value if no value was found by name
475-
Returns
476-
-------
477-
str, optional
478-
Header value
479-
"""
480-
return self.headers.get(name, default_value)

aws_lambda_powertools/utilities/trigger/common.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,54 @@ def __getitem__(self, key: str) -> Any:
1111

1212
def get(self, key: str) -> Optional[Any]:
1313
return self._data.get(key)
14+
15+
16+
class BaseProxyEvent(dict):
17+
@property
18+
def headers(self) -> Dict[str, str]:
19+
return self["headers"]
20+
21+
@property
22+
def query_string_parameters(self) -> Optional[Dict[str, str]]:
23+
return self.get("queryStringParameters")
24+
25+
@property
26+
def is_base64_encoded(self) -> bool:
27+
return self.get("isBase64Encoded")
28+
29+
@property
30+
def body(self) -> Optional[str]:
31+
return self.get("body")
32+
33+
def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
34+
"""Get query string value by name
35+
36+
Parameters
37+
----------
38+
name: str
39+
Query string parameter name
40+
default_value: str, optional
41+
Default value if no value was found by name
42+
Returns
43+
-------
44+
str, optional
45+
Query string parameter value
46+
"""
47+
params = self.query_string_parameters
48+
return default_value if params is None else params.get(name, default_value)
49+
50+
def get_header_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
51+
"""Get header value by name
52+
53+
Parameters
54+
----------
55+
name: str
56+
Header name
57+
default_value: str, optional
58+
Default value if no value was found by name
59+
Returns
60+
-------
61+
str, optional
62+
Header value
63+
"""
64+
return self.headers.get(name, default_value)

tests/events/albEvent.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"requestContext": {
3+
"elb": {
4+
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"
5+
}
6+
},
7+
"httpMethod": "GET",
8+
"path": "/lambda",
9+
"queryStringParameters": {
10+
"query": "1234ABCD"
11+
},
12+
"headers": {
13+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
14+
"accept-encoding": "gzip",
15+
"accept-language": "en-US,en;q=0.9",
16+
"connection": "keep-alive",
17+
"host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com",
18+
"upgrade-insecure-requests": "1",
19+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
20+
"x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
21+
"x-forwarded-for": "72.12.164.125",
22+
"x-forwarded-port": "80",
23+
"x-forwarded-proto": "http",
24+
"x-imforwards": "20"
25+
},
26+
"body": "Test",
27+
"isBase64Encoded": false
28+
}

tests/functional/test_lambda_trigger_events.py

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from secrets import compare_digest
55

66
from aws_lambda_powertools.utilities.trigger import (
7+
ALBEvent,
78
APIGatewayProxyEvent,
89
APIGatewayProxyEventV2,
910
CloudWatchLogsEvent,
@@ -23,6 +24,7 @@
2324
PreTokenGenerationTriggerEvent,
2425
UserMigrationTriggerEvent,
2526
)
27+
from aws_lambda_powertools.utilities.trigger.common import BaseProxyEvent
2628
from aws_lambda_powertools.utilities.trigger.dynamo_db_stream_event import (
2729
AttributeValue,
2830
DynamoDBRecordEventName,
@@ -531,11 +533,11 @@ def test_api_gateway_proxy_v2_event():
531533
assert event.stage_variables == event["stageVariables"]
532534

533535

534-
def test_api_gateway_proxy_get_query_string_value():
536+
def test_base_proxy_event_get_query_string_value():
535537
default_value = "default"
536538
set_value = "value"
537539

538-
event = APIGatewayProxyEvent({})
540+
event = BaseProxyEvent({})
539541
value = event.get_query_string_value("test", default_value)
540542
assert value == default_value
541543

@@ -550,49 +552,11 @@ def test_api_gateway_proxy_get_query_string_value():
550552
assert value is None
551553

552554

553-
def test_api_gateway_proxy_v2_get_query_string_value():
555+
def test_base_proxy_event_get_header_value():
554556
default_value = "default"
555557
set_value = "value"
556558

557-
event = APIGatewayProxyEventV2({})
558-
value = event.get_query_string_value("test", default_value)
559-
assert value == default_value
560-
561-
event["queryStringParameters"] = {"test": set_value}
562-
value = event.get_query_string_value("test", default_value)
563-
assert value == set_value
564-
565-
value = event.get_query_string_value("unknown", default_value)
566-
assert value == default_value
567-
568-
value = event.get_query_string_value("unknown")
569-
assert value is None
570-
571-
572-
def test_api_gateway_proxy_get_header_value():
573-
default_value = "default"
574-
set_value = "value"
575-
576-
event = APIGatewayProxyEvent({"headers": {}})
577-
value = event.get_header_value("test", default_value)
578-
assert value == default_value
579-
580-
event["headers"] = {"test": set_value}
581-
value = event.get_header_value("test", default_value)
582-
assert value == set_value
583-
584-
value = event.get_header_value("unknown", default_value)
585-
assert value == default_value
586-
587-
value = event.get_header_value("unknown")
588-
assert value is None
589-
590-
591-
def test_api_gateway_proxy_v2_get_header_value():
592-
default_value = "default"
593-
set_value = "value"
594-
595-
event = APIGatewayProxyEventV2({"headers": {}})
559+
event = BaseProxyEvent({"headers": {}})
596560
value = event.get_header_value("test", default_value)
597561
assert value == default_value
598562

@@ -639,3 +603,16 @@ def test_kinesis_stream_event_json_data():
639603
data = base64.b64encode(bytes(json.dumps(json_value), "utf-8")).decode("utf-8")
640604
event = KinesisStreamEvent({"Records": [{"kinesis": {"data": data}}]})
641605
assert next(event.records).kinesis.data_as_json() == json_value
606+
607+
608+
def test_alb_event():
609+
event = ALBEvent(load_event("albEvent.json"))
610+
assert event.request_context.elb_target_group_arn == event["requestContext"]["elb"]["targetGroupArn"]
611+
assert event.http_method == event["httpMethod"]
612+
assert event.path == event["path"]
613+
assert event.query_string_parameters == event["queryStringParameters"]
614+
assert event.headers == event["headers"]
615+
assert event.multi_value_query_string_parameters == event.get("multiValueQueryStringParameters")
616+
assert event.multi_value_headers == event.get("multiValueHeaders")
617+
assert event.body == event["body"]
618+
assert event.is_base64_encoded == event["isBase64Encoded"]

0 commit comments

Comments
 (0)