Skip to content

Commit 0a399ba

Browse files
Replacing Redis with Cache
1 parent 42b12a7 commit 0a399ba

12 files changed

+251
-123
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ target:
77
dev:
88
pip install --upgrade pip pre-commit poetry
99
@$(MAKE) dev-version-plugin
10-
poetry install --extras "all redis datamasking"
10+
poetry install --extras "all redis datamasking valkey"
1111
pre-commit install
1212

1313
dev-quality-code:
1414
pip install --upgrade pip pre-commit poetry
1515
@$(MAKE) dev-version-plugin
16-
poetry install --extras "all redis datamasking"
16+
poetry install --extras "all redis datamasking valkey"
1717
pre-commit install
1818

1919
dev-gitpod:
2020
pip install --upgrade pip poetry
21-
poetry install --extras "all redis datamasking"
21+
poetry install --extras "all redis datamasking valkey"
2222
pre-commit install
2323

2424
# Running licensecheck with zero to break the pipeline if there is an invalid license
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from aws_lambda_powertools.utilities.idempotency.persistence.redis import (
2+
CacheClientProtocol,
3+
CacheConnection,
4+
CachePersistenceLayer,
5+
)
6+
7+
__all__ = [
8+
"CacheClientProtocol",
9+
"CachePersistenceLayer",
10+
"CacheConnection",
11+
]

aws_lambda_powertools/utilities/idempotency/persistence/redis.py

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Any, Literal, Protocol
99

1010
import redis
11-
from typing_extensions import deprecated
11+
from typing_extensions import TypeAlias, deprecated
1212

