From dccc3dc19a487b5f0c24b3e76f81472a8864e2ce Mon Sep 17 00:00:00 2001 From: Eric Nielsen <4120606+ericbn@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:53:25 -0500 Subject: [PATCH] refactor(idempotency): add from __future__ import annotations and update code according to ruff rules TCH, UP006, UP007, UP037 and FA100. --- .../utilities/idempotency/base.py | 48 ++++++++++--------- .../utilities/idempotency/config.py | 19 ++++---- .../utilities/idempotency/exceptions.py | 11 +++-- .../utilities/idempotency/hook.py | 7 ++- .../utilities/idempotency/idempotency.py | 30 +++++++----- .../utilities/idempotency/persistence/base.py | 42 ++++++++-------- .../idempotency/persistence/datarecord.py | 11 +++-- .../idempotency/persistence/dynamodb.py | 20 ++++---- .../idempotency/persistence/redis.py | 10 ++-- .../idempotency/serialization/base.py | 8 ++-- .../idempotency/serialization/custom_dict.py | 18 +++---- .../idempotency/serialization/dataclass.py | 14 +++--- .../idempotency/serialization/no_op.py | 6 +-- .../idempotency/serialization/pydantic.py | 12 +++-- 14 files changed, 143 insertions(+), 113 deletions(-) diff --git a/aws_lambda_powertools/utilities/idempotency/base.py b/aws_lambda_powertools/utilities/idempotency/base.py index 71a4476c443..9b54421e40b 100644 --- a/aws_lambda_powertools/utilities/idempotency/base.py +++ b/aws_lambda_powertools/utilities/idempotency/base.py @@ -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, @@ -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__) @@ -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 @@ -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 @@ -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. @@ -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. """ @@ -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. @@ -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 @@ -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. @@ -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: diff --git a/aws_lambda_powertools/utilities/idempotency/config.py b/aws_lambda_powertools/utilities/idempotency/config.py index 826dbbe4089..50fee21845c 100644 --- a/aws_lambda_powertools/utilities/idempotency/config.py +++ b/aws_lambda_powertools/utilities/idempotency/config.py @@ -1,7 +1,10 @@ -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: @@ -9,14 +12,14 @@ 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 @@ -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""" diff --git a/aws_lambda_powertools/utilities/idempotency/exceptions.py b/aws_lambda_powertools/utilities/idempotency/exceptions.py index 27f319a5266..31185cabf43 100644 --- a/aws_lambda_powertools/utilities/idempotency/exceptions.py +++ b/aws_lambda_powertools/utilities/idempotency/exceptions.py @@ -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): @@ -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 @@ -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) diff --git a/aws_lambda_powertools/utilities/idempotency/hook.py b/aws_lambda_powertools/utilities/idempotency/hook.py index 1167e6264bf..6c3da299893 100644 --- a/aws_lambda_powertools/utilities/idempotency/hook.py +++ b/aws_lambda_powertools/utilities/idempotency/hook.py @@ -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): diff --git a/aws_lambda_powertools/utilities/idempotency/idempotency.py b/aws_lambda_powertools/utilities/idempotency/idempotency.py index 9593655b3cd..56b4620a777 100644 --- a/aws_lambda_powertools/utilities/idempotency/idempotency.py +++ b/aws_lambda_powertools/utilities/idempotency/idempotency.py @@ -2,25 +2,29 @@ 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__) @@ -28,10 +32,10 @@ @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: """ @@ -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 @@ -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: """ @@ -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, diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/base.py b/aws_lambda_powertools/utilities/idempotency/persistence/base.py index 95736634ca6..c9ed6c03d08 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/base.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/base.py @@ -2,6 +2,8 @@ Persistence layers supporting idempotency """ +from __future__ import annotations + import datetime import hashlib import json @@ -9,14 +11,13 @@ import os import warnings from abc import ABC, abstractmethod -from typing import Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any import jmespath from aws_lambda_powertools.shared import constants from aws_lambda_powertools.shared.cache_dict import LRUDict from aws_lambda_powertools.shared.json_encoder import Encoder -from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig from aws_lambda_powertools.utilities.idempotency.exceptions import ( IdempotencyItemAlreadyExistsError, IdempotencyKeyError, @@ -28,6 +29,9 @@ ) from aws_lambda_powertools.utilities.jmespath_utils import PowertoolsFunctions +if TYPE_CHECKING: + from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig + logger = logging.getLogger(__name__) @@ -42,7 +46,7 @@ def __init__(self): self.configured = False self.event_key_jmespath: str = "" self.event_key_compiled_jmespath = None - self.jmespath_options: Optional[dict] = None + self.jmespath_options: dict | None = None self.payload_validation_enabled = False self.validation_key_jmespath = None self.raise_on_no_idempotency_key = False @@ -50,7 +54,7 @@ def __init__(self): self.use_local_cache = False self.hash_function = hashlib.md5 - def configure(self, config: IdempotencyConfig, function_name: Optional[str] = None) -> None: + def configure(self, config: IdempotencyConfig, function_name: str | None = None) -> None: """ Initialize the base persistence layer from the configuration settings @@ -84,13 +88,13 @@ def configure(self, config: IdempotencyConfig, function_name: Optional[str] = No self._cache = LRUDict(max_items=config.local_cache_max_items) self.hash_function = getattr(hashlib, config.hash_function) - def _get_hashed_idempotency_key(self, data: Dict[str, Any]) -> Optional[str]: + def _get_hashed_idempotency_key(self, data: dict[str, Any]) -> str | None: """ Extract idempotency key and return a hashed representation Parameters ---------- - data: Dict[str, Any] + data: dict[str, Any] Incoming data Returns @@ -123,13 +127,13 @@ def is_missing_idempotency_key(data) -> bool: return False return not data - def _get_hashed_payload(self, data: Dict[str, Any]) -> str: + def _get_hashed_payload(self, data: dict[str, Any]) -> str: """ Extract payload using validation key jmespath and return a hashed representation Parameters ---------- - data: Dict[str, Any] + data: dict[str, Any] Payload Returns @@ -163,7 +167,7 @@ def _generate_hash(self, data: Any) -> str: def _validate_payload( self, - data_payload: Union[Dict[str, Any], DataRecord], + data_payload: dict[str, Any] | DataRecord, stored_data_record: DataRecord, ) -> None: """ @@ -171,7 +175,7 @@ def _validate_payload( Parameters ---------- - data_payload: Union[Dict[str, Any], DataRecord] + data_payload: dict[str, Any] | DataRecord Payload stored_data_record: DataRecord DataRecord fetched from Dynamo or cache @@ -242,13 +246,13 @@ def _delete_from_cache(self, idempotency_key: str): if idempotency_key in self._cache: del self._cache[idempotency_key] - def save_success(self, data: Dict[str, Any], result: dict) -> None: + def save_success(self, data: dict[str, Any], result: dict) -> None: """ Save record of function's execution completing successfully Parameters ---------- - data: Dict[str, Any] + data: dict[str, Any] Payload result: dict The response from function @@ -276,15 +280,15 @@ def save_success(self, data: Dict[str, Any], result: dict) -> None: self._save_to_cache(data_record=data_record) - def save_inprogress(self, data: Dict[str, Any], remaining_time_in_millis: Optional[int] = None) -> None: + def save_inprogress(self, data: dict[str, Any], remaining_time_in_millis: int | None = None) -> None: """ Save record of function's execution being in progress Parameters ---------- - data: Dict[str, Any] + data: dict[str, Any] Payload - remaining_time_in_millis: Optional[int] + remaining_time_in_millis: int | None If expiry of in-progress invocations is enabled, this will contain the remaining time available in millis """ @@ -320,13 +324,13 @@ def save_inprogress(self, data: Dict[str, Any], remaining_time_in_millis: Option self._put_record(data_record=data_record) - def delete_record(self, data: Dict[str, Any], exception: Exception): + def delete_record(self, data: dict[str, Any], exception: Exception): """ Delete record from the persistence store Parameters ---------- - data: Dict[str, Any] + data: dict[str, Any] Payload exception The exception raised by the function @@ -348,13 +352,13 @@ def delete_record(self, data: Dict[str, Any], exception: Exception): self._delete_from_cache(idempotency_key=data_record.idempotency_key) - def get_record(self, data: Dict[str, Any]) -> Optional[DataRecord]: + def get_record(self, data: dict[str, Any]) -> DataRecord | None: """ Retrieve idempotency key for data provided, fetch from persistence store, and convert to DataRecord. Parameters ---------- - data: Dict[str, Any] + data: dict[str, Any] Payload Returns diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py b/aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py index 607e238c3a0..3cbdd1da468 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py @@ -2,11 +2,12 @@ Data Class for idempotency records. """ +from __future__ import annotations + import datetime import json import logging from types import MappingProxyType -from typing import Optional logger = logging.getLogger(__name__) @@ -22,8 +23,8 @@ def __init__( self, idempotency_key: str, status: str = "", - expiry_timestamp: Optional[int] = None, - in_progress_expiry_timestamp: Optional[int] = None, + expiry_timestamp: int | None = None, + in_progress_expiry_timestamp: int | None = None, response_data: str = "", payload_hash: str = "", ) -> None: @@ -81,13 +82,13 @@ def status(self) -> str: raise IdempotencyInvalidStatusError(self._status) - def response_json_as_dict(self) -> Optional[dict]: + def response_json_as_dict(self) -> dict | None: """ Get response data deserialized to python dict Returns ------- - Optional[dict] + dict | None previous response data deserialized """ return json.loads(self.response_data) if self.response_data else None diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py b/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py index 88305b07b74..78b672385ca 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py @@ -3,11 +3,10 @@ import datetime import logging import os -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any import boto3 from boto3.dynamodb.types import TypeDeserializer -from botocore.config import Config from botocore.exceptions import ClientError from aws_lambda_powertools.shared import constants, user_agent @@ -23,6 +22,7 @@ ) if TYPE_CHECKING: + from botocore.config import Config from mypy_boto3_dynamodb.client import DynamoDBClient from mypy_boto3_dynamodb.type_defs import AttributeValueTypeDef @@ -34,16 +34,16 @@ def __init__( self, table_name: str, key_attr: str = "id", - static_pk_value: Optional[str] = None, - sort_key_attr: Optional[str] = None, + static_pk_value: str | None = None, + sort_key_attr: str | None = None, expiry_attr: str = "expiration", in_progress_expiry_attr: str = "in_progress_expiration", status_attr: str = "status", data_attr: str = "data", validation_key_attr: str = "validation", - boto_config: Optional[Config] = None, - boto3_session: Optional[boto3.session.Session] = None, - boto3_client: Optional[DynamoDBClient] = None, + boto_config: Config | None = None, + boto3_session: boto3.session.Session | None = None, + boto3_client: DynamoDBClient | None = None, ): """ Initialize the DynamoDB client @@ -145,13 +145,13 @@ def _get_key(self, idempotency_key: str) -> dict: return {self.key_attr: {"S": self.static_pk_value}, self.sort_key_attr: {"S": idempotency_key}} return {self.key_attr: {"S": idempotency_key}} - def _item_to_data_record(self, item: Dict[str, Any]) -> DataRecord: + def _item_to_data_record(self, item: dict[str, Any]) -> DataRecord: """ Translate raw item records from DynamoDB to DataRecord Parameters ---------- - item: Dict[str, Union[str, int]] + item: dict[str, str | int] Item format from dynamodb response Returns @@ -297,7 +297,7 @@ def boto3_supports_condition_check_failure(boto3_version: str) -> bool: def _update_record(self, data_record: DataRecord): logger.debug(f"Updating record for idempotency key: {data_record.idempotency_key}") update_expression = "SET #response_data = :response_data, #expiry = :expiry, #status = :status" - expression_attr_values: Dict[str, AttributeValueTypeDef] = { + expression_attr_values: dict[str, AttributeValueTypeDef] = { ":expiry": {"N": str(data_record.expiry_timestamp)}, ":response_data": {"S": data_record.response_data}, ":status": {"S": data_record.status}, diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/redis.py b/aws_lambda_powertools/utilities/idempotency/persistence/redis.py index 44e3767312a..7226245a9b5 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/redis.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/redis.py @@ -5,7 +5,7 @@ import logging from contextlib import contextmanager from datetime import timedelta -from typing import Any, Dict, Literal, Protocol +from typing import Any, Literal, Protocol import redis @@ -185,7 +185,7 @@ def _init_client(self) -> RedisClientProtocol: return client.from_url(url=self.url) else: # Redis in cluster mode doesn't support db parameter - extra_param_connection: Dict[str, Any] = {} + extra_param_connection: dict[str, Any] = {} if self.mode != "cluster": extra_param_connection = {"db": self.db_index} @@ -321,7 +321,7 @@ def _get_expiry_second(self, expiry_timestamp: int | None = None) -> int: return expiry_timestamp - int(datetime.datetime.now().timestamp()) return self.expires_after_seconds - def _item_to_data_record(self, idempotency_key: str, item: Dict[str, Any]) -> DataRecord: + def _item_to_data_record(self, idempotency_key: str, item: dict[str, Any]) -> DataRecord: in_progress_expiry_timestamp = item.get(self.in_progress_expiry_attr) return DataRecord( @@ -357,7 +357,7 @@ def _get_record(self, idempotency_key) -> DataRecord: return self._item_to_data_record(idempotency_key, item) def _put_in_progress_record(self, data_record: DataRecord) -> None: - item: Dict[str, Any] = { + item: dict[str, Any] = { "name": data_record.idempotency_key, "mapping": { self.status_attr: data_record.status, @@ -480,7 +480,7 @@ def _put_record(self, data_record: DataRecord) -> None: raise NotImplementedError def _update_record(self, data_record: DataRecord) -> None: - item: Dict[str, Any] = { + item: dict[str, Any] = { "name": data_record.idempotency_key, "mapping": { self.data_attr: data_record.response_data, diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/base.py b/aws_lambda_powertools/utilities/idempotency/serialization/base.py index ba41b23dbe4..b7ad60d8677 100644 --- a/aws_lambda_powertools/utilities/idempotency/serialization/base.py +++ b/aws_lambda_powertools/utilities/idempotency/serialization/base.py @@ -2,8 +2,10 @@ Serialization for supporting idempotency """ +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Any, Dict +from typing import Any class BaseIdempotencySerializer(ABC): @@ -12,11 +14,11 @@ class BaseIdempotencySerializer(ABC): """ @abstractmethod - def to_dict(self, data: Any) -> Dict: + def to_dict(self, data: Any) -> dict: raise NotImplementedError("Implementation of to_dict is required") @abstractmethod - def from_dict(self, data: Dict) -> Any: + def from_dict(self, data: dict) -> Any: raise NotImplementedError("Implementation of from_dict is required") diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py b/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py index 2af8bed08b0..3483f86b3a3 100644 --- a/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py +++ b/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py @@ -1,23 +1,25 @@ -from typing import Any, Callable, Dict +from __future__ import annotations + +from typing import Any, Callable from aws_lambda_powertools.utilities.idempotency.serialization.base import BaseIdempotencySerializer class CustomDictSerializer(BaseIdempotencySerializer): - def __init__(self, to_dict: Callable[[Any], Dict], from_dict: Callable[[Dict], Any]): + def __init__(self, to_dict: Callable[[Any], dict], from_dict: Callable[[dict], Any]): """ Parameters ---------- - to_dict: Callable[[Any], Dict] + to_dict: Callable[[Any], dict] A function capable of transforming the saved data object representation into a dictionary - from_dict: Callable[[Dict], Any] + from_dict: Callable[[dict], Any] A function capable of transforming the saved dictionary into the original data object representation """ - self.__to_dict: Callable[[Any], Dict] = to_dict - self.__from_dict: Callable[[Dict], Any] = from_dict + self.__to_dict: Callable[[Any], dict] = to_dict + self.__from_dict: Callable[[dict], Any] = from_dict - def to_dict(self, data: Any) -> Dict: + def to_dict(self, data: Any) -> dict: return self.__to_dict(data) - def from_dict(self, data: Dict) -> Any: + def from_dict(self, data: dict) -> Any: return self.__from_dict(data) diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py b/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py index dac77ed7345..d225299ecaf 100644 --- a/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py +++ b/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from dataclasses import asdict, is_dataclass -from typing import Any, Dict, Type +from typing import Any from aws_lambda_powertools.utilities.idempotency.exceptions import ( IdempotencyModelTypeError, @@ -18,19 +20,19 @@ class DataclassSerializer(BaseIdempotencyModelSerializer): A serializer class for transforming data between dataclass objects and dictionaries. """ - def __init__(self, model: Type[DataClass]): + def __init__(self, model: type[DataClass]): """ Parameters ---------- - model: Type[DataClass] + model: type[DataClass] A dataclass type to be used for serialization and deserialization """ - self.__model: Type[DataClass] = model + self.__model: type[DataClass] = model - def to_dict(self, data: DataClass) -> Dict: + def to_dict(self, data: DataClass) -> dict: return asdict(data) - def from_dict(self, data: Dict) -> DataClass: + def from_dict(self, data: dict) -> DataClass: return self.__model(**data) @classmethod diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/no_op.py b/aws_lambda_powertools/utilities/idempotency/serialization/no_op.py index 59185f704e7..360e1ce5607 100644 --- a/aws_lambda_powertools/utilities/idempotency/serialization/no_op.py +++ b/aws_lambda_powertools/utilities/idempotency/serialization/no_op.py @@ -1,4 +1,4 @@ -from typing import Dict +from __future__ import annotations from aws_lambda_powertools.utilities.idempotency.serialization.base import BaseIdempotencySerializer @@ -11,8 +11,8 @@ def __init__(self): Default serializer, does not transform data """ - def to_dict(self, data: Dict) -> Dict: + def to_dict(self, data: dict) -> dict: return data - def from_dict(self, data: Dict) -> Dict: + def from_dict(self, data: dict) -> dict: return data diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py b/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py index bd82c42644e..42ae179833f 100644 --- a/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py +++ b/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Type +from __future__ import annotations + +from typing import Any from pydantic import BaseModel @@ -15,19 +17,19 @@ class PydanticSerializer(BaseIdempotencyModelSerializer): """Pydantic serializer for idempotency models""" - def __init__(self, model: Type[BaseModel]): + def __init__(self, model: type[BaseModel]): """ Parameters ---------- model: Model Pydantic model to be used for serialization """ - self.__model: Type[BaseModel] = model + self.__model: type[BaseModel] = model - def to_dict(self, data: BaseModel) -> Dict: + def to_dict(self, data: BaseModel) -> dict: return data.model_dump() - def from_dict(self, data: Dict) -> BaseModel: + def from_dict(self, data: dict) -> BaseModel: return self.__model.model_validate(data) @classmethod