Skip to content

Commit 751bf99

Browse files
fix(idempotency): POWERTOOLS_IDEMPOTENCY_DISABLED should respect truthy values (#4391)
Co-authored-by: Leandro Damascena <[email protected]>
1 parent b04d648 commit 751bf99

File tree

3 files changed

+111
-4
lines changed

3 files changed

+111
-4
lines changed

aws_lambda_powertools/utilities/idempotency/idempotency.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import functools
66
import logging
77
import os
8+
import warnings
89
from inspect import isclass
910
from typing import Any, Callable, Dict, Optional, Type, Union, cast
1011

1112
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
1213
from aws_lambda_powertools.shared import constants
14+
from aws_lambda_powertools.shared.functions import strtobool
1315
from aws_lambda_powertools.shared.types import AnyCallableT
1416
from aws_lambda_powertools.utilities.idempotency.base import IdempotencyHandler
1517
from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
@@ -21,6 +23,7 @@
2123
BaseIdempotencySerializer,
2224
)
2325
from aws_lambda_powertools.utilities.typing import LambdaContext
26+
from aws_lambda_powertools.warnings import PowertoolsUserWarning
2427

2528
logger = logging.getLogger(__name__)
2629

@@ -66,7 +69,15 @@ def idempotent(
6669
>>> return {"StatusCode": 200}
6770
"""
6871

69-
if os.getenv(constants.IDEMPOTENCY_DISABLED_ENV):
72+
# Skip idempotency controls when POWERTOOLS_IDEMPOTENCY_DISABLED has a truthy value
73+
# Raises a warning if not running in development mode
74+
if strtobool(os.getenv(constants.IDEMPOTENCY_DISABLED_ENV, "false")):
75+
warnings.warn(
76+
message="Disabling idempotency is intended for development environments only "
77+
"and should not be used in production.",
78+
category=PowertoolsUserWarning,
79+
stacklevel=2,
80+
)
7081
return handler(event, context, **kwargs)
7182

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

151162
@functools.wraps(function)
152163
def decorate(*args, **kwargs):
153-
if os.getenv(constants.IDEMPOTENCY_DISABLED_ENV):
164+
# Skip idempotency controls when POWERTOOLS_IDEMPOTENCY_DISABLED has a truthy value
165+
# Raises a warning if not running in development mode
166+
if strtobool(os.getenv(constants.IDEMPOTENCY_DISABLED_ENV, "false")):
167+
warnings.warn(
168+
message="Disabling idempotency is intended for development environments only "
169+
"and should not be used in production.",
170+
category=PowertoolsUserWarning,
171+
stacklevel=2,
172+
)
154173
return function(*args, **kwargs)
155174

156175
if data_keyword_argument not in kwargs:
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Shared warnings that don't belong to a single utility"""
2+
3+
4+
class PowertoolsUserWarning(UserWarning):
5+
"""
6+
This class provides a custom Warning tailored for better clarity when certain situations occur.
7+
8+
Examples:
9+
- Using development-only features in production environment.
10+
- Potential performance or security issues due to misconfiguration.
11+
12+
Parameters
13+
----------
14+
message: str
15+
The warning message to be displayed.
16+
"""
17+
18+
def __init__(self, message):
19+
self.message = message
20+
super().__init__(message)
21+
22+
def __str__(self):
23+
return self.message

tests/functional/idempotency/test_idempotency.py

+67-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
PydanticSerializer,
5252
)
5353
from aws_lambda_powertools.utilities.validation import envelopes, validator
54+
from aws_lambda_powertools.warnings import PowertoolsUserWarning
5455
from tests.functional.idempotency.utils import (
5556
build_idempotency_put_item_response_stub,
5657
build_idempotency_put_item_stub,
@@ -1667,13 +1668,20 @@ def dummy(payload):
16671668
dummy(payload=data_two)
16681669

16691670

1670-
def test_idempotency_disabled_envvar(monkeypatch, lambda_context, persistence_store: DynamoDBPersistenceLayer):
1671+
@pytest.mark.parametrize("idempotency_disabled_value", ["1", "y", "yes", "t", "true", "on"])
1672+
def test_idempotency_enabled_envvar_in_dev_environment(
1673+
monkeypatch,
1674+
lambda_context,
1675+
persistence_store: DynamoDBPersistenceLayer,
1676+
idempotency_disabled_value,
1677+
):
16711678
# Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is set
16721679
mock_event = {"data": "value"}
16731680

16741681
persistence_store.client = MagicMock()
16751682

1676-
monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", "1")
1683+
monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", str(idempotency_disabled_value))
1684+
monkeypatch.setenv("POWERTOOLS_DEV", "true")
16771685

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

16911699

1700+
@pytest.mark.parametrize("idempotency_disabled_value", ["1", "y", "yes", "t", "true", "on"])
1701+
def test_idempotency_enabled_envvar_in_non_dev_environment(
1702+
monkeypatch,
1703+
lambda_context,
1704+
persistence_store: DynamoDBPersistenceLayer,
1705+
idempotency_disabled_value,
1706+
):
1707+
# Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is set
1708+
mock_event = {"data": "value"}
1709+
1710+
persistence_store.client = MagicMock()
1711+
1712+
monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", str(idempotency_disabled_value))
1713+
1714+
@idempotent_function(data_keyword_argument="data", persistence_store=persistence_store)
1715+
def dummy(data):
1716+
return {"message": "hello"}
1717+
1718+
@idempotent(persistence_store=persistence_store)
1719+
def dummy_handler(event, context):
1720+
return {"message": "hi"}
1721+
1722+
with pytest.warns(PowertoolsUserWarning, match="Disabling idempotency is intended for development environments*"):
1723+
dummy(data=mock_event)
1724+
dummy_handler(mock_event, lambda_context)
1725+
1726+
assert len(persistence_store.client.method_calls) == 0
1727+
1728+
1729+
@pytest.mark.parametrize("idempotency_disabled_value", ["0", "n", "no", "f", "false", "off"])
1730+
def test_idempotency_disabled_envvar(
1731+
monkeypatch,
1732+
lambda_context,
1733+
persistence_store: DynamoDBPersistenceLayer,
1734+
idempotency_disabled_value,
1735+
):
1736+
# Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is false
1737+
mock_event = {"data": "value"}
1738+
1739+
persistence_store.client = MagicMock()
1740+
1741+
monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", str(idempotency_disabled_value))
1742+
1743+
@idempotent_function(data_keyword_argument="data", persistence_store=persistence_store)
1744+
def dummy(data):
1745+
return {"message": "hello"}
1746+
1747+
@idempotent(persistence_store=persistence_store)
1748+
def dummy_handler(event, context):
1749+
return {"message": "hi"}
1750+
1751+
dummy(data=mock_event)
1752+
dummy_handler(mock_event, lambda_context)
1753+
1754+
assert len(persistence_store.client.method_calls) == 4
1755+
1756+
16921757
@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": True}], indirect=True)
16931758
def test_idempotent_function_duplicates(
16941759
idempotency_config: IdempotencyConfig,

0 commit comments

Comments
 (0)