Skip to content

Commit 2af58c0

Browse files
authored
fix(idempotency): stop CachePersistenceLayer from emitting the Redis deprecation warning (#8625)
CachePersistenceLayer, CacheConnection and CacheClientProtocol were TypeAliases of the deprecated Redis* classes, so they were the same object and @deprecated fired for both. The implementation now lives under the Cache* names and the Redis* names are thin @deprecated subclasses, so only the old names warn. Also fixes the RedisClientProtocol warning text, which named RedisPersistenceLayer/CacheProtocol. Fixes #8574
1 parent 1d09a3b commit 2af58c0

2 files changed

Lines changed: 68 additions & 15 deletions

File tree

‎aws_lambda_powertools/utilities/idempotency/persistence/redis.py‎

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
from contextlib import contextmanager
77
from datetime import timedelta
8-
from typing import Any, Literal, Protocol, TypeAlias, cast
8+
from typing import Any, Literal, Protocol, cast
99

1010
import redis
1111
from typing_extensions import deprecated
@@ -26,8 +26,7 @@
2626
logger = logging.getLogger(__name__)
2727

2828

29-
@deprecated("RedisPersistenceLayer will be removed in v4.0.0. Please use CacheProtocol instead.")
30-
class RedisClientProtocol(Protocol):
29+
class CacheClientProtocol(Protocol):
3130
"""
3231
Protocol class defining the interface for a Cache client.
3332
@@ -80,8 +79,7 @@ def delete(self, *names: bytes | str | memoryview) -> Any:
8079
raise NotImplementedError
8180

8281

83-
@deprecated("RedisConnection will be removed in v4.0.0. Please use CacheConnection instead.")
84-
class RedisConnection:
82+
class CacheConnection:
8583
def __init__(
8684
self,
8785
url: str = "",
@@ -172,7 +170,7 @@ def create_subscription_payment(event: dict) -> Payment:
172170
self.ssl = ssl
173171
self.mode = mode
174172

175-
def _init_client(self) -> RedisClientProtocol:
173+
def _init_client(self) -> CacheClientProtocol:
176174
logger.debug(f"Trying to connect to Redis: {self.host}")
177175
client: type[redis.Redis | redis.cluster.RedisCluster]
178176
if self.mode == "standalone":
@@ -185,7 +183,7 @@ def _init_client(self) -> RedisClientProtocol:
185183
try:
186184
if self.url:
187185
logger.debug(f"Using URL format to connect to Cache: {self.host}")
188-
return cast(RedisClientProtocol, client.from_url(url=self.url))
186+
return cast(CacheClientProtocol, client.from_url(url=self.url))
189187
else:
190188
# Cache in cluster mode doesn't support db parameter
191189
extra_param_connection: dict[str, Any] = {}
@@ -194,7 +192,7 @@ def _init_client(self) -> RedisClientProtocol:
194192

195193
logger.debug(f"Using arguments to connect to Cache: {self.host}")
196194
return cast(
197-
RedisClientProtocol,
195+
CacheClientProtocol,
198196
client(
199197
host=self.host,
200198
port=self.port,
@@ -210,8 +208,7 @@ def _init_client(self) -> RedisClientProtocol:
210208
raise IdempotencyPersistenceConnectionError("Could not to connect to Cache endpoint", exc) from exc
211209

212210

213-
@deprecated("RedisCachePersistenceLayer will be removed in v4.0.0. Please use CachePersistenceLayer instead.")
214-
class RedisCachePersistenceLayer(BasePersistenceLayer):
211+
class CachePersistenceLayer(BasePersistenceLayer):
215212
def __init__(
216213
self,
217214
url: str = "",
@@ -222,7 +219,7 @@ def __init__(
222219
db_index: int = 0,
223220
mode: Literal["standalone", "cluster"] = "standalone",
224221
ssl: bool = True,
225-
client: RedisClientProtocol | None = None,
222+
client: CacheClientProtocol | None = None,
226223
in_progress_expiry_attr: str = "in_progress_expiration",
227224
expiry_attr: str = "expiration",
228225
status_attr: str = "status",
@@ -297,7 +294,7 @@ def lambda_handler(event: dict, context: LambdaContext):
297294

298295
# Initialize Cache client with cache config if no client is passed in
299296
if client is None:
300-
self.client = RedisConnection(
297+
self.client = CacheConnection(
301298
host=host,
302299
port=port,
303300
username=username,
@@ -517,6 +514,14 @@ def _delete_record(self, data_record: DataRecord) -> None:
517514
self.client.delete(data_record.idempotency_key)
518515

519516

520-
CachePersistenceLayer: TypeAlias = RedisCachePersistenceLayer
521-
CacheClientProtocol: TypeAlias = RedisClientProtocol
522-
CacheConnection: TypeAlias = RedisConnection
517+
# Deprecated Redis* names are thin subclasses, so only they emit the DeprecationWarning.
518+
@deprecated("RedisClientProtocol will be removed in v4.0.0. Please use CacheClientProtocol instead.")
519+
class RedisClientProtocol(CacheClientProtocol, Protocol): ...
520+
521+
522+
@deprecated("RedisConnection will be removed in v4.0.0. Please use CacheConnection instead.")
523+
class RedisConnection(CacheConnection): ...
524+
525+
526+
@deprecated("RedisCachePersistenceLayer will be removed in v4.0.0. Please use CachePersistenceLayer instead.")
527+
class RedisCachePersistenceLayer(CachePersistenceLayer): ...

‎tests/functional/idempotency/_redis/test_redis_layer.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import json
55
import time as t
6+
import warnings
67
from threading import Event, Lock as ThreadLock, Thread
78
from unittest import mock
89

@@ -792,3 +793,50 @@ def lambda_handler(event, context):
792793
p2.join()
793794
# Then only one handler will actually run
794795
assert redis_client.cache["exec_count"] == 1
796+
797+
798+
def test_cache_names_do_not_emit_deprecation_warning():
799+
# GIVEN the non-deprecated Cache* names
800+
from aws_lambda_powertools.utilities.idempotency.persistence.cache import CacheClientProtocol, CacheConnection
801+
802+
# WHEN they are used
803+
with warnings.catch_warnings():
804+
warnings.simplefilter("error", DeprecationWarning)
805+
CachePersistenceLayer(client=MockRedis(host="localhost", port="63005"))
806+
CacheConnection(host="localhost")
807+
808+
class MyClient(CacheClientProtocol): ...
809+
810+
# THEN no DeprecationWarning is raised
811+
812+
813+
@pytest.mark.parametrize(
814+
"deprecated_name, kwargs",
815+
[
816+
("RedisCachePersistenceLayer", {"client": MockRedis(host="localhost", port="63005")}),
817+
("RedisConnection", {"host": "localhost"}),
818+
],
819+
)
820+
def test_redis_names_still_emit_deprecation_warning(deprecated_name, kwargs):
821+
# GIVEN a deprecated Redis* name
822+
from aws_lambda_powertools.utilities.idempotency.persistence import redis as redis_module
823+
824+
deprecated_cls = getattr(redis_module, deprecated_name)
825+
826+
# WHEN it is instantiated
827+
# THEN a DeprecationWarning pointing to the Cache* name is raised
828+
with pytest.warns(DeprecationWarning, match=f"{deprecated_name} will be removed in v4.0.0"):
829+
instance = deprecated_cls(**kwargs)
830+
831+
# AND it is still usable wherever a Cache* object is expected
832+
from aws_lambda_powertools.utilities.idempotency.persistence.cache import CacheConnection
833+
834+
assert isinstance(instance, (CachePersistenceLayer, CacheConnection))
835+
836+
837+
def test_redis_client_protocol_still_emits_deprecation_warning_on_subclass():
838+
from aws_lambda_powertools.utilities.idempotency.persistence.redis import RedisClientProtocol
839+
840+
with pytest.warns(DeprecationWarning, match="RedisClientProtocol will be removed in v4.0.0"):
841+
842+
class MyClient(RedisClientProtocol): ...

0 commit comments

Comments
 (0)