Skip to content

Commit 68fa0c4

Browse files
Improving the documentation
1 parent c3674c0 commit 68fa0c4

File tree

7 files changed

+179
-57
lines changed

7 files changed

+179
-57
lines changed

aws_lambda_powertools/shared/functions.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -253,24 +253,30 @@ def dataclass_to_dict(data) -> dict:
253253
return dataclasses.asdict(data)
254254

255255

256-
def abs_lambda_path(relatvie_path="") -> str:
257-
"""Return the absolute path from the given relative path to lambda handler
256+
def abs_lambda_path(relative_path: str = "") -> str:
257+
"""Return the absolute path from the given relative path to lambda handler.
258258
259259
Parameters
260260
----------
261-
path : string
262-
the relative path to lambda handler, by default ""
261+
relative_path : str, optional
262+
The relative path to the lambda handler, by default an empty string.
263263
264264
Returns
265265
-------
266-
string
267-
the absolute path generated from the given relative path.
266+
str
267+
The absolute path generated from the given relative path.
268268
If the environment variable LAMBDA_TASK_ROOT is set, it will use that value.
269269
Otherwise, it will use the current working directory.
270270
If the path is empty, it will return the current working directory.
271271
"""
272+
# Retrieve the LAMBDA_TASK_ROOT environment variable or default to an empty string
272273
current_working_directory = os.environ.get("LAMBDA_TASK_ROOT", "")
274+
275+
# If LAMBDA_TASK_ROOT is not set, use the current working directory
273276
if not current_working_directory:
274277
current_working_directory = str(Path.cwd())
275-
Path(current_working_directory, relatvie_path)
276-
return str(Path(current_working_directory, relatvie_path))
278+
279+
# Combine the current working directory and the relative path to get the absolute path
280+
absolute_path = str(Path(current_working_directory, relative_path))
281+
282+
return absolute_path

docs/utilities/idempotency.md

+85-46
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The idempotency utility provides a simple solution to convert your Lambda functi
1414
* Select a subset of the event as the idempotency key using JMESPath expressions
1515
* Set a time window in which records with the same payload should be considered duplicates
1616
* Expires in-progress executions if the Lambda function times out halfway through
17+
* Support Amazon DynamoDB and Redis as persistence layer
1718

1819
## Terminology
1920

@@ -65,7 +66,7 @@ Your Lambda function IAM Role must have `dynamodb:GetItem`, `dynamodb:PutItem`,
6566

6667
Before getting started, you need to create a persistent storage layer where the idempotency utility can store its state - your lambda functions will need read and write access to it.
6768

68-
As of now, Amazon DynamoDB is the only supported persistent storage layer, so you'll need to create a table first.
69+
We currently support Amazon DynamoDB and Redis as a storage layer. This example demonstrates how to create a table in DynamoDB. If you want to use Redis, please go to the section [RedisPersistenceLayer](#redispersistencelayer)
6970

7071
**Default table configuration**
7172

@@ -336,6 +337,51 @@ If an Exception is raised _outside_ the scope of the decorated function and afte
336337

337338
As this happens outside the scope of your decorated function, you are not able to catch it if you're using the `idempotent` decorator on your Lambda handler.
338339

340+
### Persistence layers
341+
342+
#### DynamoDBPersistenceLayer
343+
344+
This persistence layer is built-in, and you can either use an existing DynamoDB table or create a new one dedicated for idempotency state (recommended).
345+
346+
=== "Customizing DynamoDBPersistenceLayer to suit your table structure"
347+
348+
```python hl_lines="7-15"
349+
--8<-- "examples/idempotency/src/customize_persistence_layer.py"
350+
```
351+
352+
When using DynamoDB as a persistence layer, you can alter the attribute names by passing these parameters when initializing the persistence layer:
353+
354+
| Parameter | Required | Default | Description |
355+
| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
356+
| **table_name** | :heavy_check_mark: | | Table name to store state |
357+
| **key_attr** | | `id` | Partition key of the table. Hashed representation of the payload (unless **sort_key_attr** is specified) |
358+
| **expiry_attr** | | `expiration` | Unix timestamp of when record expires |
359+
| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) |
360+
| **status_attr** | | `status` | Stores status of the lambda execution during and after invocation |
361+
| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers |
362+
| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation |
363+
| **sort_key_attr** | | | Sort key of the table (if table is configured with a sort key). |
364+
| **static_pk_value** | | `idempotency#{LAMBDA_FUNCTION_NAME}` | Static value to use as the partition key. Only used when **sort_key_attr** is set. |
365+
366+
#### RedisPersistenceLayer
367+
368+
This persistence layer is built-in, and you can use an existing Redis service. For optimal performance and compatibility, we strongly advise using a Redis service version 7 or higher.
369+
370+
=== "Customizing RedisPersistenceLayer to suit your data structure"
371+
372+
```python hl_lines="14-20"
373+
--8<-- "examples/idempotency/src/customize_persistence_layer_redis.py"
374+
```
375+
376+
When using Redis as a persistence layer, you can alter the attribute names by passing these parameters when initializing the persistence layer:
377+
378+
| Parameter | Required | Default | Description |
379+
| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
380+
| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) |
381+
| **status_attr** | | `status` | Stores status of the lambda execution during and after invocation |
382+
| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers |
383+
| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation |
384+
339385
### Idempotency request flow
340386

341387
The following sequence diagrams explain how the Idempotency feature behaves under different scenarios.
@@ -538,15 +584,23 @@ You need an existing Redis service before setting up Redis as persistent storage
538584
???+ tip "No existing Redis service?"
539585
If you don't have an existing Redis service, we recommend using [DynamoDB](#dynamodbpersistencelayer) as persistent storage layer provider.
540586

587+
=== "AWS CloudFormation example"
588+
589+
```yaml hl_lines="5"
590+
--8<-- "examples/idempotency/templates/cfn_redis_serverless.yaml"
591+
```
592+
593+
1. Replace the Security Group ID and Subnet ID to match your VPC settings.
594+
541595
### VPC Access
542596

543-
Your Lambda Function must be able to reach the Redis endpoint before using it for idempotency persistent storage layer. In most cases you will need to [configure VPC access](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html) for your Lambda Fucntion. Using a public accessable Redis is not recommended.
597+
Your Lambda Function must be able to reach the Redis endpoint before using it for idempotency persistent storage layer. In most cases you will need to [configure VPC access](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html) for your Lambda Function. Using a public accessible Redis is not recommended.
544598

545599
???+ tip "Amazon ElastiCache/MemoryDB for Redis as persistent storage layer provider"
546600
If you intend to use Amazon ElastiCache for Redis for idempotency persistent storage layer, you can also reference [This AWS Tutorial](https://docs.aws.amazon.com/lambda/latest/dg/services-elasticache-tutorial.html).
547601
If you are using Amazon MemoryDB for Redis, reference [This AWS Tutorial](https://aws.amazon.com/blogs/database/access-amazon-memorydb-for-redis-from-aws-lambda/) for only VPC setup part.
548602

549-
After VPC setup, you can follow the templates down below to setup Lambda fucntions with VPC internal subnet access.
603+
After VPC setup, you can follow the templates down below to setup Lambda functions with VPC internal subnet access.
550604

551605
=== "AWS Serverless Application Model (SAM) example"
552606

@@ -556,12 +610,12 @@ After VPC setup, you can follow the templates down below to setup Lambda fucntio
556610

557611
1. Replace the Security Group ID and Subnet ID to match your Redis' VPC setting.
558612

559-
### Idempotent decorator for Redis
613+
### Configuring Redis persistence layer
560614

561615
You can quickly start by initializing the `RedisCachePersistenceLayer` class and using it with the `idempotent` decorator on your lambda handler. Check out detailed example of `RedisCachePersistenceLayer` in [Persistence layers section](#redispersistencelayer)
562616

563617
???+ warning "Passing in Redis Client"
564-
We support passing in established Redis clients when initilizing `RedisPersistenceLayer`. However, this rely on Redis parameter `decode_responses=True` to decode all Redis response. Please make sure this parameter is set when establishing Redis client or `RedisPersistenceLayer` will raise a `IdempotencyRedisClientConfigError`. See example below
618+
We support passing in established Redis clients when initializing `RedisPersistenceLayer`. However, this rely on Redis parameter `decode_responses=True` to decode all Redis response. Please make sure this parameter is set when establishing Redis client or `RedisPersistenceLayer` will raise a `IdempotencyRedisClientConfigError`. See example below
565619

566620
=== "Use established Redis Client"
567621
```python hl_lines="4 7 12-16 18 32"
@@ -581,54 +635,39 @@ You can quickly start by initializing the `RedisCachePersistenceLayer` class and
581635
--8<-- "examples/idempotency/src/getting_started_with_idempotency_payload.json"
582636
```
583637

584-
For other use cases like `Idempotent function decorator` please reference the [DynamoDB section](#idempotent_function-decorator). You only need to substitute the `persistence_store` from `DynamoDBPersistenceLayer` to `RedisPersistenceLayer` and no other code changes are required.
585-
586-
## Advanced
587-
588-
### Persistence layers
589-
590-
#### DynamoDBPersistenceLayer
591-
592-
This persistence layer is built-in, and you can either use an existing DynamoDB table or create a new one dedicated for idempotency state (recommended).
638+
### Custom advanced settings
593639

594-
=== "Customizing DynamoDBPersistenceLayer to suit your table structure"
640+
For advanced settings, including SSL certificates and the ability to customize parameters such as a custom timeout, you can use the Redis client to accommodate these specific settings.
595641

596-
```python hl_lines="7-15"
597-
--8<-- "examples/idempotency/src/customize_persistence_layer.py"
642+
=== "Advanced configuration using AWS Secrets"
643+
```python hl_lines="8 10 20"
644+
--8<-- "examples/idempotency/src/using_redis_client_with_aws_secrets.py"
598645
```
599646

600-
When using DynamoDB as a persistence layer, you can alter the attribute names by passing these parameters when initializing the persistence layer:
601-
602-
| Parameter | Required | Default | Description |
603-
| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
604-
| **table_name** | :heavy_check_mark: | | Table name to store state |
605-
| **key_attr** | | `id` | Partition key of the table. Hashed representation of the payload (unless **sort_key_attr** is specified) |
606-
| **expiry_attr** | | `expiration` | Unix timestamp of when record expires |
607-
| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) |
608-
| **status_attr** | | `status` | Stores status of the lambda execution during and after invocation |
609-
| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers |
610-
| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation |
611-
| **sort_key_attr** | | | Sort key of the table (if table is configured with a sort key). |
612-
| **static_pk_value** | | `idempotency#{LAMBDA_FUNCTION_NAME}` | Static value to use as the partition key. Only used when **sort_key_attr** is set. |
613-
614-
#### RedisPersistenceLayer
615-
616-
This persistence layer is built-in, and you can use an existing Redis service. We don't recomend using Redis Persistence Layer if you don't have a exsiting Redis service. You can try [DynamoDBPersistenceLayer](#dynamodbpersistencelayer) instead.
617-
618-
=== "Customizing RedisPersistenceLayer to suit your data structure"
647+
1. JSON stored:
648+
{
649+
"REDIS_ENDPOINT": "127.0.0.1",
650+
"REDIS_PORT": "6379",
651+
"REDIS_PASSWORD": "redis-secret"
652+
}
619653

620-
```python hl_lines="14-20"
621-
--8<-- "examples/idempotency/src/customize_persistence_layer_redis.py"
654+
=== "Advanced configuration with local certificates"
655+
```python hl_lines="11 22-24"
656+
--8<-- "examples/idempotency/src/using_redis_client_with_local_certs.py"
622657
```
623658

624-
When using Redis as a persistence layer, you can alter the attribute names by passing these parameters when initializing the persistence layer:
659+
1. JSON stored:
660+
{
661+
"REDIS_ENDPOINT": "127.0.0.1",
662+
"REDIS_PORT": "6379",
663+
"REDIS_PASSWORD": "redis-secret"
664+
}
665+
2. Return the absolute path from the given relative path to lambda handler
666+
3. redis_user.crt file stored in the root directory of your Lambda function
667+
4. redis_user_private.key file stored in the root directory of your Lambda function
668+
5. redis_ca.pem file stored in the root directory of your Lambda function
625669

626-
| Parameter | Required | Default | Description |
627-
| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
628-
| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) |
629-
| **status_attr** | | `status` | Stores status of the lambda execution during and after invocation |
630-
| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers |
631-
| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation |
670+
## Advanced
632671

633672
### Customizing the default behavior
634673

@@ -858,7 +897,7 @@ The idempotency utility can be used with the `validator` decorator. Ensure that
858897
If you use an envelope with the validator, the event received by the idempotency utility will be the unwrapped
859898
event - not the "raw" event Lambda was invoked with.
860899

861-
Make sure to account for this behaviour, if you set the `event_key_jmespath`.
900+
Make sure to account for this behavior, if you set the `event_key_jmespath`.
862901

863902
=== "Using Idempotency with JSONSchema Validation utility"
864903

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from typing import Any
2+
3+
from redis import Redis
4+
5+
from aws_lambda_powertools.utilities import parameters
6+
from aws_lambda_powertools.utilities.idempotency import IdempotencyConfig, RedisCachePersistenceLayer, idempotent
7+
8+
redis_values: Any = parameters.get_secret("redis_info", transform="json") # (1)!
9+
10+
redis_client = Redis(
11+
host=redis_values.get("REDIS_HOST"),
12+
port=redis_values.get("REDIS_PORT"),
13+
password=redis_values.get("REDIS_PASSWORD"),
14+
decode_responses=True,
15+
socket_timeout=10.0,
16+
ssl=True,
17+
retry_on_timeout=True,
18+
)
19+
20+
persistence_layer = RedisCachePersistenceLayer(client=redis_client)
21+
config = IdempotencyConfig(
22+
expires_after_seconds=2 * 60, # 2 minutes
23+
)
24+
25+
26+
@idempotent(config=config, persistence_store=persistence_layer)
27+
def lambda_handler(event, context):
28+
return {"message": "Hello"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import Any
2+
3+
from redis import Redis
4+
5+
from aws_lambda_powertools.shared.functions import abs_lambda_path
6+
from aws_lambda_powertools.utilities import parameters
7+
from aws_lambda_powertools.utilities.idempotency import IdempotencyConfig, RedisCachePersistenceLayer, idempotent
8+
9+
redis_values: Any = parameters.get_secret("redis_info", transform="json") # (1)!
10+
11+
default_lambda_path = abs_lambda_path() # (2)!
12+
13+
14+
redis_client = Redis(
15+
host=redis_values.get("REDIS_HOST"),
16+
port=redis_values.get("REDIS_PORT"),
17+
password=redis_values.get("REDIS_PASSWORD"),
18+
decode_responses=True,
19+
socket_timeout=10.0,
20+
ssl=True,
21+
retry_on_timeout=True,
22+
ssl_certfile=f"{default_lambda_path}/redis_user.crt", # (3)!
23+
ssl_keyfile=f"{default_lambda_path}/redis_user_private.key", # (4)!
24+
ssl_ca_certs=f"{default_lambda_path}/redis_ca.pem", # (5)!
25+
)
26+
27+
persistence_layer = RedisCachePersistenceLayer(client=redis_client)
28+
config = IdempotencyConfig(
29+
expires_after_seconds=2 * 60, # 2 minutes
30+
)
31+
32+
33+
@idempotent(config=config, persistence_store=persistence_layer)
34+
def lambda_handler(event, context):
35+
return {"message": "Hello"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
3+
Resources:
4+
RedisServerlessIdempotency:
5+
Type: AWS::ElastiCache::ServerlessCache
6+
Properties:
7+
Engine: redis
8+
ServerlessCacheName: redis-cache
9+
SecurityGroupIds: # (1)!
10+
- security-{your_sg_id}
11+
SubnetIds:
12+
- subnet-{your_subnet_id_1}
13+
- subnet-{your_subnet_id_2}

0 commit comments

Comments
 (0)