Skip to content

Commit

Permalink
fix(idempotency): POWERTOOLS_IDEMPOTENCY_DISABLED should respect trut…
Browse files Browse the repository at this point in the history
…hy values (#4391)

Co-authored-by: Leandro Damascena <[email protected]>
  • Loading branch information
stevenhoelscher and leandrodamascena authored Jun 4, 2024
1 parent b04d648 commit 751bf99
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 4 deletions.
23 changes: 21 additions & 2 deletions aws_lambda_powertools/utilities/idempotency/idempotency.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import functools
import logging
import os
import warnings
from inspect import isclass
from typing import Any, Callable, Dict, Optional, Type, Union, cast

from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.shared.functions import strtobool
from aws_lambda_powertools.shared.types import AnyCallableT
from aws_lambda_powertools.utilities.idempotency.base import IdempotencyHandler
from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
Expand All @@ -21,6 +23,7 @@
BaseIdempotencySerializer,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.warnings import PowertoolsUserWarning

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -66,7 +69,15 @@ def idempotent(
>>> return {"StatusCode": 200}
"""

if os.getenv(constants.IDEMPOTENCY_DISABLED_ENV):
# Skip idempotency controls when POWERTOOLS_IDEMPOTENCY_DISABLED has a truthy value
# Raises a warning if not running in development mode
if strtobool(os.getenv(constants.IDEMPOTENCY_DISABLED_ENV, "false")):
warnings.warn(
message="Disabling idempotency is intended for development environments only "
"and should not be used in production.",
category=PowertoolsUserWarning,
stacklevel=2,
)
return handler(event, context, **kwargs)

config = config or IdempotencyConfig()
Expand Down Expand Up @@ -150,7 +161,15 @@ def process_order(customer_id: str, order: dict, **kwargs):

@functools.wraps(function)
def decorate(*args, **kwargs):
if os.getenv(constants.IDEMPOTENCY_DISABLED_ENV):
# Skip idempotency controls when POWERTOOLS_IDEMPOTENCY_DISABLED has a truthy value
# Raises a warning if not running in development mode
if strtobool(os.getenv(constants.IDEMPOTENCY_DISABLED_ENV, "false")):
warnings.warn(
message="Disabling idempotency is intended for development environments only "
"and should not be used in production.",
category=PowertoolsUserWarning,
stacklevel=2,
)
return function(*args, **kwargs)

if data_keyword_argument not in kwargs:
Expand Down
23 changes: 23 additions & 0 deletions aws_lambda_powertools/warnings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Shared warnings that don't belong to a single utility"""


class PowertoolsUserWarning(UserWarning):
"""
This class provides a custom Warning tailored for better clarity when certain situations occur.
Examples:
- Using development-only features in production environment.
- Potential performance or security issues due to misconfiguration.
Parameters
----------
message: str
The warning message to be displayed.
"""

def __init__(self, message):
self.message = message
super().__init__(message)

def __str__(self):
return self.message
69 changes: 67 additions & 2 deletions tests/functional/idempotency/test_idempotency.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
PydanticSerializer,
)
from aws_lambda_powertools.utilities.validation import envelopes, validator
from aws_lambda_powertools.warnings import PowertoolsUserWarning
from tests.functional.idempotency.utils import (
build_idempotency_put_item_response_stub,
build_idempotency_put_item_stub,
Expand Down Expand Up @@ -1667,13 +1668,20 @@ def dummy(payload):
dummy(payload=data_two)


def test_idempotency_disabled_envvar(monkeypatch, lambda_context, persistence_store: DynamoDBPersistenceLayer):
@pytest.mark.parametrize("idempotency_disabled_value", ["1", "y", "yes", "t", "true", "on"])
def test_idempotency_enabled_envvar_in_dev_environment(
monkeypatch,
lambda_context,
persistence_store: DynamoDBPersistenceLayer,
idempotency_disabled_value,
):
# Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is set
mock_event = {"data": "value"}

persistence_store.client = MagicMock()

monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", "1")
monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", str(idempotency_disabled_value))
monkeypatch.setenv("POWERTOOLS_DEV", "true")

@idempotent_function(data_keyword_argument="data", persistence_store=persistence_store)
def dummy(data):
Expand All @@ -1689,6 +1697,63 @@ def dummy_handler(event, context):
assert len(persistence_store.client.method_calls) == 0


@pytest.mark.parametrize("idempotency_disabled_value", ["1", "y", "yes", "t", "true", "on"])
def test_idempotency_enabled_envvar_in_non_dev_environment(
monkeypatch,
lambda_context,
persistence_store: DynamoDBPersistenceLayer,
idempotency_disabled_value,
):
# Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is set
mock_event = {"data": "value"}

persistence_store.client = MagicMock()

monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", str(idempotency_disabled_value))

@idempotent_function(data_keyword_argument="data", persistence_store=persistence_store)
def dummy(data):
return {"message": "hello"}

@idempotent(persistence_store=persistence_store)
def dummy_handler(event, context):
return {"message": "hi"}

with pytest.warns(PowertoolsUserWarning, match="Disabling idempotency is intended for development environments*"):
dummy(data=mock_event)
dummy_handler(mock_event, lambda_context)

assert len(persistence_store.client.method_calls) == 0


@pytest.mark.parametrize("idempotency_disabled_value", ["0", "n", "no", "f", "false", "off"])
def test_idempotency_disabled_envvar(
monkeypatch,
lambda_context,
persistence_store: DynamoDBPersistenceLayer,
idempotency_disabled_value,
):
# Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is false
mock_event = {"data": "value"}

persistence_store.client = MagicMock()

monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", str(idempotency_disabled_value))

@idempotent_function(data_keyword_argument="data", persistence_store=persistence_store)
def dummy(data):
return {"message": "hello"}

@idempotent(persistence_store=persistence_store)
def dummy_handler(event, context):
return {"message": "hi"}

dummy(data=mock_event)
dummy_handler(mock_event, lambda_context)

assert len(persistence_store.client.method_calls) == 4


@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": True}], indirect=True)
def test_idempotent_function_duplicates(
idempotency_config: IdempotencyConfig,
Expand Down

0 comments on commit 751bf99

Please sign in to comment.