diff --git a/aws_lambda_powertools/utilities/connections/__init__.py b/aws_lambda_powertools/utilities/connections/__init__.py new file mode 100644 index 00000000000..660c559447f --- /dev/null +++ b/aws_lambda_powertools/utilities/connections/__init__.py @@ -0,0 +1,6 @@ +from aws_lambda_powertools.utilities.connections.redis import ( + RedisCluster, + RedisStandalone, +) + +__all__ = ["RedisStandalone", "RedisCluster"] diff --git a/aws_lambda_powertools/utilities/connections/base_sync.py b/aws_lambda_powertools/utilities/connections/base_sync.py new file mode 100644 index 00000000000..f67149c3277 --- /dev/null +++ b/aws_lambda_powertools/utilities/connections/base_sync.py @@ -0,0 +1,7 @@ +from abc import ABC, abstractmethod + + +class BaseConnectionSync(ABC): + @abstractmethod + def init_connection(self): + raise NotImplementedError() # pragma: no cover diff --git a/aws_lambda_powertools/utilities/connections/exceptions.py b/aws_lambda_powertools/utilities/connections/exceptions.py new file mode 100644 index 00000000000..b4426c2b142 --- /dev/null +++ b/aws_lambda_powertools/utilities/connections/exceptions.py @@ -0,0 +1,4 @@ +class RedisConnectionError(Exception): + """ + Redis connection error + """ diff --git a/aws_lambda_powertools/utilities/connections/redis.py b/aws_lambda_powertools/utilities/connections/redis.py new file mode 100644 index 00000000000..d394cba4777 --- /dev/null +++ b/aws_lambda_powertools/utilities/connections/redis.py @@ -0,0 +1,133 @@ +import logging +from typing import Optional, Type, Union + +import redis + +from .base_sync import BaseConnectionSync +from .exceptions import RedisConnectionError + +logger = logging.getLogger(__name__) + + +class RedisConnection(BaseConnectionSync): + def __init__( + self, + client: Type[Union[redis.Redis, redis.RedisCluster]], + host: Optional[str] = None, + port: Optional[int] = None, + username: Optional[str] = None, + password: Optional[str] = None, + db_index: Optional[int] = None, + url: Optional[str] = None, + **extra_options, + ) -> None: + self.extra_options: dict = {} + + self.url = url + self.host = host + self.port = port + self.username = username + self.password = password + self.db_index = db_index + self.extra_options.update(**extra_options) + self._connection = None + self._client = client + + def init_connection(self): + """ + Connection is cached, so returning this + """ + if self._connection: + return self._connection + + logger.info(f"Trying to connect to Redis: {self.host}") + + try: + if self.url: + logger.debug(f"Using URL format to connect to Redis: {self.host}") + self._connection = self._client.from_url(url=self.url) + else: + logger.debug(f"Using other parameters to connect to Redis: {self.host}") + self._connection = self._client( + host=self.host, + port=self.port, + username=self.username, + password=self.password, + db=self.db_index, + decode_responses=True, + **self.extra_options, + ) + except redis.exceptions.ConnectionError as exc: + logger.debug(f"Cannot connect in Redis: {self.host}") + raise RedisConnectionError("Could not to connect to Redis", exc) from exc + + return self._connection + + +class RedisStandalone(RedisConnection): + def __init__( + self, + host: Optional[str] = None, + port: Optional[int] = None, + username: Optional[str] = None, + password: Optional[str] = None, + db_index: Optional[int] = None, + url: Optional[str] = None, + **extra_options, + ) -> None: + """ + Initialize the Redis standalone client + Parameters + ---------- + host: str + Name of the host to connect to Redis instance/cluster + port: int + Number of the port to connect to Redis instance/cluster + username: str + Name of the username to connect to Redis instance/cluster in case of using ACL + See: https://redis.io/docs/management/security/acl/ + password: str + Passwod to connect to Redis instance/cluster + db_index: int + Index of Redis database + See: https://redis.io/commands/select/ + url: str + Redis client object configured from the given URL + See: https://redis.readthedocs.io/en/latest/connections.html#redis.Redis.from_url + """ + super().__init__(redis.Redis, host, port, username, password, db_index, url, **extra_options) + + +class RedisCluster(RedisConnection): + def __init__( + self, + host: Optional[str] = None, + port: Optional[int] = None, + username: Optional[str] = None, + password: Optional[str] = None, + db_index: Optional[int] = None, + url: Optional[str] = None, + **extra_options, + ) -> None: + """ + Initialize the Redis standalone client + Parameters + ---------- + host: str + Name of the host to connect to Redis instance/cluster + port: int + Number of the port to connect to Redis instance/cluster + username: str + Name of the username to connect to Redis instance/cluster in case of using ACL + See: https://redis.io/docs/management/security/acl/ + password: str + Passwod to connect to Redis instance/cluster + db_index: int + Index of Redis database + See: https://redis.io/commands/select/ + url: str + Redis client object configured from the given URL + See: https://redis.readthedocs.io/en/latest/connections.html#redis.Redis.from_url + """ + + super().__init__(redis.cluster.RedisCluster, host, port, username, password, db_index, url, **extra_options) diff --git a/aws_lambda_powertools/utilities/idempotency/__init__.py b/aws_lambda_powertools/utilities/idempotency/__init__.py index 148b291ea6d..30447acb28c 100644 --- a/aws_lambda_powertools/utilities/idempotency/__init__.py +++ b/aws_lambda_powertools/utilities/idempotency/__init__.py @@ -8,7 +8,17 @@ from aws_lambda_powertools.utilities.idempotency.persistence.dynamodb import ( DynamoDBPersistenceLayer, ) +from aws_lambda_powertools.utilities.idempotency.persistence.redis import ( + RedisCachePersistenceLayer, +) from .idempotency import IdempotencyConfig, idempotent, idempotent_function -__all__ = ("DynamoDBPersistenceLayer", "BasePersistenceLayer", "idempotent", "idempotent_function", "IdempotencyConfig") +__all__ = ( + "DynamoDBPersistenceLayer", + "BasePersistenceLayer", + "idempotent", + "idempotent_function", + "IdempotencyConfig", + "RedisCachePersistenceLayer", +) diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/base.py b/aws_lambda_powertools/utilities/idempotency/persistence/base.py index 28b284b8e5e..7a3271926c0 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/base.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/base.py @@ -37,7 +37,7 @@ class DataRecord: def __init__( self, - idempotency_key: str, + idempotency_key: str = "", status: str = "", expiry_timestamp: Optional[int] = None, in_progress_expiry_timestamp: Optional[int] = None, @@ -116,6 +116,7 @@ class BasePersistenceLayer(ABC): def __init__(self): """Initialize the defaults""" self.function_name = "" + self.backend = "" self.configured = False self.event_key_jmespath: Optional[str] = None self.event_key_compiled_jmespath = None @@ -262,9 +263,12 @@ def _get_expiry_timestamp(self) -> int: unix timestamp of expiry date for idempotency record """ - now = datetime.datetime.now() - period = datetime.timedelta(seconds=self.expires_after_seconds) - return int((now + period).timestamp()) + if self.backend == "redis": + return self.expires_after_seconds + else: + now = datetime.datetime.now() + period = datetime.timedelta(seconds=self.expires_after_seconds) + return int((now + period).timestamp()) def _save_to_cache(self, data_record: DataRecord): """ diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py b/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py index b05d8216b50..26f4e4c841d 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py @@ -26,7 +26,7 @@ class DynamoDBPersistenceLayer(BasePersistenceLayer): def __init__( self, table_name: str, - key_attr: str = "id", + key_attr: Optional[str] = "id", static_pk_value: Optional[str] = None, sort_key_attr: Optional[str] = None, expiry_attr: str = "expiration", @@ -59,6 +59,8 @@ def __init__( DynamoDB attribute name for status, by default "status" data_attr: str, optional DynamoDB attribute name for response data, by default "data" + validation_key_attr: str, optional + DynamoDB attribute name for hashed representation of the parts of the event used for validation boto_config: botocore.config.Config, optional Botocore configuration to pass during client initialization boto3_session : boto3.session.Session, optional diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/redis.py b/aws_lambda_powertools/utilities/idempotency/persistence/redis.py new file mode 100644 index 00000000000..56913a70d20 --- /dev/null +++ b/aws_lambda_powertools/utilities/idempotency/persistence/redis.py @@ -0,0 +1,141 @@ +import datetime +import logging +from typing import Any, Dict, Union + +import redis + +from aws_lambda_powertools.utilities.idempotency import BasePersistenceLayer +from aws_lambda_powertools.utilities.idempotency.exceptions import ( + IdempotencyItemAlreadyExistsError, + IdempotencyItemNotFoundError, +) +from aws_lambda_powertools.utilities.idempotency.persistence.base import ( + STATUS_CONSTANTS, + DataRecord, +) + +logger = logging.getLogger(__name__) + + +class RedisCachePersistenceLayer(BasePersistenceLayer): + def __init__( + self, + connection, + in_progress_expiry_attr: str = "in_progress_expiration", + status_attr: str = "status", + data_attr: str = "data", + validation_key_attr: str = "validation", + ): + """ + Initialize the Redis Persistence Layer + Parameters + ---------- + in_progress_expiry_attr: str, optional + Redis hash attribute name for in-progress expiry timestamp, by default "in_progress_expiration" + status_attr: str, optional + Redis hash attribute name for status, by default "status" + data_attr: str, optional + Redis hash attribute name for response data, by default "data" + validation_key_attr: str, optional + Redis hash attribute name for hashed representation of the parts of the event used for validation + """ + + # Initialize connection with Redis + self._connection: Union[redis.Redis, redis.RedisCluster] = connection.init_connection() + + self.in_progress_expiry_attr = in_progress_expiry_attr + self.status_attr = status_attr + self.data_attr = data_attr + self.validation_key_attr = validation_key_attr + super(RedisCachePersistenceLayer, self).__init__() + + def _item_to_data_record(self, item: Dict[str, Any]) -> DataRecord: + return DataRecord( + status=item[self.status_attr], + in_progress_expiry_timestamp=item.get(self.in_progress_expiry_attr), + response_data=item.get(self.data_attr), + payload_hash=item.get(self.validation_key_attr), + ) + + def _get_record(self, idempotency_key) -> DataRecord: + # See: https://redis.io/commands/hgetall/ + response = self._connection.hgetall(idempotency_key) + + try: + item = response + except KeyError: + raise IdempotencyItemNotFoundError + return self._item_to_data_record(item) + + def _put_record(self, data_record: DataRecord) -> None: + item: Dict[str, Any] = {} + + # Redis works with hset to support hashing keys with multiple attributes + # See: https://redis.io/commands/hset/ + item = { + "name": data_record.idempotency_key, + "mapping": { + self.status_attr: data_record.status, + }, + } + + if data_record.in_progress_expiry_timestamp is not None: + item["mapping"][self.in_progress_expiry_attr] = data_record.in_progress_expiry_timestamp + + if self.payload_validation_enabled: + item["mapping"][self.validation_key_attr] = data_record.payload_hash + + now = datetime.datetime.now() + try: + # | LOCKED | RETRY if status = "INPROGRESS" | RETRY + # |----------------|-------------------------------------------------------|-------------> .... (time) + # | Lambda Idempotency Record + # | Timeout Timeout + # | (in_progress_expiry) (expiry) + + # Conditions to successfully save a record: + + # The idempotency key does not exist: + # - first time that this invocation key is used + # - previous invocation with the same key was deleted due to TTL + idempotency_record = self._connection.hgetall(data_record.idempotency_key) + if len(idempotency_record) > 0: + # record already exists. + + # status is completed, so raise exception because it exists and still valid + if idempotency_record[self.status_attr] == STATUS_CONSTANTS["COMPLETED"]: + raise + + # checking if in_progress_expiry_attr exists + # if in_progress_expiry_attr exist, must be lower than now + if self.in_progress_expiry_attr in idempotency_record and int( + idempotency_record[self.in_progress_expiry_attr] + ) > int(now.timestamp() * 1000): + raise + + logger.debug(f"Putting record on Redis for idempotency key: {data_record.idempotency_key}") + self._connection.hset(**item) + # hset type must set expiration after adding the record + # Need to review this to get ttl in seconds + self._connection.expire(name=data_record.idempotency_key, time=self.expires_after_seconds) + except Exception: + logger.debug(f"Failed to put record for already existing idempotency key: {data_record.idempotency_key}") + raise IdempotencyItemAlreadyExistsError + + def _update_record(self, data_record: DataRecord) -> None: + item: Dict[str, Any] = {} + + item = { + "name": data_record.idempotency_key, + "mapping": { + self.data_attr: data_record.response_data, + self.status_attr: data_record.status, + }, + } + logger.debug(f"Updating record for idempotency key: {data_record.idempotency_key}") + self._connection.hset(**item) + + def _delete_record(self, data_record: DataRecord) -> None: + logger.debug(f"Deleting record for idempotency key: {data_record.idempotency_key}") + # See: https://redis.io/commands/del/ + self._connection.delete(data_record.idempotency_key) diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md index 49a028168b3..8b0c1800c94 100644 --- a/docs/utilities/idempotency.md +++ b/docs/utilities/idempotency.md @@ -28,6 +28,7 @@ times with the same parameters**. This makes idempotent operations safe to retry ### IAM Permissions +#### DynamoDB Your Lambda function IAM Role must have `dynamodb:GetItem`, `dynamodb:PutItem`, `dynamodb:UpdateItem` and `dynamodb:DeleteItem` IAM permissions before using this feature. ???+ note @@ -35,10 +36,16 @@ Your Lambda function IAM Role must have `dynamodb:GetItem`, `dynamodb:PutItem`, ### Required resources +_**DynamoDB**_ + 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. As of now, Amazon DynamoDB is the only supported persistent storage layer, so you'll need to create a table first. +_**Redis**_ + +Before getting started you need to setup your [EC2 Instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html) and [ElastiCache for Redis cluster](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/GettingStarted.html). + **Default table configuration** If you're not [changing the default configuration for the DynamoDB persistence layer](#dynamodbpersistencelayer), this is the expected default configuration: @@ -90,6 +97,8 @@ Resources: ### Idempotent decorator +_**DynamoDB**_ + You can quickly start by initializing the `DynamoDBPersistenceLayer` class and using it with the `idempotent` decorator on your lambda handler. === "app.py" @@ -123,6 +132,31 @@ You can quickly start by initializing the `DynamoDBPersistenceLayer` class and u "product_id": "123456789" } ``` + +_**Redis**_ + +You can initialize `RedisCachePersistenceLayer` class and use it with `idempotent` decorator on your lambda handler. + +=== "app.py" + +``` + from aws_lambda_powertools.utilities.connections import RedisStandalone, RedisCluster + from aws_lambda_powertools.utilities.idempotency import ( + idempotent, + RedisCachePersistenceLayer, + IdempotencyConfig + ) + # For connection using Redis Standalone architecture + redis_connection = RedisStandalone(host="192.168.68.112", port=6379, password="pass", db_index=0) + + persistence_layer = RedisCachePersistenceLayer(connection=redis_connection) + config = IdempotencyConfig( + expires_after_seconds=1*60, # 1 minutes + ) + @idempotent(config=config, persistence_store=persistence_layer) + def lambda_handler(event, context): + return {"message":"Hello"} +``` ### Idempotent_function decorator @@ -565,6 +599,57 @@ When using DynamoDB as a persistence layer, you can alter the attribute names by | **sort_key_attr** | | | Sort key of the table (if table is configured with a sort key). | | **static_pk_value** | | `idempotency#{LAMBDA_FUNCTION_NAME}` | Static value to use as the partition key. Only used when **sort_key_attr** is set. | +#### RedisCachePersistenceLayer + +This persistence layer is built-in and you can use ElastiCache to store and see the keys. + +``` + from aws_lambda_powertools.utilities.idempotency import RedisCachePersistenceLayer + persistence_layer = RedisCachePersistenceLayer( + static_pk_value: Optional[str] = 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", + ) +``` + +When using ElastiCache for Redis as a persistence layer, you can alter the attribute names by passing these parameters when initializing the persistence layer: + +| Parameter | Required | Default | Description | +| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- | +| **static_pk_value** | | `idempotency#{LAMBDA_FUNCTION_NAME}` | Static value to use as the partition key. Only used when **sort_key_attr** is set. | +| **expiry_attr** | | `expiration` | Unix timestamp of when record expires | +| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) | +| **status_attr** | | `status` | Stores status of the lambda execution during and after invocation | +| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers | +| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation | + +#### RedisStandalone/RedisCluster: + +``` +from aws_lambda_powertools.utilities.connections import RedisStandalone,RedisCluster + +redis_connection = RedisStandalone( + host="192.168.68.112", + port=6379, + username = "abc" + password="pass", + db_index=0, + url = None +) +``` + +| Parameter | Required | Default | Description | +| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- | +| **host** | | `localhost` | Name of the host to connect to Redis instance/cluster | +| **port** | | 6379 | Number of the port to connect to Redis instance/cluster | +| **username** | | `None` | Name of the username to connect to Redis instance/cluster in case of using ACL | +| **password** | | `None` | Passwod to connect to Redis instance/cluster | +| **db_index** | | 0. | Index of Redis database | +| **url** | | `None` | Redis client object configured from the given URL. | + ## Advanced ### Customizing the default behavior @@ -626,6 +711,8 @@ In most cases, it is not desirable to store the idempotency records forever. Rat You can change this window with the **`expires_after_seconds`** parameter: +_**DynamoDB**_ + ```python hl_lines="8 11" title="Adjusting cache TTL" from aws_lambda_powertools.utilities.idempotency import ( IdempotencyConfig, DynamoDBPersistenceLayer, idempotent @@ -642,6 +729,24 @@ def handler(event, context): ... ``` +_**Redis**_ + +``` +from aws_lambda_powertools.utilities.connections import RedisStandalone, RedisCluster +from aws_lambda_powertools.utilities.idempotency import ( + idempotent, + RedisCachePersistenceLayer, + IdempotencyConfig +) +# For connection using Redis Standalone architecture +redis_connection = RedisStandalone(host="192.168.68.112", port=6379, password="pass", db_index=0) + +persistence_layer = RedisCachePersistenceLayer(connection=redis_connection) +config = IdempotencyConfig( + expires_after_seconds=5*60, # 5 minutes +) +``` + This will mark any records older than 5 minutes as expired, and the lambda handler will be executed as normal if it is invoked with a matching payload. ???+ note "Note: DynamoDB time-to-live field" @@ -856,6 +961,8 @@ This utility provides an abstract base class (ABC), so that you can implement yo You can inherit from the `BasePersistenceLayer` class and implement the abstract methods `_get_record`, `_put_record`, `_update_record` and `_delete_record`. +_**DynamoDB**_ + ```python hl_lines="8-13 57 65 74 96 124" title="Excerpt DynamoDB Persistence Layer implementation for reference" import datetime import logging @@ -985,6 +1092,155 @@ class DynamoDBPersistenceLayer(BasePersistenceLayer): self.table.delete_item(Key={self.key_attr: data_record.idempotency_key},) ``` +_**Redis**_ + +``` +import logging +import os +from typing import Any, Dict, Optional + +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.utilities.idempotency import BasePersistenceLayer +from aws_lambda_powertools.utilities.idempotency.exceptions import ( + IdempotencyItemAlreadyExistsError, + IdempotencyItemNotFoundError, +) +from aws_lambda_powertools.utilities.idempotency.persistence.base import DataRecord + +logger = logging.getLogger(__name__) + + +class RedisCachePersistenceLayer(BasePersistenceLayer): + def __init__( + self, + connection, + static_pk_value: Optional[str] = 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", + ): + """ + Initialize the Redis Persistence Layer + Parameters + ---------- + static_pk_value: str, optional + Redis attribute value for cache key, by default "idempotency#". + expiry_attr: str, optional + Redis hash attribute name for expiry timestamp, by default "expiration" + in_progress_expiry_attr: str, optional + Redis hash attribute name for in-progress expiry timestamp, by default "in_progress_expiration" + status_attr: str, optional + Redis hash attribute name for status, by default "status" + data_attr: str, optional + Redis hash attribute name for response data, by default "data" + """ + + # Initialize connection with Redis + self._connection = connection.init_connection() + + if static_pk_value is None: + static_pk_value = f"idempotency#{os.getenv(constants.LAMBDA_FUNCTION_NAME_ENV, '')}" + + self.static_pk_value = static_pk_value + self.in_progress_expiry_attr = in_progress_expiry_attr + self.expiry_attr = expiry_attr + self.status_attr = status_attr + self.data_attr = data_attr + self.validation_key_attr = validation_key_attr + super(RedisCachePersistenceLayer, self).__init__() + + def _get_key(self, idempotency_key: str) -> dict: + # Need to review this after adding GETKEY logic + if self.sort_key_attr: + return {self.key_attr: self.static_pk_value, self.sort_key_attr: idempotency_key} + return {self.key_attr: idempotency_key} + + def _item_to_data_record(self, item: Dict[str, Any]) -> DataRecord: + # Need to review this after adding GETKEY logic + return DataRecord( + status=item[self.status_attr], + expiry_timestamp=item[self.expiry_attr], + in_progress_expiry_timestamp=item.get(self.in_progress_expiry_attr), + response_data=item.get(self.data_attr), + payload_hash=item.get(self.validation_key_attr), + ) + + def _get_record(self, idempotency_key) -> DataRecord: + # See: https://redis.io/commands/hgetall/ + response = self._connection.hgetall(idempotency_key) + + try: + item = response + except KeyError: + raise IdempotencyItemNotFoundError + return self._item_to_data_record(item) + + def _put_record(self, data_record: DataRecord) -> None: + # Redis works with hset to support hashing keys with multiple attributes + # See: https://redis.io/commands/hset/ + item = { + "name": data_record.idempotency_key, + "mapping": { + self.in_progress_expiry_attr: data_record.in_progress_expiry_timestamp, + self.status_attr: data_record.status, + self.expiry_attr: data_record.expiry_timestamp, + }, + } + + if data_record.in_progress_expiry_timestamp is not None: + item["mapping"][self.in_progress_expiry_attr] = data_record.in_progress_expiry_timestamp + + if self.payload_validation_enabled: + item["mapping"][self.validation_key_attr] = data_record.payload_hash + + try: + # | LOCKED | RETRY if status = "INPROGRESS" | RETRY + # |----------------|-------------------------------------------------------|-------------> .... (time) + # | Lambda Idempotency Record + # | Timeout Timeout + # | (in_progress_expiry) (expiry) + + # Conditions to successfully save a record: + + # The idempotency key does not exist: + # - first time that this invocation key is used + # - previous invocation with the same key was deleted due to TTL + idempotency_key_not_exist = self._connection.exists(data_record.idempotency_key) + + # key exists + if idempotency_key_not_exist == 1: + raise + + # missing logic to compare expiration + + logger.debug(f"Putting record on Redis for idempotency key: {data_record.idempotency_key}") + self._connection.hset(**item) + # hset type must set expiration after adding the record + # Need to review this to get ttl in seconds + self._connection.expire(name=data_record.idempotency_key, time=self.expires_after_seconds) + except Exception: + logger.debug(f"Failed to put record for already existing idempotency key: {data_record.idempotency_key}") + raise IdempotencyItemAlreadyExistsError + + def _update_record(self, data_record: DataRecord) -> None: + item = { + "name": data_record.idempotency_key, + "mapping": { + self.data_attr: data_record.response_data, + self.status_attr: data_record.status, + }, + } + logger.debug(f"Updating record for idempotency key: {data_record.idempotency_key}") + self._connection.hset(**item) + + def _delete_record(self, data_record: DataRecord) -> None: + logger.debug(f"Deleting record for idempotency key: {data_record.idempotency_key}") + # See: https://redis.io/commands/del/ + self._connection.delete(data_record.idempotency_key) +``` + ???+ danger Pay attention to the documentation for each - you may need to perform additional checks inside these methods to ensure the idempotency guarantees remain intact. diff --git a/poetry.lock b/poetry.lock index b8005dbc350..871a2472a9b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -22,6 +22,21 @@ doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] trio = ["trio (>=0.16,<0.22)"] +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + [[package]] name = "attrs" version = "22.2.0" @@ -43,18 +58,18 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy [[package]] name = "aws-cdk-asset-awscli-v1" -version = "2.2.67" +version = "2.2.69" description = "A library that contains the AWS CLI for use in Lambda Layers" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk.asset-awscli-v1-2.2.67.tar.gz", hash = "sha256:92d3c003e692a4feafbcf215fd4b8c6321a0dc86f82008d906892ca6eac08851"}, - {file = "aws_cdk.asset_awscli_v1-2.2.67-py3-none-any.whl", hash = "sha256:250c66fe8611c4cdef115ce32bb02d19d374597c53b299c68bb2a1070dd66727"}, + {file = "aws-cdk.asset-awscli-v1-2.2.69.tar.gz", hash = "sha256:a076a29075ce863d2a3cae2036f31d2317e5dc52f97780d828bf67d37a3fb523"}, + {file = "aws_cdk.asset_awscli_v1-2.2.69-py3-none-any.whl", hash = "sha256:e53d352bc50c566bbbbcbb85b17f98432af853eff16559570c6d5d6be58bb44e"}, ] [package.dependencies] -jsii = ">=1.74.0,<2.0.0" +jsii = ">=1.75.0,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" @@ -77,35 +92,35 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-asset-node-proxy-agent-v5" -version = "2.0.56" +version = "2.0.58" description = "@aws-cdk/asset-node-proxy-agent-v5" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk.asset-node-proxy-agent-v5-2.0.56.tar.gz", hash = "sha256:4014d618fe22e8cfe026d5307728af434c695c039716ad31544a2b5301a98de1"}, - {file = "aws_cdk.asset_node_proxy_agent_v5-2.0.56-py3-none-any.whl", hash = "sha256:47bd82e63988a4d1982aa2d04367df10b1e564971847adf3e9d42c0fdce0adeb"}, + {file = "aws-cdk.asset-node-proxy-agent-v5-2.0.58.tar.gz", hash = "sha256:2b045e6d1ec8f3290ab32b839ed26fddd691dfa2cb8868fc2d641a3c9b768cad"}, + {file = "aws_cdk.asset_node_proxy_agent_v5-2.0.58-py3-none-any.whl", hash = "sha256:e9b913a613342f277505eb32ac268d699ebf66624940509b2e33453392f6f90a"}, ] [package.dependencies] -jsii = ">=1.74.0,<2.0.0" +jsii = ">=1.75.0,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-aws-apigatewayv2-alpha" -version = "2.64.0a0" +version = "2.65.0a0" description = "The CDK Construct Library for AWS::APIGatewayv2" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk.aws-apigatewayv2-alpha-2.64.0a0.tar.gz", hash = "sha256:7e33fb04b10c1668abe334e25a998967b51aeed76243fc591b66705c8d6241d4"}, - {file = "aws_cdk.aws_apigatewayv2_alpha-2.64.0a0-py3-none-any.whl", hash = "sha256:88f72a435fc91f7c02a8f1fb564958ac1c8125c5319021d61b67d00466185199"}, + {file = "aws-cdk.aws-apigatewayv2-alpha-2.65.0a0.tar.gz", hash = "sha256:f1a30470f6fe550046671497bbe325bc2d94828d9dc8fb4270b35c3f5c03c91b"}, + {file = "aws_cdk.aws_apigatewayv2_alpha-2.65.0a0-py3-none-any.whl", hash = "sha256:d3ad9ee6947095eb549ddf51e3ac5e3b27992d8ec4b0e52e473e2ef3aeedcc44"}, ] [package.dependencies] -aws-cdk-lib = ">=2.64.0,<3.0.0" +aws-cdk-lib = ">=2.65.0,<3.0.0" constructs = ">=10.0.0,<11.0.0" jsii = ">=1.74.0,<2.0.0" publication = ">=0.0.3" @@ -113,19 +128,19 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-aws-apigatewayv2-authorizers-alpha" -version = "2.64.0a0" +version = "2.65.0a0" description = "Authorizers for AWS APIGateway V2" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk.aws-apigatewayv2-authorizers-alpha-2.64.0a0.tar.gz", hash = "sha256:670ee77f19818723aeeea47fbac1441d58f39b5eff79332e15196452ec6183bf"}, - {file = "aws_cdk.aws_apigatewayv2_authorizers_alpha-2.64.0a0-py3-none-any.whl", hash = "sha256:e2377441ad33aa43453f5c501e00a9a0c261627e78b2080617edd6e09949c139"}, + {file = "aws-cdk.aws-apigatewayv2-authorizers-alpha-2.65.0a0.tar.gz", hash = "sha256:821fa0cc9d27fc8580f436e66736b5d4286ffb63ce1459ff28717b795f1bede6"}, + {file = "aws_cdk.aws_apigatewayv2_authorizers_alpha-2.65.0a0-py3-none-any.whl", hash = "sha256:45d77bce2063f33690597d2f96d63fe2188772bb724716318fa5a6dae41eb45b"}, ] [package.dependencies] -"aws-cdk.aws-apigatewayv2-alpha" = "2.64.0.a0" -aws-cdk-lib = ">=2.64.0,<3.0.0" +"aws-cdk.aws-apigatewayv2-alpha" = "2.65.0.a0" +aws-cdk-lib = ">=2.65.0,<3.0.0" constructs = ">=10.0.0,<11.0.0" jsii = ">=1.74.0,<2.0.0" publication = ">=0.0.3" @@ -133,19 +148,19 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-aws-apigatewayv2-integrations-alpha" -version = "2.64.0a0" +version = "2.65.0a0" description = "Integrations for AWS APIGateway V2" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk.aws-apigatewayv2-integrations-alpha-2.64.0a0.tar.gz", hash = "sha256:1826fa641a0e849cff90e681033066fa3fea44bca447c6696681dddf862df364"}, - {file = "aws_cdk.aws_apigatewayv2_integrations_alpha-2.64.0a0-py3-none-any.whl", hash = "sha256:a34f87cafbbdf76078ce564642f7f11771f4693a04bb7f41eca7d76b26ffe562"}, + {file = "aws-cdk.aws-apigatewayv2-integrations-alpha-2.65.0a0.tar.gz", hash = "sha256:a70ce0e34ad653d84eee42068eed1eab804f77e6cd54731b3579f94255a64c52"}, + {file = "aws_cdk.aws_apigatewayv2_integrations_alpha-2.65.0a0-py3-none-any.whl", hash = "sha256:a99b38f4dc5b535c560bbbee0a18c7b44d72b6a35acc1d36a20dd6c49a6b25e0"}, ] [package.dependencies] -"aws-cdk.aws-apigatewayv2-alpha" = "2.64.0.a0" -aws-cdk-lib = ">=2.64.0,<3.0.0" +"aws-cdk.aws-apigatewayv2-alpha" = "2.65.0.a0" +aws-cdk-lib = ">=2.65.0,<3.0.0" constructs = ">=10.0.0,<11.0.0" jsii = ">=1.74.0,<2.0.0" publication = ">=0.0.3" @@ -189,24 +204,23 @@ requests = ">=0.14.0" [[package]] name = "aws-sam-translator" -version = "1.59.0" +version = "1.55.0" description = "AWS SAM Translator is a library that transform SAM templates into AWS CloudFormation templates" category = "dev" optional = false python-versions = ">=3.7, <=4.0, !=4.0" files = [ - {file = "aws-sam-translator-1.59.0.tar.gz", hash = "sha256:9b8f23a5754cba92677d334ece5c5d9dc9b1f1a327a650fc8939ae3fc6da4141"}, - {file = "aws_sam_translator-1.59.0-py3-none-any.whl", hash = "sha256:6761293a21bd1cb0e19f168926ebfc4a3a6c9011aca67bd448ef485a55d6f658"}, + {file = "aws-sam-translator-1.55.0.tar.gz", hash = "sha256:08e182e76d6fabc13ce2f38b8a3932b3131407c6ad29ec2849ef3d9a41576b94"}, + {file = "aws_sam_translator-1.55.0-py2-none-any.whl", hash = "sha256:e86a67b87329a0de7d531d33257d1a448d0d6ecd84aee058d084957f28a8e4b1"}, + {file = "aws_sam_translator-1.55.0-py3-none-any.whl", hash = "sha256:93dc74614ab291c86be681e025679d08f4fa685ed6b55d410f62f2f235012205"}, ] [package.dependencies] boto3 = ">=1.19.5,<2.0.0" -jsonschema = ">=3.2,<5" -pydantic = ">=1.8,<2.0" -typing-extensions = ">=4.4.0,<4.5.0" +jsonschema = ">=3.2,<4.0" [package.extras] -dev = ["black (==20.8b1)", "boto3 (>=1.23,<2)", "boto3-stubs[appconfig,serverlessrepo] (>=1.19.5,<2.0.0)", "click (>=7.1,<8.0)", "coverage (>=5.3,<6.0)", "dateparser (>=0.7,<1.0)", "docopt (>=0.6.2,<0.7.0)", "flake8 (>=3.8.4,<3.9.0)", "mypy (==0.971)", "parameterized (>=0.7.4,<0.8.0)", "pylint (>=2.15.0,<2.16.0)", "pytest (>=6.2.5,<6.3.0)", "pytest-cov (>=2.10.1,<2.11.0)", "pytest-env (>=0.6.2,<0.7.0)", "pytest-rerunfailures (>=9.1.1,<9.2.0)", "pytest-xdist (>=2.5,<3.0)", "pyyaml (>=5.4,<6.0)", "requests (>=2.25.0,<2.26.0)", "ruamel.yaml (==0.17.21)", "tenacity (>=7.0.0,<7.1.0)", "tox (>=3.24,<4.0)", "types-PyYAML (>=5.4,<6.0)", "types-jsonschema (>=3.2,<4.0)"] +dev = ["black (==20.8b1)", "boto3 (>=1.23,<2)", "boto3-stubs[appconfig,serverlessrepo] (>=1.19.5,<2.0.0)", "click (>=7.1,<8.0)", "coverage (>=5.3,<6.0)", "dateparser (>=0.7,<1.0)", "docopt (>=0.6.2,<0.7.0)", "flake8 (>=3.8.4,<3.9.0)", "mypy (==0.971)", "parameterized (>=0.7.4,<0.8.0)", "pylint (>=2.15.0,<2.16.0)", "pytest (>=6.2.5,<6.3.0)", "pytest-cov (>=2.10.1,<2.11.0)", "pytest-env (>=0.6.2,<0.7.0)", "pytest-rerunfailures (>=9.1.1,<9.2.0)", "pytest-xdist (>=2.5,<3.0)", "pyyaml (>=5.4,<6.0)", "requests (>=2.24.0,<2.25.0)", "ruamel.yaml (==0.17.21)", "tenacity (>=7.0.0,<7.1.0)", "tox (>=3.24,<4.0)", "types-PyYAML (>=5.4,<6.0)", "types-jsonschema (>=3.2,<4.0)"] [[package]] name = "aws-xray-sdk" @@ -300,18 +314,18 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.26.68" +version = "1.26.73" description = "The AWS SDK for Python" category = "main" optional = false python-versions = ">= 3.7" files = [ - {file = "boto3-1.26.68-py3-none-any.whl", hash = "sha256:bbb426a9b3afd3ccbac25e03b215d79e90b4c47905b1b08b3b9d86fc74096974"}, - {file = "boto3-1.26.68.tar.gz", hash = "sha256:c92dd0fde7839c0ca9c16a989d67ceb7f80f53de19f2b087fd1182f2af41b2ae"}, + {file = "boto3-1.26.73-py3-none-any.whl", hash = "sha256:98efcc8472c58060856bf42e9afe10eefb60c0e52680db0a43daf96e7a1216de"}, + {file = "boto3-1.26.73.tar.gz", hash = "sha256:bd92def38355ea055c6c29bd599832878eecc19cad21dab34ade38280e1b403b"}, ] [package.dependencies] -botocore = ">=1.29.68,<1.30.0" +botocore = ">=1.29.73,<1.30.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.6.0,<0.7.0" @@ -320,14 +334,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.29.68" +version = "1.29.73" description = "Low-level, data-driven core of boto 3." category = "main" optional = false python-versions = ">= 3.7" files = [ - {file = "botocore-1.29.68-py3-none-any.whl", hash = "sha256:08fa8302a22553e69b70b1de2cc8cec61a3a878546658d091473e13d5b9d2ca4"}, - {file = "botocore-1.29.68.tar.gz", hash = "sha256:8f5cb96dc0862809d29fe512087c77c15fe6328a2d8238f0a96cccb6eb77ec12"}, + {file = "botocore-1.29.73-py3-none-any.whl", hash = "sha256:df467573a96d2e1ba1196b5a30b0a5e4a83fd960d27b5c2f8f128c9148dc120e"}, + {file = "botocore-1.29.73.tar.gz", hash = "sha256:a9f0e006b3342424d59d5e23dc1ca0c6972c909a727dcd0811c9b20966d4adf8"}, ] [package.dependencies] @@ -367,6 +381,83 @@ files = [ {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "cfn-lint" version = "0.67.0" @@ -529,18 +620,18 @@ files = [ [[package]] name = "constructs" -version = "10.1.246" +version = "10.1.252" description = "A programming model for software-defined state" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "constructs-10.1.246-py3-none-any.whl", hash = "sha256:f07c7c4aa2d22ff960a9f51f7011030b4a3d8cc6df0e0a84e30ea63c2c8c8456"}, - {file = "constructs-10.1.246.tar.gz", hash = "sha256:26d0b017eef92bde3ece7454b524dddc051425819c59932ebe3c1ff6f9e1cb4a"}, + {file = "constructs-10.1.252-py3-none-any.whl", hash = "sha256:cf115fe7729c93ce01b1839e8760be24606c694b96b628230711cbf9b8fa1acd"}, + {file = "constructs-10.1.252.tar.gz", hash = "sha256:1ee8f4a11c4515730dff1de9c40342dd37bd7bd5f5650a500c06a22954e373a2"}, ] [package.dependencies] -jsii = ">=1.74.0,<2.0.0" +jsii = ">=1.75.0,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" @@ -611,6 +702,52 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "cryptography" +version = "39.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"}, + {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"}, + {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"}, + {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"}, + {file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"}, + {file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"}, + {file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"] +test-randomorder = ["pytest-randomly"] +tox = ["tox"] + [[package]] name = "decorator" version = "5.1.1" @@ -908,14 +1045,14 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.30" -description = "GitPython is a python library used to interact with Git repositories" +version = "3.1.31" +description = "GitPython is a Python library used to interact with Git repositories" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, - {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, + {file = "GitPython-3.1.31-py3-none-any.whl", hash = "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"}, + {file = "GitPython-3.1.31.tar.gz", hash = "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573"}, ] [package.dependencies] @@ -1103,7 +1240,7 @@ files = [ name = "importlib-metadata" version = "6.0.0" description = "Read metadata from Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1120,25 +1257,6 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] -[[package]] -name = "importlib-resources" -version = "5.10.2" -description = "Read resources from Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, - {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1218,14 +1336,14 @@ pbr = "*" [[package]] name = "jsii" -version = "1.74.0" +version = "1.75.0" description = "Python client for jsii runtime" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "jsii-1.74.0-py3-none-any.whl", hash = "sha256:ee76781fe66106c367fbb3bb383db4f5e9b8ff3d3c4c0f34624c050211f040be"}, - {file = "jsii-1.74.0.tar.gz", hash = "sha256:575131396ad34f8f6e9f2604953ecbf4f3368625656a828b13089e4abb81b443"}, + {file = "jsii-1.75.0-py3-none-any.whl", hash = "sha256:0a36266470e223413f5e3b10ab656bb0a9c8a8902aa180a0c1ebcc93cc15cfce"}, + {file = "jsii-1.75.0.tar.gz", hash = "sha256:87ecc63fdd7e972ae35f25e0804d86ce6f56871f1f4b0dc4e620d3e9fe761912"}, ] [package.dependencies] @@ -1285,27 +1403,26 @@ files = [ [[package]] name = "jsonschema" -version = "4.17.3" +version = "3.2.0" description = "An implementation of JSON Schema validation for Python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = "*" files = [ - {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, - {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, ] [package.dependencies] attrs = ">=17.4.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} -pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} -pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +pyrsistent = ">=0.14.0" +setuptools = "*" +six = ">=1.11.0" [package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] [[package]] name = "junit-xml" @@ -1315,7 +1432,6 @@ category = "dev" optional = false python-versions = "*" files = [ - {file = "junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f"}, {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"}, ] @@ -1871,18 +1987,6 @@ files = [ mako = "*" markdown = ">=3.0" -[[package]] -name = "pkgutil-resolve-name" -version = "1.3.10" -description = "Resolve a name to an object." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, - {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, -] - [[package]] name = "platformdirs" version = "3.0.0" @@ -1981,12 +2085,24 @@ files = [ {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + [[package]] name = "pydantic" version = "1.10.5" description = "Data validation and settings management using python type hints" category = "main" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "pydantic-1.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5920824fe1e21cbb3e38cf0f3dd24857c8959801d1031ce1fac1d50857a03bfb"}, @@ -2415,6 +2531,27 @@ colorama = {version = ">=0.4.1", markers = "python_version > \"3.4\""} future = "*" mando = ">=0.6,<0.7" +[[package]] +name = "redis" +version = "4.5.1" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-4.5.1-py3-none-any.whl", hash = "sha256:5deb072d26e67d2be1712603bfb7947ec3431fb0eec9c578994052e33035af6d"}, + {file = "redis-4.5.1.tar.gz", hash = "sha256:1eec3741cda408d3a5f84b78d089c8b8d895f21b3b050988351e925faf202864"}, +] + +[package.dependencies] +async-timeout = ">=4.0.2" +importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "regex" version = "2022.10.31" @@ -2603,6 +2740,23 @@ files = [ attrs = "*" pbr = "*" +[[package]] +name = "setuptools" +version = "67.3.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, + {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -2717,6 +2871,21 @@ files = [ doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] test = ["mypy", "pytest", "typing-extensions"] +[[package]] +name = "types-pyopenssl" +version = "23.0.0.3" +description = "Typing stubs for pyOpenSSL" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-pyOpenSSL-23.0.0.3.tar.gz", hash = "sha256:6ca54d593f8b946f9570f9ed7457c41da3b518feff5e344851941a6209bea62b"}, + {file = "types_pyOpenSSL-23.0.0.3-py3-none-any.whl", hash = "sha256:847ab17a16475a882dc29898648a6a35ad0d3e11a5bba5aa8ab2f3435a8647cb"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" + [[package]] name = "types-python-dateutil" version = "2.8.19.7" @@ -2729,6 +2898,22 @@ files = [ {file = "types_python_dateutil-2.8.19.7-py3-none-any.whl", hash = "sha256:669751e1e6d4f3dbbff471231740e7ecdae2135b604383e477fe31fd56223967"}, ] +[[package]] +name = "types-redis" +version = "4.5.1.1" +description = "Typing stubs for redis" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-redis-4.5.1.1.tar.gz", hash = "sha256:c072e4824855f46d0a968509c3e0fa4789fc13b62d472064527bad3d1815aeed"}, + {file = "types_redis-4.5.1.1-py3-none-any.whl", hash = "sha256:081dfeec730df6e3f32ccbdafe3198873b7c02516c22d79cc2a40efdd69a3963"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" +types-pyOpenSSL = "*" + [[package]] name = "types-requests" version = "2.28.11.13" @@ -2746,26 +2931,26 @@ types-urllib3 = "<1.27" [[package]] name = "types-urllib3" -version = "1.26.25.5" +version = "1.26.25.6" description = "Typing stubs for urllib3" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-urllib3-1.26.25.5.tar.gz", hash = "sha256:5630e578246d170d91ebe3901788cd28d53c4e044dc2e2488e3b0d55fb6895d8"}, - {file = "types_urllib3-1.26.25.5-py3-none-any.whl", hash = "sha256:e8f25c8bb85cde658c72ee931e56e7abd28803c26032441eea9ff4a4df2b0c31"}, + {file = "types-urllib3-1.26.25.6.tar.gz", hash = "sha256:35586727cbd7751acccf2c0f34a88baffc092f435ab62458f10776466590f2d5"}, + {file = "types_urllib3-1.26.25.6-py3-none-any.whl", hash = "sha256:a6c23c41bd03e542eaee5423a018f833077b51c4bf9ceb5aa544e12b812d5604"}, ] [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] @@ -2936,7 +3121,7 @@ requests = ">=2.0,<3.0" name = "zipp" version = "3.13.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2952,10 +3137,11 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" all = ["pydantic", "aws-xray-sdk", "fastjsonschema"] aws-sdk = ["boto3"] parser = ["pydantic"] +redis = ["redis"] tracer = ["aws-xray-sdk"] validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "a73608119ca34396048b1c318e1e9bb5552e64cb59aa5aa3ab152d310687c3c5" +content-hash = "bdbccdd716558b962a801c46c062aeb7892153a1f821c3cfee836294a102cb0f" diff --git a/pyproject.toml b/pyproject.toml index 83fc4f81c36..54f06b6a849 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ fastjsonschema = { version = "^2.14.5", optional = true } pydantic = { version = "^1.8.2", optional = true } boto3 = { version = "^1.20.32", optional = true } typing-extensions = "^4.4.0" +redis = {version = "^4.4.2", optional = true} [tool.poetry.dev-dependencies] coverage = {extras = ["toml"], version = "^7.1"} @@ -98,12 +99,15 @@ tracer = ["aws-xray-sdk"] all = ["pydantic", "aws-xray-sdk", "fastjsonschema"] # allow customers to run code locally without emulators (SAM CLI, etc.) aws-sdk = ["boto3"] +redis = ["redis"] [tool.poetry.group.dev.dependencies] cfn-lint = "0.67.0" mypy = "^0.982" types-python-dateutil = "^2.8.19.6" +types-redis = "^4.5.1.1" httpx = "^0.23.3" +redis = "^4.5.1" [tool.coverage.run] source = ["aws_lambda_powertools"]