Skip to content

Commit

Permalink
refactor(idempotency): add from __future__ import annotations
Browse files Browse the repository at this point in the history
and update code according to ruff rules TCH, UP006, UP007, UP037 and
FA100.
  • Loading branch information
ericbn committed Aug 13, 2024
1 parent 2d59b7a commit 6c0bb17
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 113 deletions.
48 changes: 26 additions & 22 deletions aws_lambda_powertools/utilities/idempotency/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from __future__ import annotations

import datetime
import logging
from copy import deepcopy
from typing import Any, Callable, Dict, Optional, Tuple
from typing import TYPE_CHECKING, Any, Callable

from aws_lambda_powertools.utilities.idempotency.config import (
IdempotencyConfig,
)
from aws_lambda_powertools.utilities.idempotency.exceptions import (
IdempotencyAlreadyInProgressError,
IdempotencyInconsistentStateError,
Expand All @@ -15,20 +14,25 @@
IdempotencyPersistenceLayerError,
IdempotencyValidationError,
)
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
BasePersistenceLayer,
)
from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import (
STATUS_CONSTANTS,
DataRecord,
)
from aws_lambda_powertools.utilities.idempotency.serialization.base import (
BaseIdempotencySerializer,
)
from aws_lambda_powertools.utilities.idempotency.serialization.no_op import (
NoOpSerializer,
)

if TYPE_CHECKING:
from aws_lambda_powertools.utilities.idempotency.config import (
IdempotencyConfig,
)
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
BasePersistenceLayer,
)
from aws_lambda_powertools.utilities.idempotency.serialization.base import (
BaseIdempotencySerializer,
)

MAX_RETRIES = 2
logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -69,9 +73,9 @@ def __init__(
function_payload: Any,
config: IdempotencyConfig,
persistence_store: BasePersistenceLayer,
output_serializer: Optional[BaseIdempotencySerializer] = None,
function_args: Optional[Tuple] = None,
function_kwargs: Optional[Dict] = None,
output_serializer: BaseIdempotencySerializer | None = None,
function_args: tuple | None = None,
function_kwargs: dict | None = None,
):
"""
Initialize the IdempotencyHandler
Expand All @@ -84,12 +88,12 @@ def __init__(
Idempotency Configuration
persistence_store : BasePersistenceLayer
Instance of persistence layer to store idempotency records
output_serializer: Optional[BaseIdempotencySerializer]
output_serializer: BaseIdempotencySerializer | None
Serializer to transform the data to and from a dictionary.
If not supplied, no serialization is done via the NoOpSerializer
function_args: Optional[Tuple]
function_args: tuple | None
Function arguments
function_kwargs: Optional[Dict]
function_kwargs: dict | None
Function keyword arguments
"""
self.function = function
Expand Down Expand Up @@ -150,7 +154,7 @@ def _process_idempotency(self):

return self._get_function_response()

def _get_remaining_time_in_millis(self) -> Optional[int]:
def _get_remaining_time_in_millis(self) -> int | None:
"""
Tries to determine the remaining time available for the current lambda invocation.
Expand All @@ -160,7 +164,7 @@ def _get_remaining_time_in_millis(self) -> Optional[int]:
Returns
-------
Optional[int]
int | None
Remaining time in millis, or None if the remaining time cannot be determined.
"""

Expand All @@ -169,7 +173,7 @@ def _get_remaining_time_in_millis(self) -> Optional[int]:

return None

def _get_idempotency_record(self) -> Optional[DataRecord]:
def _get_idempotency_record(self) -> DataRecord | None:
"""
Retrieve the idempotency record from the persistence layer.
Expand Down Expand Up @@ -198,7 +202,7 @@ def _get_idempotency_record(self) -> Optional[DataRecord]:

return data_record

def _handle_for_status(self, data_record: DataRecord) -> Optional[Any]:
def _handle_for_status(self, data_record: DataRecord) -> Any | None:
"""
Take appropriate action based on data_record's status
Expand All @@ -208,7 +212,7 @@ def _handle_for_status(self, data_record: DataRecord) -> Optional[Any]:
Returns
-------
Optional[Any]
Any | None
Function's response previously used for this idempotency key, if it has successfully executed already.
In case an output serializer is configured, the response is deserialized.
Expand All @@ -235,7 +239,7 @@ def _handle_for_status(self, data_record: DataRecord) -> Optional[Any]:
f"Execution already in progress with idempotency key: "
f"{self.persistence_store.event_key_jmespath}={data_record.idempotency_key}",
)
response_dict: Optional[dict] = data_record.response_json_as_dict()
response_dict: dict | None = data_record.response_json_as_dict()
if response_dict is not None:
serialized_response = self.output_serializer.from_dict(response_dict)
if self.config.response_hook is not None:
Expand Down
19 changes: 11 additions & 8 deletions aws_lambda_powertools/utilities/idempotency/config.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from typing import Dict, Optional
from __future__ import annotations

from aws_lambda_powertools.utilities.idempotency import IdempotentHookFunction
from aws_lambda_powertools.utilities.typing import LambdaContext
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from aws_lambda_powertools.utilities.idempotency import IdempotentHookFunction
from aws_lambda_powertools.utilities.typing import LambdaContext