1313
from aws_lambda_powertools.utilities.idempotency import BasePersistenceLayer
1414
from aws_lambda_powertools.utilities.idempotency.exceptions import (
@@ -88,7 +88,7 @@ def __init__(
8888
host: str = "",
8989
port: int = 6379,
9090
username: str = "",
91-
password: str = "", # nosec - password for Redis connection
91+
password: str = "", # nosec - password for Cache connection
9292
db_index: int = 0,
9393
mode: Literal["standalone", "cluster"] = "standalone",
9494
ssl: bool = True,
@@ -131,7 +131,7 @@ def __init__(
131131
132132
from aws_lambda_powertools.utilities.typing import LambdaContext
133133
134-
persistence_layer = RedisCachePersistenceLayer(host="localhost", port=6379)
134+
persistence_layer = CachePersistenceLayer(host="localhost", port=6379)
135135
136136
137137
@dataclass
@@ -184,15 +184,15 @@ def _init_client(self) -> RedisClientProtocol:
184184

185185
try:
186186
if self.url:
187-
logger.debug(f"Using URL format to connect to Redis: {self.host}")
187+
logger.debug(f"Using URL format to connect to Cache: {self.host}")
188188
return client.from_url(url=self.url)
189189
else:
190-
# Redis in cluster mode doesn't support db parameter
190+
# Cache in cluster mode doesn't support db parameter
191191
extra_param_connection: dict[str, Any] = {}
192192
if self.mode != "cluster":
193193
extra_param_connection = {"db": self.db_index}
194194

195-
logger.debug(f"Using arguments to connect to Redis: {self.host}")
195+
logger.debug(f"Using arguments to connect to Cache: {self.host}")
196196
return client(
197197
host=self.host,
198198
port=self.port,
@@ -203,8 +203,8 @@ def _init_client(self) -> RedisClientProtocol:
203203
**extra_param_connection,
204204
)
205205
except redis.exceptions.ConnectionError as exc:
206-
logger.debug(f"Cannot connect in Redis: {self.host}")
207-
raise IdempotencyPersistenceConnectionError("Could not to connect to Redis", exc) from exc
206+
logger.debug(f"Cannot connect to Cache endpoint: {self.host}")
207+
raise IdempotencyPersistenceConnectionError("Could not to connect to Cache endpoint", exc) from exc
208208

209209

210210
@deprecated("RedisCachePersistenceLayer will be removed in v4.0.0. Please use CachePersistenceLayer instead.")
@@ -215,7 +215,7 @@ def __init__(
215215
host: str = "",
216216
port: int = 6379,
217217
username: str = "",
218-
password: str = "", # nosec - password for Redis connection
218+
password: str = "", # nosec - password for Cache connection
219219
db_index: int = 0,
220220
mode: Literal["standalone", "cluster"] = "standalone",
221221
ssl: bool = True,
@@ -227,39 +227,39 @@ def __init__(
227227
validation_key_attr: str = "validation",
228228
):
229229
"""
230-
Initialize the Redis Persistence Layer
230+
Initialize the Cache Persistence Layer
231231
232232
Parameters
233233
----------
234234
host: str, optional
235-
Redis host
235+
Cache host
236236
port: int, optional: default 6379
237-
Redis port
237+
Cache port
238238
username: str, optional
239-
Redis username
239+
Cache username
240240
password: str, optional
241-
Redis password
241+
Cache password
242242
url: str, optional
243-
Redis connection string, using url will override the host/port in the previous parameters
243+
Cache connection string, using url will override the host/port in the previous parameters
244244
db_index: int, optional: default 0
245-
Redis db index
245+
Cache db index
246246
mode: str, Literal["standalone","cluster"]
247-
set Redis client mode, choose from standalone/cluster
247+
set Cache client mode, choose from standalone/cluster
248248
ssl: bool, optional: default True
249-
set whether to use ssl for Redis connection
250-
client: RedisClientProtocol, optional
251-
Bring your own Redis client that follows RedisClientProtocol.
249+
set whether to use ssl for Cache connection
250+
client: CacheClientProtocol, optional
251+
Bring your own Cache client that follows CacheClientProtocol.
252252
If provided, all other connection configuration options will be ignored
253253
expiry_attr: str, optional
254-
Redis json attribute name for expiry timestamp, by default "expiration"
254+
Cache json attribute name for expiry timestamp, by default "expiration"
255255
in_progress_expiry_attr: str, optional
256-
Redis json attribute name for in-progress expiry timestamp, by default "in_progress_expiration"
256+
Cache json attribute name for in-progress expiry timestamp, by default "in_progress_expiration"
257257
status_attr: str, optional
258-
Redis json attribute name for status, by default "status"
258+
Cache json attribute name for status, by default "status"
259259
data_attr: str, optional
260-
Redis json attribute name for response data, by default "data"
260+
Cache json attribute name for response data, by default "data"
261261
validation_key_attr: str, optional
262-
Redis json attribute name for hashed representation of the parts of the event used for validation
262+
Cache json attribute name for hashed representation of the parts of the event used for validation
263263
264264
Examples
265265
--------
@@ -270,16 +270,16 @@ def __init__(
270270
idempotent,
271271
)
272272
273-
from aws_lambda_powertools.utilities.idempotency.persistence.redis import (
274-
RedisCachePersistenceLayer,
273+
from aws_lambda_powertools.utilities.idempotency.persistence.cache import (
274+
CachePersistenceLayer,
275275
)
276276
277277
client = redis.Redis(
278278
host="localhost",
279279
port="6379",
280280
decode_responses=True,
281281
)
282-
persistence_layer = RedisCachePersistenceLayer(client=client)
282+
persistence_layer = CachePersistenceLayer(client=client)
283283
284284
@idempotent(persistence_store=persistence_layer)
285285
def lambda_handler(event: dict, context: LambdaContext):
@@ -292,7 +292,7 @@ def lambda_handler(event: dict, context: LambdaContext):
292292
```
293293
"""
294294

295-
# Initialize Redis client with Redis config if no client is passed in
295+
# Initialize Cache client with cache config if no client is passed in
296296
if client is None:
297297
self.client = RedisConnection(
298298
host=host,
@@ -334,11 +334,11 @@ def _item_to_data_record(self, idempotency_key: str, item: dict[str, Any]) -> Da
334334
in_progress_expiry_timestamp=in_progress_expiry_timestamp,
335335
response_data=str(item.get(self.data_attr)),
336336
payload_hash=str(item.get(self.validation_key_attr)),
337-
expiry_timestamp=item.get("expiration", None),
337+
expiry_timestamp=item.get("expiration"),
338338
)
339339

340340
def _get_record(self, idempotency_key) -> DataRecord:
341-
# See: https://redis.io/commands/get/
341+
# See: https://valkey.io/valkey-glide/python/core/#glide.async_commands.CoreCommands.set
342342
response = self.client.get(idempotency_key)
343343

344344
# key not found
@@ -388,25 +388,25 @@ def _put_in_progress_record(self, data_record: DataRecord) -> None:
388388
# The idempotency key does not exist:
389389
# - first time that this invocation key is used
390390
# - previous invocation with the same key was deleted due to TTL
391-
# - SET see https://redis.io/commands/set/
391+
# - SET see https://valkey.io/valkey-glide/python/core/#glide.async_commands.CoreCommands.set
392392

393-
logger.debug(f"Putting record on Redis for idempotency key: {data_record.idempotency_key}")
393+
logger.debug(f"Putting record on Cache for idempotency key: {data_record.idempotency_key}")
394394
encoded_item = self._json_serializer(item["mapping"])
395395
ttl = self._get_expiry_second(expiry_timestamp=data_record.expiry_timestamp)
396396

397-
redis_response = self.client.set(name=data_record.idempotency_key, value=encoded_item, ex=ttl, nx=True)
397+
cache_response = self.client.set(name=data_record.idempotency_key, value=encoded_item, ex=ttl, nx=True)
398398

399-
# If redis_response is True, the Redis SET operation was successful and the idempotency key was not
399+
# If cache_response is True, the Cache SET operation was successful and the idempotency key was not
400400
# previously set. This indicates that we can safely proceed to the handler execution phase.
401401
# Most invocations should successfully proceed past this point.
402-
if redis_response:
402+
if cache_response:
403403
return
404404

405-
# If redis_response is None, it indicates an existing record in Redis for the given idempotency key.
405+
# If cache_response is None, it indicates an existing record in Cache for the given idempotency key.
406406
# This could be due to:
407407
# - An active idempotency record from a previous invocation that has not yet expired.
408408
# - An orphan record where a previous invocation has timed out.
409-
# - An expired idempotency record that has not been deleted by Redis.
409+
# - An expired idempotency record that has not been deleted by Cache.
410410
# In any case, we proceed to retrieve the record for further inspection.
411411

412412
idempotency_record = self._get_record(data_record.idempotency_key)
@@ -431,32 +431,30 @@ def _put_in_progress_record(self, data_record: DataRecord) -> None:
431431

432432
# Reaching this point indicates that the idempotency record found is an orphan record. An orphan record is
433433
# one that is neither completed nor in-progress within its expected time frame. It may result from a
434-
# previous invocation that has timed out or an expired record that has yet to be cleaned up by Redis.
434+
# previous invocation that has timed out or an expired record that has yet to be cleaned up by Cache.
435435
# We raise an error to handle this exceptional scenario appropriately.
436436
raise IdempotencyPersistenceConsistencyError
437437

438438
except IdempotencyPersistenceConsistencyError:
439439
# Handle an orphan record by attempting to acquire a lock, which by default lasts for 10 seconds.
440440
# The purpose of acquiring the lock is to prevent race conditions with other processes that might
441441
# also be trying to handle the same orphan record. Once the lock is acquired, we set a new value
442-
# for the idempotency record in Redis with the appropriate time-to-live (TTL).
442+
# for the idempotency record in Cache with the appropriate time-to-live (TTL).
443443
with self._acquire_lock(name=item["name"]):
444444
self.client.set(name=item["name"], value=encoded_item, ex=ttl)
445445

446446
# Not removing the lock here serves as a safeguard against race conditions,
447447
# preventing another operation from mistakenly treating this record as an orphan while the
448448
# current operation is still in progress.
449-
except (redis.exceptions.RedisError, redis.exceptions.RedisClusterException) as e:
450-
raise e
451449
except Exception as e:
452-
logger.debug(f"encountered non-Redis exception: {e}")
453-
raise e
450+
logger.debug(f"An error occurred: {e}")
451+
raise
454452

455453
@contextmanager
456454
def _acquire_lock(self, name: str):
457455
"""
458456
Attempt to acquire a lock for a specified resource name, with a default timeout.
459-
This context manager attempts to set a lock using Redis to prevent concurrent
457+
This context manager attempts to set a lock using Cache to prevent concurrent
460458
access to a resource identified by 'name'. It uses the 'nx' flag to ensure that
461459
the lock is only set if it does not already exist, thereby enforcing mutual exclusion.
462460
"""
@@ -500,15 +498,20 @@ def _update_record(self, data_record: DataRecord) -> None:
500498

501499
def _delete_record(self, data_record: DataRecord) -> None:
502500
"""
503-
Deletes the idempotency record associated with a given DataRecord from Redis.
501+
Deletes the idempotency record associated with a given DataRecord from Cache.
504502
This function is designed to be called after a Lambda handler invocation has completed processing.
505-
It ensures that the idempotency key associated with the DataRecord is removed from Redis to
503+
It ensures that the idempotency key associated with the DataRecord is removed from Cache to
506504
prevent future conflicts and to maintain the idempotency integrity.
507505
508506
Note: it is essential that the idempotency key is not empty, as that would indicate the Lambda
509507
handler has not been invoked or the key was not properly set.
510508
"""
511509
logger.debug(f"Deleting record for idempotency key: {data_record.idempotency_key}")
512510

513-
# See: https://redis.io/commands/del/
511+
# See: https://valkey.io/valkey-glide/python/core/#glide.async_commands.CoreCommands.delete
514512
self.client.delete(data_record.idempotency_key)
513+
514+
515+
CachePersistenceLayer: TypeAlias = RedisCachePersistenceLayer
516+
CacheClientProtocol: TypeAlias = RedisClientProtocol
517+
CacheConnection: TypeAlias = RedisConnection

0 commit comments

Comments
 (0)