Skip to content

docs(idempotency): improve navigation, wording, and new section on guarantees #4613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8b4a7b3
docs(idempotency): cleanup redis usage and link with setup/infra
heitorlessa May 23, 2024
92307cb
docs(idempotency): cleanup idempotent decorator; inline admonitions
heitorlessa May 27, 2024
06dc1ee
docs(idempotency): cleanup idempotent_decorator section
heitorlessa May 27, 2024
eb42534
docs(idempotency): cleanup serialization, fields subset, move batch t…
heitorlessa May 27, 2024
2344e01
docs: cleanup handling exceptions
heitorlessa May 28, 2024
6632396
docs: move caching to getting started
heitorlessa Jun 24, 2024
51a7af7
docs: use env var for DDB table, no hardcode
heitorlessa Jun 25, 2024
5807a1f
docs: moved expiration window to getting started; updated example to …
heitorlessa Jun 25, 2024
9f90c39
docs: include IdempotencyValidationError in example
heitorlessa Jun 25, 2024
66549a6
Fixing errors on Redis examples
leandrodamascena Jun 26, 2024
0a46164
docs(config): add social links
heitorlessa Jun 24, 2024
dbbd6f5
docs(idempotency): cleanup intro and key features
heitorlessa May 23, 2024
6d58fae
docs(idempotency): cleanup getting started ddb vs redis
heitorlessa May 23, 2024
11bc9fb
docs(idempotency): break iam permissions into table; IAM permission t…
heitorlessa May 23, 2024
e65e8bd
docs(idempotency): cleanup dynamodb required resource; break subsecti…
heitorlessa May 23, 2024
fa2b067
docs(idempotency): make terminologies crispier
heitorlessa May 24, 2024
108f456
docs(idempotency): line editing before decorators
heitorlessa May 27, 2024
dd90766
docs(idempotency): cleanup timeout section
heitorlessa May 27, 2024
cf39547
docs(idempotency): use cards for required resources
heitorlessa May 27, 2024
6776d79
docs(idempotency): note to skip timeout section when using handler de…
heitorlessa May 27, 2024
55a9fc6
docs: remove tabbed content for single timeout snippet
heitorlessa May 27, 2024
d900b12
docs: cleanup persistence layers attrs, snippet titles etc
heitorlessa May 28, 2024
6c02031
docs: typo in batch integration
heitorlessa May 28, 2024
14d4f4d
docs: rename batch integration to actual use case name
heitorlessa May 28, 2024
bb23ce9
docs(idempotency): cleanup default behavior section
heitorlessa Jun 6, 2024
f795c2e
docs: move bold to draw attention to whole event as idempotency key
heitorlessa Jun 24, 2024
aa25c42
docs: lead with parameter name over config name
heitorlessa Jun 25, 2024
8837b1d
docs: moved composite key under DDB section
heitorlessa Jun 25, 2024
0145c8d
docs: cut unnecessary anchor name
heitorlessa Jun 25, 2024
ea1792e
docs: fix broken links after sections renaming
heitorlessa Jun 25, 2024
97211e6
Making mypy happy
leandrodamascena Jun 27, 2024
9dc9e94
docs: fix conflicts out of order
heitorlessa Jul 23, 2024
aeece16
docs(leandro's feedback): add caching in key features
heitorlessa Jul 23, 2024
8d3fee2
Apply suggestions from code review
heitorlessa Jul 23, 2024
3c4e56b
docs(leandro's feedback): time window placement
heitorlessa Jul 23, 2024
fc02e17
docs(leandro's feedback): key features success vs failure ambiguity
heitorlessa Jul 23, 2024
ea159c9
docs(leandro's feedback): Redis anchor name
heitorlessa Jul 23, 2024
b1ad23e
docs(leandro's feedback): remove ambiguity on Redis VPC connectivity
heitorlessa Jul 23, 2024
f052d2c
Update docs/utilities/idempotency.md
heitorlessa Jul 23, 2024
738f2fc
Update docs/utilities/idempotency.md
heitorlessa Jul 23, 2024
f8318f4
docs: move primary key for both persistence storages plus additional ctx
heitorlessa Jul 25, 2024
30e1e89
Merge branch 'develop' into docs/idempotency-guarantees
leandrodamascena Jul 25, 2024
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
669 changes: 330 additions & 339 deletions docs/utilities/idempotency.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion examples/idempotency/src/customize_persistence_layer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import os

from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
idempotent,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(
table_name="IdempotencyTable",
table_name=table,
key_attr="idempotency_key",
expiry_attr="expires_at",
in_progress_expiry_attr="in_progress_expires_at",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from aws_lambda_powertools.utilities.idempotency import (
idempotent,
)
Expand All @@ -6,8 +8,9 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext

redis_endpoint = os.getenv("REDIS_CLUSTER_ENDPOINT", "localhost")
persistence_layer = RedisCachePersistenceLayer(
host="localhost",
host=redis_endpoint,
port=6379,
in_progress_expiry_attr="in_progress_expiration",
status_attr="status",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from dataclasses import dataclass, field
from uuid import uuid4

Expand All @@ -7,7 +8,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table)


@dataclass
Expand All @@ -17,8 +19,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")


class PaymentError(Exception):
...
class PaymentError(Exception): ...


@idempotent(persistence_store=persistence_layer)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from dataclasses import dataclass, field
from uuid import uuid4

Expand All @@ -11,8 +12,9 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext

redis_endpoint = os.getenv("REDIS_CLUSTER_ENDPOINT", "localhost")
client = Redis(
host="localhost",
host=redis_endpoint,
port=6379,
socket_connect_timeout=5,
socket_timeout=5,
Expand All @@ -29,8 +31,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")


class PaymentError(Exception):
...
class PaymentError(Exception): ...


@idempotent(persistence_store=persistence_layer)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from dataclasses import dataclass, field
from uuid import uuid4

Expand All @@ -9,7 +10,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext

persistence_layer = RedisCachePersistenceLayer(host="localhost", port=6379)
redis_endpoint = os.getenv("REDIS_CLUSTER_ENDPOINT", "localhost")
persistence_layer = RedisCachePersistenceLayer(host=redis_endpoint, port=6379)


@dataclass
Expand All @@ -19,8 +21,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")


class PaymentError(Exception):
...
class PaymentError(Exception): ...


@idempotent(persistence_store=persistence_layer)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType
import os
from typing import Any, Dict

from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
Expand All @@ -8,30 +10,24 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()
processor = BatchProcessor(event_type=EventType.SQS)

dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
config = IdempotencyConfig(
event_key_jmespath="messageId", # see Choosing a payload subset section
)
table = os.getenv("IDEMPOTENCY_TABLE", "")
dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="messageId")


@idempotent_function(data_keyword_argument="record", config=config, persistence_store=dynamodb)
def record_handler(record: SQSRecord):
return {"message": record.body}


def lambda_handler(event: SQSRecord, context: LambdaContext):
def lambda_handler(event: Dict[str, Any], context: LambdaContext):
config.register_lambda_context(context) # see Lambda timeouts section

# with Lambda context registered for Idempotency
# we can now kick in the Bach processing logic
batch = event["Records"]
with processor(records=batch, handler=record_handler):
# in case you want to access each record processed by your record_handler
# otherwise ignore the result variable assignment
processed_messages = processor.process()
logger.info(processed_messages)

return processor.response()
return process_partial_response(
event=event,
context=context,
processor=processor,
record_handler=record_handler,
)
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
Expand All @@ -6,8 +8,9 @@
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import envelopes, validator

table = os.getenv("IDEMPOTENCY_TABLE", "")
config = IdempotencyConfig(event_key_jmespath='["message", "username"]')
persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
persistence_layer = DynamoDBPersistenceLayer(table_name=table)


@validator(envelope=envelopes.API_GATEWAY_HTTP)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Any

from redis import Redis
Expand All @@ -8,11 +10,11 @@
RedisCachePersistenceLayer,
)

redis_values: Any = parameters.get_secret("redis_info", transform="json") # (1)!
redis_values: dict[str, Any] = parameters.get_secret("redis_info", transform="json") # (1)!

redis_client = Redis(
host=redis_values.get("REDIS_HOST"),
port=redis_values.get("REDIS_PORT"),
host=redis_values.get("REDIS_HOST", "localhost"),
port=redis_values.get("REDIS_PORT", 6379),
password=redis_values.get("REDIS_PASSWORD"),
decode_responses=True,
socket_timeout=10.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing import Any

from redis import Redis
Expand All @@ -9,12 +11,12 @@
RedisCachePersistenceLayer,
)

redis_values: Any = parameters.get_secret("redis_info", transform="json") # (1)!
redis_values: dict[str, Any] = parameters.get_secret("redis_info", transform="json") # (1)!


redis_client = Redis(
host=redis_values.get("REDIS_HOST"),
port=redis_values.get("REDIS_PORT"),
host=redis_values.get("REDIS_HOST", "localhost"),
port=redis_values.get("REDIS_PORT", 6379),
password=redis_values.get("REDIS_PASSWORD"),
decode_responses=True,
socket_timeout=10.0,
Expand Down
5 changes: 4 additions & 1 deletion examples/idempotency/src/working_with_composite_key.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import os

from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
idempotent,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable", sort_key_attr="sort_key")
table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table, sort_key_attr="sort_key")


@idempotent(persistence_store=persistence_layer)
Expand Down
5 changes: 4 additions & 1 deletion examples/idempotency/src/working_with_custom_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from botocore.config import Config

from aws_lambda_powertools.utilities.idempotency import (
Expand All @@ -10,7 +12,8 @@
# See: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore-config
boto_config = Config()

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable", boto_config=boto_config)
table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table, boto_config=boto_config)

config = IdempotencyConfig(event_key_jmespath="body")

Expand Down
5 changes: 4 additions & 1 deletion examples/idempotency/src/working_with_custom_session.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

import boto3

from aws_lambda_powertools.utilities.idempotency import (
Expand All @@ -10,7 +12,8 @@
# See: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html#module-boto3.session
boto3_session = boto3.session.Session()

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable", boto3_session=boto3_session)
table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table, boto3_session=boto3_session)

config = IdempotencyConfig(event_key_jmespath="body")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from dataclasses import dataclass

from aws_lambda_powertools.utilities.idempotency import (
Expand All @@ -8,7 +9,8 @@
from aws_lambda_powertools.utilities.idempotency.serialization.dataclass import DataclassSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext

dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
table = os.getenv("IDEMPOTENCY_TABLE", "")
dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section


Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from dataclasses import dataclass

from aws_lambda_powertools.utilities.idempotency import (
Expand All @@ -8,7 +9,8 @@
from aws_lambda_powertools.utilities.idempotency.serialization.dataclass import DataclassSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext

dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
table = os.getenv("IDEMPOTENCY_TABLE", "")
dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section


Expand Down
37 changes: 19 additions & 18 deletions examples/idempotency/src/working_with_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
import os

import requests

from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
idempotent_function,
)
from aws_lambda_powertools.utilities.idempotency.exceptions import IdempotencyPersistenceLayerError
from aws_lambda_powertools.utilities.typing import LambdaContext

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table)

config = IdempotencyConfig()


def lambda_handler(event: dict, context: LambdaContext):
# If an exception is raised here, no idempotent record will ever get created as the
# idempotent function does not get called
try:
endpoint = "https://jsonplaceholder.typicode.com/comments/" # change this endpoint to force an exception
requests.get(endpoint)
except Exception as exc:
return str(exc)

call_external_service(data={"user": "user1", "id": 5})

# This exception will not cause the idempotent record to be deleted, since it
# happens after the decorated function has been successfully called
raise Exception


@idempotent_function(data_keyword_argument="data", config=config, persistence_store=persistence_layer)
def call_external_service(data: dict):
# Any exception raised will lead to idempotency record to be deleted
result: requests.Response = requests.post(
"https://jsonplaceholder.typicode.com/comments/",
json={"user": data["user"], "transaction_id": data["id"]},
json=data,
)
return result.json()


def lambda_handler(event: dict, context: LambdaContext):
try:
call_external_service(data=event)
except IdempotencyPersistenceLayerError as e:
# No idempotency, but you can decide to error differently.
raise RuntimeError(f"Oops, can't talk to persistence layer. Permissions? error: {e}")

# This exception will not impact the idempotency of 'call_external_service'
# because it happens in isolation, or outside their scope.
raise SyntaxError("Oops, this shouldn't be here.")
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import os

from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
idempotent,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(
event_key_jmespath='["user.uid", "order_id"]',
raise_on_no_idempotency_key=True,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from typing import Dict, Type

from aws_lambda_powertools.utilities.idempotency import (
Expand All @@ -8,7 +9,8 @@
from aws_lambda_powertools.utilities.idempotency.serialization.custom_dict import CustomDictSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext

dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
table = os.getenv("IDEMPOTENCY_TABLE", "")
dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section


Expand Down
Loading