class IdempotencyConfig:
def __init__(
self,
event_key_jmespath: str = "",
payload_validation_jmespath: str = "",
jmespath_options: Optional[Dict] = None,
jmespath_options: dict | None = None,
raise_on_no_idempotency_key: bool = False,
expires_after_seconds: int = 60 * 60, # 1 hour default
use_local_cache: bool = False,
local_cache_max_items: int = 256,
hash_function: str = "md5",
lambda_context: Optional[LambdaContext] = None,
response_hook: Optional[IdempotentHookFunction] = None,
lambda_context: LambdaContext | None = None,
response_hook: IdempotentHookFunction | None = None,
):
"""
Initialize the base persistence layer
Expand Down Expand Up @@ -50,8 +53,8 @@ def __init__(
self.use_local_cache = use_local_cache
self.local_cache_max_items = local_cache_max_items
self.hash_function = hash_function
self.lambda_context: Optional[LambdaContext] = lambda_context
self.response_hook: Optional[IdempotentHookFunction] = response_hook
self.lambda_context: LambdaContext | None = lambda_context
self.response_hook: IdempotentHookFunction | None = response_hook

def register_lambda_context(self, lambda_context: LambdaContext):
"""Captures the Lambda context, to calculate the remaining time before the invocation times out"""
Expand Down
11 changes: 7 additions & 4 deletions aws_lambda_powertools/utilities/idempotency/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
Idempotency errors
"""

from typing import Optional, Union
from __future__ import annotations

from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import DataRecord
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import DataRecord


class BaseError(Exception):
Expand All @@ -13,7 +16,7 @@ class BaseError(Exception):
See https://github.com/aws-powertools/powertools-lambda-python/issues/1772
"""

def __init__(self, *args: Optional[Union[str, Exception]]):
def __init__(self, *args: str | Exception | None):
self.message = str(args[0]) if args else ""
self.details = "".join(str(arg) for arg in args[1:]) if args[1:] else None

Expand All @@ -31,7 +34,7 @@ class IdempotencyItemAlreadyExistsError(BaseError):
Item attempting to be inserted into persistence store already exists and is not expired
"""

def __init__(self, *args: Optional[Union[str, Exception]], old_data_record: Optional[DataRecord] = None):
def __init__(self, *args: str | Exception | None, old_data_record: DataRecord | None = None):
self.old_data_record = old_data_record
super().__init__(*args)

Expand Down
7 changes: 5 additions & 2 deletions aws_lambda_powertools/utilities/idempotency/hook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from typing import Any, Protocol
from __future__ import annotations

from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import DataRecord
from typing import TYPE_CHECKING, Any, Protocol

if TYPE_CHECKING:
from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import DataRecord


class IdempotentHookFunction(Protocol):
Expand Down
30 changes: 17 additions & 13 deletions aws_lambda_powertools/utilities/idempotency/idempotency.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,40 @@
Primary interface for idempotent Lambda functions utility
"""

from __future__ import annotations

import functools
import logging
import os
from inspect import isclass
from typing import Any, Callable, Dict, Optional, Type, Union, cast
from typing import TYPE_CHECKING, Any, Callable, cast

from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.shared.types import AnyCallableT
from aws_lambda_powertools.utilities.idempotency.base import IdempotencyHandler
from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
BasePersistenceLayer,
)
from aws_lambda_powertools.utilities.idempotency.serialization.base import (
BaseIdempotencyModelSerializer,
BaseIdempotencySerializer,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

if TYPE_CHECKING:
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
BasePersistenceLayer,
)
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = logging.getLogger(__name__)


@lambda_handler_decorator
def idempotent(
handler: Callable[[Any, LambdaContext], Any],
event: Dict[str, Any],
event: dict[str, Any],
context: LambdaContext,
persistence_store: BasePersistenceLayer,
config: Optional[IdempotencyConfig] = None,
config: IdempotencyConfig | None = None,
**kwargs,
) -> Any:
"""
Expand All @@ -41,9 +45,9 @@ def idempotent(
----------
handler: Callable
Lambda's handler
event: Dict
event: dict
Lambda's Event
context: Dict
context: dict
Lambda's Context
persistence_store: BasePersistenceLayer
Instance of BasePersistenceLayer to store data
Expand Down Expand Up @@ -86,12 +90,12 @@ def idempotent(


def idempotent_function(
function: Optional[AnyCallableT] = None,
function: AnyCallableT | None = None,
*,
data_keyword_argument: str,
persistence_store: BasePersistenceLayer,
config: Optional[IdempotencyConfig] = None,
output_serializer: Optional[Union[BaseIdempotencySerializer, Type[BaseIdempotencyModelSerializer]]] = None,
config: IdempotencyConfig | None = None,
output_serializer: BaseIdempotencySerializer | type[BaseIdempotencyModelSerializer] | None = None,
**kwargs: Any,
) -> Any:
"""
Expand All @@ -107,7 +111,7 @@ def idempotent_function(
Instance of BasePersistenceLayer to store data
config: IdempotencyConfig
Configuration
output_serializer: Optional[Union[BaseIdempotencySerializer, Type[BaseIdempotencyModelSerializer]]]
output_serializer: BaseIdempotencySerializer | type[BaseIdempotencyModelSerializer] | None
Serializer to transform the data to and from a dictionary.
If not supplied, no serialization is done via the NoOpSerializer.
In case a serializer of type inheriting BaseIdempotencyModelSerializer is given,
Expand Down
Loading

0 comments on commit 6c0bb17

Please sign in to comment.