Skip to content
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
35 changes: 20 additions & 15 deletions aws_lambda_powertools/utilities/idempotency/persistence/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging
from contextlib import contextmanager
from datetime import timedelta
from typing import Any, Literal, Protocol, TypeAlias, cast
from typing import Any, Literal, Protocol, cast

import redis
from typing_extensions import deprecated
Expand All @@ -26,8 +26,7 @@
logger = logging.getLogger(__name__)


@deprecated("RedisPersistenceLayer will be removed in v4.0.0. Please use CacheProtocol instead.")
class RedisClientProtocol(Protocol):
class CacheClientProtocol(Protocol):
"""
Protocol class defining the interface for a Cache client.

Expand Down Expand Up @@ -80,8 +79,7 @@ def delete(self, *names: bytes | str | memoryview) -> Any:
raise NotImplementedError


@deprecated("RedisConnection will be removed in v4.0.0. Please use CacheConnection instead.")
class RedisConnection:
class CacheConnection:
def __init__(
self,
url: str = "",
Expand Down Expand Up @@ -172,7 +170,7 @@ def create_subscription_payment(event: dict) -> Payment:
self.ssl = ssl
self.mode = mode

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

logger.debug(f"Using arguments to connect to Cache: {self.host}")
return cast(
RedisClientProtocol,
CacheClientProtocol,
client(
host=self.host,
port=self.port,
Expand All @@ -210,8 +208,7 @@ def _init_client(self) -> RedisClientProtocol:
raise IdempotencyPersistenceConnectionError("Could not to connect to Cache endpoint", exc) from exc


@deprecated("RedisCachePersistenceLayer will be removed in v4.0.0. Please use CachePersistenceLayer instead.")
class RedisCachePersistenceLayer(BasePersistenceLayer):
class CachePersistenceLayer(BasePersistenceLayer):
def __init__(
self,
url: str = "",
Expand All @@ -222,7 +219,7 @@ def __init__(
db_index: int = 0,
mode: Literal["standalone", "cluster"] = "standalone",
ssl: bool = True,
client: RedisClientProtocol | None = None,
client: CacheClientProtocol | None = None,
in_progress_expiry_attr: str = "in_progress_expiration",
expiry_attr: str = "expiration",
status_attr: str = "status",
Expand Down Expand Up @@ -297,7 +294,7 @@ def lambda_handler(event: dict, context: LambdaContext):

# Initialize Cache client with cache config if no client is passed in
if client is None:
self.client = RedisConnection(
self.client = CacheConnection(
host=host,
port=port,
username=username,
Expand Down Expand Up @@ -517,6 +514,14 @@ def _delete_record(self, data_record: DataRecord) -> None:
self.client.delete(data_record.idempotency_key)


CachePersistenceLayer: TypeAlias = RedisCachePersistenceLayer
CacheClientProtocol: TypeAlias = RedisClientProtocol
CacheConnection: TypeAlias = RedisConnection
# Deprecated Redis* names are thin subclasses, so only they emit the DeprecationWarning.
@deprecated("RedisClientProtocol will be removed in v4.0.0. Please use CacheClientProtocol instead.")
class RedisClientProtocol(CacheClientProtocol, Protocol): ...


@deprecated("RedisConnection will be removed in v4.0.0. Please use CacheConnection instead.")
class RedisConnection(CacheConnection): ...


@deprecated("RedisCachePersistenceLayer will be removed in v4.0.0. Please use CachePersistenceLayer instead.")
class RedisCachePersistenceLayer(CachePersistenceLayer): ...
48 changes: 48 additions & 0 deletions tests/functional/idempotency/_redis/test_redis_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import datetime
import json
import time as t
import warnings
from threading import Event, Lock as ThreadLock, Thread
from unittest import mock

Expand Down Expand Up @@ -728,3 +729,50 @@ def lambda_handler(event, context):
p2.join()
# Then only one handler will actually run
assert redis_client.cache["exec_count"] == 1


def test_cache_names_do_not_emit_deprecation_warning():
# GIVEN the non-deprecated Cache* names
from aws_lambda_powertools.utilities.idempotency.persistence.cache import CacheClientProtocol, CacheConnection

# WHEN they are used
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
CachePersistenceLayer(client=MockRedis(host="localhost", port="63005"))
CacheConnection(host="localhost")

class MyClient(CacheClientProtocol): ...

# THEN no DeprecationWarning is raised


@pytest.mark.parametrize(
"deprecated_name, kwargs",
[
("RedisCachePersistenceLayer", {"client": MockRedis(host="localhost", port="63005")}),
("RedisConnection", {"host": "localhost"}),
],
)
def test_redis_names_still_emit_deprecation_warning(deprecated_name, kwargs):
# GIVEN a deprecated Redis* name
from aws_lambda_powertools.utilities.idempotency.persistence import redis as redis_module

deprecated_cls = getattr(redis_module, deprecated_name)

# WHEN it is instantiated
# THEN a DeprecationWarning pointing to the Cache* name is raised
with pytest.warns(DeprecationWarning, match=f"{deprecated_name} will be removed in v4.0.0"):
instance = deprecated_cls(**kwargs)

# AND it is still usable wherever a Cache* object is expected
from aws_lambda_powertools.utilities.idempotency.persistence.cache import CacheConnection

assert isinstance(instance, (CachePersistenceLayer, CacheConnection))


def test_redis_client_protocol_still_emits_deprecation_warning_on_subclass():
from aws_lambda_powertools.utilities.idempotency.persistence.redis import RedisClientProtocol

with pytest.warns(DeprecationWarning, match="RedisClientProtocol will be removed in v4.0.0"):

class MyClient(RedisClientProtocol): ...
Loading