Skip to content

refactor(jmespath_utils): deprecate extract_data_from_envelope in favor of query #4907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"}
Loading