Skip to content

Commit b4d0baa

Browse files
author
Michael Brewer
authored
feat(idempotent): Add support for jmespath_options (#302)
* feat(idempotent): Add support for jmespath_options Like for the validator, idempotent utility should allow for extracting an idempotent key using custom functions * test(idempotent): Add test for support jmepath custom function * chore: Fix test by using fixtures * refactor(jmespath_functions): Move into shared
1 parent dfc928f commit b4d0baa

File tree

5 files changed

+62
-2
lines changed

5 files changed

+62
-2
lines changed

Diff for: aws_lambda_powertools/utilities/idempotency/persistence/base.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import jmespath
1515

1616
from aws_lambda_powertools.shared.cache_dict import LRUDict
17+
from aws_lambda_powertools.shared.jmespath_functions import PowertoolsFunctions
1718
from aws_lambda_powertools.shared.json_encoder import Encoder
1819
from aws_lambda_powertools.utilities.idempotency.exceptions import (
1920
IdempotencyInvalidStatusError,
@@ -115,6 +116,7 @@ def __init__(
115116
local_cache_max_items: int = 256,
116117
hash_function: str = "md5",
117118
raise_on_no_idempotency_key: bool = False,
119+
jmespath_options: Dict = None,
118120
) -> None:
119121
"""
120122
Initialize the base persistence layer
@@ -135,6 +137,8 @@ def __init__(
135137
Function to use for calculating hashes, by default md5.
136138
raise_on_no_idempotency_key: bool, optional
137139
Raise exception if no idempotency key was found in the request, by default False
140+
jmespath_options : Dict
141+
Alternative JMESPath options to be included when filtering expr
138142
"""
139143
self.event_key_jmespath = event_key_jmespath
140144
if self.event_key_jmespath:
@@ -149,6 +153,9 @@ def __init__(
149153
self.payload_validation_enabled = True
150154
self.hash_function = getattr(hashlib, hash_function)
151155
self.raise_on_no_idempotency_key = raise_on_no_idempotency_key
156+
if not jmespath_options:
157+
jmespath_options = {"custom_functions": PowertoolsFunctions()}
158+
self.jmespath_options = jmespath_options
152159

153160
def _get_hashed_idempotency_key(self, lambda_event: Dict[str, Any]) -> str:
154161
"""
@@ -166,8 +173,11 @@ def _get_hashed_idempotency_key(self, lambda_event: Dict[str, Any]) -> str:
166173
167174
"""
168175
data = lambda_event
176+
169177
if self.event_key_jmespath:
170-
data = self.event_key_compiled_jmespath.search(lambda_event)
178+
data = self.event_key_compiled_jmespath.search(
179+
lambda_event, options=jmespath.Options(**self.jmespath_options)
180+
)
171181

172182
if self.is_missing_idempotency_key(data):
173183
warnings.warn(f"No value found for idempotency_key. jmespath: {self.event_key_jmespath}")

Diff for: aws_lambda_powertools/utilities/validation/base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import jmespath
66
from jmespath.exceptions import LexerError
77

8+
from aws_lambda_powertools.shared.jmespath_functions import PowertoolsFunctions
9+
810
from .exceptions import InvalidEnvelopeExpressionError, InvalidSchemaFormatError, SchemaValidationError
9-
from .jmespath_functions import PowertoolsFunctions
1011

1112
logger = logging.getLogger(__name__)
1213

Diff for: tests/functional/idempotency/conftest.py

+18
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
from botocore import stub
1111
from botocore.config import Config
12+
from jmespath import functions
1213

1314
from aws_lambda_powertools.shared.json_encoder import Encoder
1415
from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer
@@ -180,6 +181,23 @@ def persistence_store_with_validation(config, request, default_jmespath):
180181
return persistence_store
181182

182183

184+
@pytest.fixture
185+
def persistence_store_with_jmespath_options(config, request):
186+
class CustomFunctions(functions.Functions):
187+
@functions.signature({"types": ["string"]})
188+
def _func_echo_decoder(self, value):
189+
return value
190+
191+
persistence_store = DynamoDBPersistenceLayer(
192+
table_name=TABLE_NAME,
193+
boto_config=config,
194+
use_local_cache=False,
195+
event_key_jmespath=request.param,
196+
jmespath_options={"custom_functions": CustomFunctions()},
197+
)
198+
return persistence_store
199+
200+
183201
@pytest.fixture
184202
def mock_function():
185203
return mock.MagicMock()

Diff for: tests/functional/idempotency/test_idempotency.py

+31
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
from hashlib import md5
55

6+
import jmespath
67
import pytest
78
from botocore import stub
89

@@ -690,3 +691,33 @@ def test_raise_on_no_idempotency_key(persistence_store):
690691

691692
# THEN raise IdempotencyKeyError error
692693
assert "No data found to create a hashed idempotency_key" in str(excinfo.value)
694+
695+
696+
@pytest.mark.parametrize("persistence_store", [{"use_local_cache": True}], indirect=True)
697+
def test_jmespath_with_powertools_json(persistence_store):
698+
# GIVEN an event_key_jmespath with powertools_json custom function
699+
persistence_store.event_key_jmespath = "[requestContext.authorizer.claims.sub, powertools_json(body).id]"
700+
persistence_store.event_key_compiled_jmespath = jmespath.compile(persistence_store.event_key_jmespath)
701+
sub_attr_value = "cognito_user"
702+
key_attr_value = "some_key"
703+
expected_value = [sub_attr_value, key_attr_value]
704+
api_gateway_proxy_event = {
705+
"requestContext": {"authorizer": {"claims": {"sub": sub_attr_value}}},
706+
"body": json.dumps({"id": key_attr_value}),
707+
}
708+
709+
# WHEN calling _get_hashed_idempotency_key
710+
result = persistence_store._get_hashed_idempotency_key(api_gateway_proxy_event)
711+
712+
# THEN the hashed idempotency key should match the extracted values generated hash
713+
assert result == persistence_store._generate_hash(expected_value)
714+
715+
716+
@pytest.mark.parametrize("persistence_store_with_jmespath_options", ["powertools_json(data).payload"], indirect=True)
717+
def test_custom_jmespath_function_overrides_builtin_functions(persistence_store_with_jmespath_options):
718+
# GIVEN an persistence store with a custom jmespath_options
719+
# AND use a builtin powertools custom function
720+
with pytest.raises(jmespath.exceptions.UnknownFunctionError, match="Unknown function: powertools_json()"):
721+
# WHEN calling _get_hashed_idempotency_key
722+
# THEN raise unknown function
723+
persistence_store_with_jmespath_options._get_hashed_idempotency_key({})

0 commit comments

Comments
 (0)