Skip to content

Commit

Permalink
refactor(jmespath_utils): deprecate extract_data_from_envelope in fav…
Browse files Browse the repository at this point in the history
…or of query (#4907)

* refactor!(jmespath_utils): deprecate extract_data_from_envelope in jmespath_utils and replace with query

issue: #4218

* Adding deprecation decorator

* Adding test

* Fix highlight

---------

Co-authored-by: Neel Krishna <[email protected]>
Co-authored-by: Leandro Damascena <[email protected]>
  • Loading branch information
3 people authored Aug 7, 2024
1 parent 1a8818d commit a4fdec0
Show file tree
Hide file tree
Showing 16 changed files with 80 additions and 51 deletions.
2 changes: 1 addition & 1 deletion aws_lambda_powertools/logging/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ def decorate(event, context, *args, **kwargs):

if correlation_id_path:
self.set_correlation_id(
jmespath_utils.extract_data_from_envelope(envelope=correlation_id_path, data=event),
jmespath_utils.query(envelope=correlation_id_path, data=event),
)

if log_event:
Expand Down
2 changes: 1 addition & 1 deletion aws_lambda_powertools/utilities/feature_flags/appconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def get_configuration(self) -> Dict[str, Any]:

if self.envelope:
self.logger.debug("Envelope enabled; extracting data from config", extra={"envelope": self.envelope})
config = jmespath_utils.extract_data_from_envelope(
config = jmespath_utils.query(
data=config,
envelope=self.envelope,
jmespath_options=self.jmespath_options,
Expand Down
25 changes: 22 additions & 3 deletions aws_lambda_powertools/utilities/jmespath_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import gzip
import json
import logging
import warnings
from typing import Any, Dict, Optional, Union

import jmespath
from jmespath.exceptions import LexerError
from jmespath.functions import Functions, signature
from typing_extensions import deprecated

from aws_lambda_powertools.exceptions import InvalidEnvelopeExpressionError
from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning

logger = logging.getLogger(__name__)

Expand All @@ -30,7 +33,7 @@ def _func_powertools_base64_gzip(self, value):
return uncompressed.decode()


def extract_data_from_envelope(data: Union[Dict, str], envelope: str, jmespath_options: Optional[Dict] = None) -> Any:
def query(data: Union[Dict, str], envelope: str, jmespath_options: Optional[Dict] = None) -> Any:
"""Searches and extracts data using JMESPath
Envelope being the JMESPath expression to extract the data you're after
Expand All @@ -42,13 +45,13 @@ def extract_data_from_envelope(data: Union[Dict, str], envelope: str, jmespath_o
**Deserialize JSON string and extracts data from body key**
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
from aws_lambda_powertools.utilities.jmespath_utils import query
from aws_lambda_powertools.utilities.typing import LambdaContext
def handler(event: dict, context: LambdaContext):
# event = {"body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}"} # noqa: ERA001
payload = extract_data_from_envelope(data=event, envelope="powertools_json(body)")
payload = query(data=event, envelope="powertools_json(body)")
customer = payload.get("customerId") # now deserialized
...
Expand Down Expand Up @@ -76,3 +79,19 @@ def handler(event: dict, context: LambdaContext):
except (LexerError, TypeError, UnicodeError) as e:
message = f"Failed to unwrap event from envelope using expression. Error: {e} Exp: {envelope}, Data: {data}" # noqa: B306, E501
raise InvalidEnvelopeExpressionError(message)


@deprecated("`extract_data_from_envelope` is deprecated; use `query` instead.", category=None)
def extract_data_from_envelope(data: Union[Dict, str], envelope: str, jmespath_options: Optional[Dict] = None) -> Any:
"""Searches and extracts data using JMESPath
*Deprecated*: Use query instead
"""
warnings.warn(
"The extract_data_from_envelope method is deprecated in V3 "
"and will be removed in the next major version. Use query instead.",
category=PowertoolsDeprecationWarning,
stacklevel=2,
)

return query(data=data, envelope=envelope, jmespath_options=jmespath_options)
4 changes: 2 additions & 2 deletions aws_lambda_powertools/utilities/validation/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def handler(event, context):
When JMESPath expression to unwrap event is invalid
""" # noqa: E501
if envelope:
event = jmespath_utils.extract_data_from_envelope(
event = jmespath_utils.query(
data=event,
envelope=envelope,
jmespath_options=jmespath_options,
Expand Down Expand Up @@ -223,7 +223,7 @@ def handler(event, context):
When JMESPath expression to unwrap event is invalid
""" # noqa: E501
if envelope:
event = jmespath_utils.extract_data_from_envelope(
event = jmespath_utils.query(
data=event,
envelope=envelope,
jmespath_options=jmespath_options,
Expand Down
38 changes: 19 additions & 19 deletions docs/utilities/jmespath_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ Powertools for AWS Lambda (Python) also have utilities like [validation](validat

### Extracting data

You can use the `extract_data_from_envelope` function with any [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank" rel="nofollow"}.
You can use the `query` function with any [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank" rel="nofollow"}.

???+ tip
Another common use case is to fetch deeply nested data, filter, flatten, and more.

=== "extract_data_from_envelope.py"
=== "query.py"
```python hl_lines="1 6 10"
--8<-- "examples/jmespath_functions/src/extract_data_from_envelope.py"
--8<-- "examples/jmespath_functions/src/query.py"
```

=== "extract_data_from_envelope.json"
Expand All @@ -52,7 +52,7 @@ We provide built-in envelopes for popular AWS Lambda event sources to easily dec

=== "extract_data_from_builtin_envelope.py"

```python hl_lines="1-4 9"
```python hl_lines="4-7 14"
--8<-- "examples/jmespath_functions/src/extract_data_from_builtin_envelope.py"
```

Expand All @@ -64,21 +64,21 @@ We provide built-in envelopes for popular AWS Lambda event sources to easily dec

These are all built-in envelopes you can use along with their expression as a reference:

| Envelope | JMESPath expression |
| --------------------------------- | ----------------------------------------------------------------------------------------- |
| **`API_GATEWAY_HTTP`** | `powertools_json(body)` |
| **`API_GATEWAY_REST`** | `powertools_json(body)` |
| **`CLOUDWATCH_EVENTS_SCHEDULED`** | `detail` |
| Envelope | JMESPath expression | |
| --------------------------------- | ----------------------------------------------------------------------------------------- |-|
| **`API_GATEWAY_HTTP`** | `powertools_json(body)` | |
| **`API_GATEWAY_REST`** | `powertools_json(body)` | |
| **`CLOUDWATCH_EVENTS_SCHEDULED`** | `detail` | |
| **`CLOUDWATCH_LOGS`** | `awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]` |
| **`EVENTBRIDGE`** | `detail` |
| **`KINESIS_DATA_STREAM`** | `Records[*].kinesis.powertools_json(powertools_base64(data))` |
| **`S3_EVENTBRIDGE_SQS`** | `Records[*].powertools_json(body).detail` |
| **`S3_KINESIS_FIREHOSE`** | `records[*].powertools_json(powertools_base64(data)).Records[0]` |
| **`S3_SNS_KINESIS_FIREHOSE`** | `records[*].powertools_json(powertools_base64(data)).powertools_json(Message).Records[0]` |
| **`S3_SNS_SQS`** | `Records[*].powertools_json(body).powertools_json(Message).Records[0]` |
| **`S3_SQS`** | `Records[*].powertools_json(body).Records[0]` |
| **`EVENTBRIDGE`** | `detail` | |
| **`KINESIS_DATA_STREAM`** | `Records[*].kinesis.powertools_json(powertools_base64(data))` | |
| **`S3_EVENTBRIDGE_SQS`** | `Records[*].powertools_json(body).detail` | |
| **`S3_KINESIS_FIREHOSE`** | `records[*].powertools_json(powertools_base64(data)).Records[0]` | |
| **`S3_SNS_KINESIS_FIREHOSE`** | `records[*].powertools_json(powertools_base64(data)).powertools_json(Message).Records[0]` | |
| **`S3_SNS_SQS`** | `Records[*].powertools_json(body).powertools_json(Message).Records[0]` | |
| **`S3_SQS`** | `Records[*].powertools_json(body).Records[0]` | |
| **`SNS`** | `Records[0].Sns.Message | powertools_json(@)` |
| **`SQS`** | `Records[*].powertools_json(body)` |
| **`SQS`** | `Records[*].powertools_json(body)` | |

???+ tip "Using SNS?"
If you don't require SNS metadata, enable [raw message delivery](https://docs.aws.amazon.com/sns/latest/dg/sns-large-payload-raw-message-delivery.html). It will reduce multiple payload layers and size, when using SNS in combination with other services (_e.g., SQS, S3, etc_).
Expand All @@ -102,7 +102,7 @@ This sample will deserialize the JSON string within the `data` key before valida

=== "powertools_json_jmespath_function.py"

```python hl_lines="5 8 34 45 48 51"
```python hl_lines="5 6 34 45 48 51"
--8<-- "examples/jmespath_functions/src/powertools_json_jmespath_function.py"
```

Expand Down Expand Up @@ -142,7 +142,7 @@ This sample will decode the base64 value within the `data` key, and deserialize

=== "powertools_base64_jmespath_function.py"

```python hl_lines="7 10 37 49 53 55 57"
```python hl_lines="7 11 36 48 52 54 56"
--8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_function.py"
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.jmespath_utils import (
envelopes,
extract_data_from_envelope,
query,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()


def handler(event: dict, context: LambdaContext) -> dict:
records: list = extract_data_from_envelope(data=event, envelope=envelopes.SQS)
records: list = query(data=event, envelope=envelopes.SQS)
for record in records: # records is a list
logger.info(record.get("customerId")) # now deserialized

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def lambda_handler(event, context: LambdaContext) -> dict:
try:
validate(event=event, schema=schemas.INPUT, envelope="powertools_base64_gzip(payload) | powertools_json(@)")

# Alternatively, extract_data_from_envelope works here too
# Alternatively, query works here too
encoded_payload = base64.b64decode(event["payload"])
uncompressed_payload = gzip.decompress(encoded_payload).decode()
log: dict = json.loads(uncompressed_payload)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def lambda_handler(event, context: LambdaContext) -> dict:
try:
validate(event=event, schema=schemas.INPUT, envelope="powertools_json(powertools_base64(payload))")

# alternatively, extract_data_from_envelope works here too
# alternatively, query works here too
payload_decoded = base64.b64decode(event["payload"]).decode()

order_payload: dict = json.loads(payload_decoded)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from aws_lambda_powertools.utilities.jmespath_utils import (
PowertoolsFunctions,
extract_data_from_envelope,
query,
)


Expand All @@ -27,7 +27,7 @@ def lambda_handler(event, context) -> dict:
try:
logs = []
logs.append(
extract_data_from_envelope(
query(
data=event,
# NOTE: Use the prefix `_func_` before the name of the function
envelope="Records[*].decode_zlib_compression(log)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def lambda_handler(event, context: LambdaContext) -> dict:
validate(event=event, schema=schemas.INPUT, envelope="powertools_json(payload)")

# Deserialize JSON string order as dict
# alternatively, extract_data_from_envelope works here too
# alternatively, query works here too
order_payload: dict = json.loads(event.get("payload"))

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
from aws_lambda_powertools.utilities.jmespath_utils import query
from aws_lambda_powertools.utilities.typing import LambdaContext


def handler(event: dict, context: LambdaContext) -> dict:
payload = extract_data_from_envelope(data=event, envelope="powertools_json(body)")
payload = query(data=event, envelope="powertools_json(body)")
customer_id = payload.get("customerId") # now deserialized

# also works for fetching and flattening deeply nested data
some_data = extract_data_from_envelope(data=event, envelope="deeply_nested[*].some_data[]")
some_data = query(data=event, envelope="deeply_nested[*].some_data[]")

return {"customer_id": customer_id, "message": "success", "context": some_data, "statusCode": 200}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
from aws_lambda_powertools.utilities.feature_flags.types import JSONType
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
from aws_lambda_powertools.utilities.jmespath_utils import query
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate

Expand Down Expand Up @@ -42,8 +42,8 @@ def middleware_custom(
}

# extracting headers and requestContext from event
headers = extract_data_from_envelope(data=event, envelope="headers")
request_context = extract_data_from_envelope(data=event, envelope="requestContext")
headers = query(data=event, envelope="headers")
request_context = query(data=event, envelope="requestContext")

logger.debug(f"X-Customer-Id => {headers.get('X-Customer-Id')}")
tracer.put_annotation(key="CustomerId", value=headers.get("X-Customer-Id"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.utilities.jmespath_utils import (
envelopes,
extract_data_from_envelope,
query,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

Expand All @@ -19,8 +19,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")


class PaymentError(Exception):
...
class PaymentError(Exception): ...


@lambda_handler_decorator
Expand All @@ -30,7 +29,7 @@ def middleware_before(
context: LambdaContext,
) -> dict:
# extract payload from a EventBridge event
detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE)
detail: dict = query(data=event, envelope=envelopes.EVENTBRIDGE)

# check if status_id exists in payload, otherwise add default state before processing payment
if "status_id" not in detail:
Expand All @@ -44,7 +43,7 @@ def middleware_before(
@middleware_before
def lambda_handler(event: dict, context: LambdaContext) -> dict:
try:
payment_payload: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE)
payment_payload: dict = query(data=event, envelope=envelopes.EVENTBRIDGE)
return {
"order": Payment(**payment_payload).__dict__,
"message": "payment created",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.utilities.jmespath_utils import (
envelopes,
extract_data_from_envelope,
query,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

Expand All @@ -23,8 +23,7 @@ class Booking:
booking_id: str = field(default_factory=lambda: f"{uuid4()}")


class BookingError(Exception):
...
class BookingError(Exception): ...


@lambda_handler_decorator
Expand All @@ -35,7 +34,7 @@ def obfuscate_sensitive_data(
fields: List,
) -> dict:
# extracting payload from a EventBridge event
detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE)
detail: dict = query(data=event, envelope=envelopes.EVENTBRIDGE)
guest_data: Any = detail.get("guest")

# Obfuscate fields (email, vat, passport) before calling Lambda handler
Expand All @@ -56,7 +55,7 @@ def obfuscate_data(value: str) -> bytes:
@obfuscate_sensitive_data(fields=["email", "passport", "vat"])
def lambda_handler(event: dict, context: LambdaContext) -> dict:
try:
booking_payload: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE)
booking_payload: dict = query(data=event, envelope=envelopes.EVENTBRIDGE)
return {
"book": Booking(**booking_payload).__dict__,
"message": "booking created",
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/idempotency/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer
from aws_lambda_powertools.utilities.idempotency.idempotency import IdempotencyConfig
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
from aws_lambda_powertools.utilities.jmespath_utils import query
from aws_lambda_powertools.utilities.validation import envelopes
from tests.functional.idempotency.utils import hash_idempotency_key
from tests.functional.utils import json_serialize, load_event
Expand Down Expand Up @@ -195,7 +195,7 @@ def hashed_idempotency_key(request, lambda_apigw_event, default_jmespath, lambda

@pytest.fixture
def hashed_idempotency_key_with_envelope(request, lambda_apigw_event):
event = extract_data_from_envelope(
event = query(
data=lambda_apigw_event,
envelope=envelopes.API_GATEWAY_HTTP,
jmespath_options={},
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/jmespath_util/test_jmespath_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import pytest

from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning


def test_extract_data_from_envelope():
data = {"data": {"foo": "bar"}}
envelope = "data"

with pytest.warns(PowertoolsDeprecationWarning, match="The extract_data_from_envelope method is deprecated in V3*"):
assert extract_data_from_envelope(data=data, envelope=envelope) == {"foo": "bar"}

0 comments on commit a4fdec0

Please sign in to comment.