From e6c210873b080b2b5be329db039eb81ee8d1e0fb Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 27 Aug 2024 23:39:12 +0100 Subject: [PATCH 1/2] Fix unexpected bugs in v3 --- .../event_handler/openapi/constants.py | 4 - .../event_handler/openapi/models.py | 9 +- .../openapi/swagger_ui/oauth2.py | 2 +- .../event_handler/openapi/types.py | 5 +- .../api_gateway_authorizer_event.py | 102 ++++++++++++- .../data_classes/appsync_resolver_event.py | 58 ++++++- .../utilities/data_classes/common.py | 115 +++++++++++++- .../data_classes/shared_functions.py | 141 ++++++++++++++++++ .../utilities/data_classes/vpc_lattice.py | 4 +- docs/utilities/batch.md | 36 ----- ...nced_accessing_lambda_context_decorator.py | 28 ---- .../src/getting_started_dynamodb_decorator.py | 33 ---- .../src/getting_started_kinesis_decorator.py | 28 ---- .../src/getting_started_sqs_decorator.py | 29 ---- .../src/getting_started_sqs_fifo_decorator.py | 28 ---- .../test_api_gateway_authorizer_event.py | 15 ++ .../test_appsync_resolver_event.py | 12 +- .../data_classes/test_vpc_lattice_eventv2.py | 9 ++ 18 files changed, 456 insertions(+), 202 deletions(-) delete mode 100644 examples/batch_processing/src/advanced_accessing_lambda_context_decorator.py delete mode 100644 examples/batch_processing/src/getting_started_dynamodb_decorator.py delete mode 100644 examples/batch_processing/src/getting_started_kinesis_decorator.py delete mode 100644 examples/batch_processing/src/getting_started_sqs_decorator.py delete mode 100644 examples/batch_processing/src/getting_started_sqs_fifo_decorator.py diff --git a/aws_lambda_powertools/event_handler/openapi/constants.py b/aws_lambda_powertools/event_handler/openapi/constants.py index 3fa50dc1411..f5d72d47f7e 100644 --- a/aws_lambda_powertools/event_handler/openapi/constants.py +++ b/aws_lambda_powertools/event_handler/openapi/constants.py @@ -1,6 +1,2 @@ -from pydantic import ConfigDict - DEFAULT_API_VERSION = "1.0.0" DEFAULT_OPENAPI_VERSION = "3.1.0" -MODEL_CONFIG_ALLOW = ConfigDict(extra="allow") -MODEL_CONFIG_IGNORE = ConfigDict(extra="ignore") diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py index 69d0e96cc9f..d1bc1bce386 100644 --- a/aws_lambda_powertools/event_handler/openapi/models.py +++ b/aws_lambda_powertools/event_handler/openapi/models.py @@ -2,14 +2,13 @@ from enum import Enum from typing import Any, Dict, List, Literal, Optional, Set, Union -from pydantic import AnyUrl, BaseModel, Field +from pydantic import AnyUrl, BaseModel, ConfigDict, Field from typing_extensions import Annotated from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild -from aws_lambda_powertools.event_handler.openapi.constants import ( - MODEL_CONFIG_ALLOW, - MODEL_CONFIG_IGNORE, -) + +MODEL_CONFIG_ALLOW = ConfigDict(extra="allow") +MODEL_CONFIG_IGNORE = ConfigDict(extra="ignore") """ The code defines Pydantic models for the various OpenAPI objects like OpenAPI, PathItem, Operation, Parameter etc. diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py index 490029d1f73..4cafdfe401c 100644 --- a/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py +++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, Field, field_validator -from aws_lambda_powertools.event_handler.openapi.constants import ( +from aws_lambda_powertools.event_handler.openapi.models import ( MODEL_CONFIG_ALLOW, ) from aws_lambda_powertools.shared.functions import powertools_dev_is_set diff --git a/aws_lambda_powertools/event_handler/openapi/types.py b/aws_lambda_powertools/event_handler/openapi/types.py index 59cf91f3567..fcbc3ecbef1 100644 --- a/aws_lambda_powertools/event_handler/openapi/types.py +++ b/aws_lambda_powertools/event_handler/openapi/types.py @@ -4,14 +4,13 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type, TypedDict, Union -from pydantic import BaseModel - if TYPE_CHECKING: + from pydantic import BaseModel # noqa: F401 from typing_extensions import NotRequired CacheKey = Union[Callable[..., Any], None] IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] -TypeModelOrEnum = Union[Type[BaseModel], Type[Enum]] +TypeModelOrEnum = Union[Type["BaseModel"], Type[Enum]] ModelNameMap = Dict[TypeModelOrEnum, str] UnionType = getattr(types, "UnionType", Union) diff --git a/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py b/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py index 09d7b7ecb85..5143b9df88e 100644 --- a/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py +++ b/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py @@ -2,7 +2,10 @@ import enum import re -from typing import Any +import warnings +from typing import Any, overload + +from typing_extensions import deprecated from aws_lambda_powertools.utilities.data_classes.common import ( BaseRequestContext, @@ -10,6 +13,10 @@ CaseInsensitiveDict, DictWrapper, ) +from aws_lambda_powertools.utilities.data_classes.shared_functions import ( + get_header_value, +) +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning class APIGatewayRouteArn: @@ -162,6 +169,55 @@ def stage_variables(self) -> dict[str, str]: def request_context(self) -> BaseRequestContext: return BaseRequestContext(self._data) + @overload + def get_header_value( + self, + name: str, + default_value: str, + case_sensitive: bool = False, + ) -> str: ... + + @overload + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: ... + + @deprecated( + "`get_header_value` function is deprecated; Access headers directly using event.headers.get('HeaderName')", + category=None, + ) + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: + """Get header value by name + Parameters + ---------- + name: str + Header name + default_value: str, optional + Default value if no value was found by name + case_sensitive: bool + Whether to use a case-sensitive look up + Returns + ------- + str, optional + Header value + """ + warnings.warn( + "The `get_header_value` function is deprecated in V3 and the `case_sensitive` parameter " + "no longer has any effect. This function will be removed in the next major version. " + "Instead, access headers directly using event.headers.get('HeaderName'), which is case insensitive.", + category=PowertoolsDeprecationWarning, + stacklevel=2, + ) + return get_header_value(self.headers, name, default_value, case_sensitive) + class APIGatewayAuthorizerEventV2(DictWrapper): """API Gateway Authorizer Event Format 2.0 @@ -244,6 +300,50 @@ def path_parameters(self) -> dict[str, str]: def stage_variables(self) -> dict[str, str]: return self.get("stageVariables") or {} + @overload + def get_header_value(self, name: str, default_value: str, case_sensitive: bool = False) -> str: ... + + @overload + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: ... + + @deprecated( + "`get_header_value` function is deprecated; Access headers directly using event.headers.get('HeaderName')", + category=None, + ) + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: + """Get header value by name + Parameters + ---------- + name: str + Header name + default_value: str, optional + Default value if no value was found by name + case_sensitive: bool + Whether to use a case-sensitive look up + Returns + ------- + str, optional + Header value + """ + warnings.warn( + "The `get_header_value` function is deprecated in V3 and the `case_sensitive` parameter " + "no longer has any effect. This function will be removed in the next major version. " + "Instead, access headers directly using event.headers.get('HeaderName'), which is case insensitive.", + category=PowertoolsDeprecationWarning, + stacklevel=2, + ) + return get_header_value(self.headers, name, default_value, case_sensitive) + class APIGatewayAuthorizerResponseV2: """Api Gateway HTTP API V2 payload authorizer simple response helper diff --git a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py index 99e3b7015a4..9d2223d2b3e 100644 --- a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py +++ b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py @@ -1,8 +1,15 @@ from __future__ import annotations -from typing import Any +import warnings +from typing import Any, overload + +from typing_extensions import deprecated from aws_lambda_powertools.utilities.data_classes.common import CaseInsensitiveDict, DictWrapper +from aws_lambda_powertools.utilities.data_classes.shared_functions import ( + get_header_value, +) +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning def get_identity_object(identity: dict | None) -> Any: @@ -212,3 +219,52 @@ def stash(self) -> dict: stash to pass arbitrary data across request and response mapping templates, and across functions in a pipeline resolver.""" return self.get("stash") or {} + + @overload + def get_header_value( + self, + name: str, + default_value: str, + case_sensitive: bool = False, + ) -> str: ... + + @overload + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: ... + + @deprecated( + "`get_header_value` function is deprecated; Access headers directly using event.headers.get('HeaderName')", + category=None, + ) + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: + """Get header value by name + Parameters + ---------- + name: str + Header name + default_value: str, optional + Default value if no value was found by name + case_sensitive: bool + Whether to use a case-sensitive look up + Returns + ------- + str, optional + Header value + """ + warnings.warn( + "The `get_header_value` function is deprecated in V3 and the `case_sensitive` parameter " + "no longer has any effect. This function will be removed in the next major version. " + "Instead, access headers directly using event.headers.get('HeaderName'), which is case insensitive.", + category=PowertoolsDeprecationWarning, + stacklevel=2, + ) + return get_header_value(self.request_headers, name, default_value, case_sensitive) diff --git a/aws_lambda_powertools/utilities/data_classes/common.py b/aws_lambda_powertools/utilities/data_classes/common.py index b44e9b8ab3c..ec4335b9ee8 100644 --- a/aws_lambda_powertools/utilities/data_classes/common.py +++ b/aws_lambda_powertools/utilities/data_classes/common.py @@ -2,12 +2,23 @@ import base64 import json +import warnings from functools import cached_property -from typing import TYPE_CHECKING, Any, Callable, Iterator, Mapping +from typing import TYPE_CHECKING, Any, Callable, Iterator, Mapping, overload + +from typing_extensions import deprecated + +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning if TYPE_CHECKING: from aws_lambda_powertools.shared.headers_serializer import BaseHeadersSerializer +from aws_lambda_powertools.utilities.data_classes.shared_functions import ( + get_header_value, + get_multi_value_query_string_values, + get_query_string_value, +) + class CaseInsensitiveDict(dict): """Case insensitive dict implementation. Assumes string keys only.""" @@ -208,6 +219,108 @@ def http_method(self) -> str: """The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT.""" return self["httpMethod"] + @overload + def get_query_string_value(self, name: str, default_value: str) -> str: ... + + @overload + def get_query_string_value(self, name: str, default_value: str | None = None) -> str | None: ... + + def get_query_string_value(self, name: str, default_value: str | None = None) -> str | None: + """Get query string value by name + Parameters + ---------- + name: str + Query string parameter name + default_value: str, optional + Default value if no value was found by name + Returns + ------- + str, optional + Query string parameter value + """ + return get_query_string_value( + query_string_parameters=self.query_string_parameters, + name=name, + default_value=default_value, + ) + + def get_multi_value_query_string_values( + self, + name: str, + default_values: list[str] | None = None, + ) -> list[str]: + """Get multi-value query string parameter values by name + Parameters + ---------- + name: str + Multi-Value query string parameter name + default_values: List[str], optional + Default values is no values are found by name + Returns + ------- + List[str], optional + List of query string values + """ + return get_multi_value_query_string_values( + multi_value_query_string_parameters=self.multi_value_query_string_parameters, + name=name, + default_values=default_values, + ) + + @overload + def get_header_value( + self, + name: str, + default_value: str, + case_sensitive: bool = False, + ) -> str: ... + + @overload + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: ... + + @deprecated( + "`get_header_value` function is deprecated; Access headers directly using event.headers.get('HeaderName')", + category=None, + ) + def get_header_value( + self, + name: str, + default_value: str | None = None, + case_sensitive: bool = False, + ) -> str | None: + """Get header value by name + Parameters + ---------- + name: str + Header name + default_value: str, optional + Default value if no value was found by name + case_sensitive: bool + Whether to use a case-sensitive look up. By default we make a case-insensitive lookup. + Returns + ------- + str, optional + Header value + """ + warnings.warn( + "The `get_header_value` function is deprecated in V3 and the `case_sensitive` parameter " + "no longer has any effect. This function will be removed in the next major version. " + "Instead, access headers directly using event.headers.get('HeaderName'), which is case insensitive.", + category=PowertoolsDeprecationWarning, + stacklevel=2, + ) + return get_header_value( + headers=self.headers, + name=name, + default_value=default_value, + case_sensitive=case_sensitive, + ) + def header_serializer(self) -> BaseHeadersSerializer: raise NotImplementedError() diff --git a/aws_lambda_powertools/utilities/data_classes/shared_functions.py b/aws_lambda_powertools/utilities/data_classes/shared_functions.py index 4f3451714a1..ab7bd3f57b3 100644 --- a/aws_lambda_powertools/utilities/data_classes/shared_functions.py +++ b/aws_lambda_powertools/utilities/data_classes/shared_functions.py @@ -1,4 +1,12 @@ +from __future__ import annotations + import base64 +import warnings +from typing import Any, overload + +from typing_extensions import deprecated + +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning def base64_decode(value: str) -> str: @@ -16,3 +24,136 @@ def base64_decode(value: str) -> str: The decoded string value. """ return base64.b64decode(value).decode("UTF-8") + + +@overload +def get_header_value( + headers: dict[str, Any], + name: str, + default_value: str, + case_sensitive: bool = False, +) -> str: ... + + +@overload +def get_header_value( + headers: dict[str, Any], + name: str, + default_value: str | None = None, + case_sensitive: bool = False, +) -> str | None: ... + + +@deprecated( + "`get_header_value` function is deprecated; Access headers directly using event.headers.get('HeaderName')", + category=None, +) +def get_header_value( + headers: dict[str, Any], + name: str, + default_value: str | None = None, + case_sensitive: bool = False, +) -> str | None: + """ + Get the value of a header by its name. + Parameters + ---------- + headers: Dict[str, str] + The dictionary of headers. + name: str + The name of the header to retrieve. + default_value: str, optional + The default value to return if the header is not found. Default is None. + case_sensitive: bool, optional + Indicates whether the header name should be case-sensitive. Default is False. + Returns + ------- + str, optional + The value of the header if found, otherwise the default value or None. + """ + + warnings.warn( + "The `get_header_value` function is deprecated in V3 and the `case_sensitive` parameter " + "no longer has any effect. This function will be removed in the next major version. " + "Instead, access headers directly using event.headers.get('HeaderName'), which is case insensitive.", + category=PowertoolsDeprecationWarning, + stacklevel=2, + ) + + # If headers is NoneType, return default value + if not headers: + return default_value + + if case_sensitive: + return headers.get(name, default_value) + name_lower = name.lower() + + return next( + # Iterate over the dict and do a case-insensitive key comparison + (value for key, value in headers.items() if key.lower() == name_lower), + # Default value is returned if no matches was found + default_value, + ) + + +@overload +def get_query_string_value( + query_string_parameters: dict[str, str] | None, + name: str, + default_value: str, +) -> str: ... + + +@overload +def get_query_string_value( + query_string_parameters: dict[str, str] | None, + name: str, + default_value: str | None = None, +) -> str | None: ... + + +def get_query_string_value( + query_string_parameters: dict[str, str] | None, + name: str, + default_value: str | None = None, +) -> str | None: + """ + Retrieves the value of a query string parameter specified by the given name. + Parameters + ---------- + name: str + The name of the query string parameter to retrieve. + default_value: str, optional + The default value to return if the parameter is not found. Defaults to None. + Returns + ------- + str. optional + The value of the query string parameter if found, or the default value if not found. + """ + params = query_string_parameters + return default_value if params is None else params.get(name, default_value) + + +def get_multi_value_query_string_values( + multi_value_query_string_parameters: dict[str, list[str]] | None, + name: str, + default_values: list[str] | None = None, +) -> list[str]: + """ + Retrieves the values of a multi-value string parameters specified by the given name. + Parameters + ---------- + name: str + The name of the query string parameter to retrieve. + default_value: list[str], optional + The default value to return if the parameter is not found. Defaults to None. + Returns + ------- + List[str]. optional + The values of the query string parameter if found, or the default values if not found. + """ + + default = default_values or [] + params = multi_value_query_string_parameters or {} + + return params.get(name) or default diff --git a/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py b/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py index 661193b01e0..5100b038850 100644 --- a/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +++ b/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py @@ -35,9 +35,7 @@ def headers(self) -> dict[str, str]: def decoded_body(self) -> str: """Dynamically base64 decode body as a str""" body: str = self["body"] - if self.is_base64_encoded: - return base64_decode(body) - return body + return base64_decode(body) if self.is_base64_encoded else body @property def method(self) -> str: diff --git a/docs/utilities/batch.md b/docs/utilities/batch.md index 491773e392c..5dab0e46f14 100644 --- a/docs/utilities/batch.md +++ b/docs/utilities/batch.md @@ -119,12 +119,6 @@ Processing batches from SQS works in three stages: --8<-- "examples/batch_processing/src/getting_started_sqs_context_manager.py" ``` -=== "As a decorator (deprecated)" - - ```python hl_lines="4-9 12 18 27 29" - --8<-- "examples/batch_processing/src/getting_started_sqs_decorator.py" - ``` - === "Sample response" The second record failed to be processed, therefore the processor added its message ID in the response. @@ -161,12 +155,6 @@ Enable the `skip_group_on_error` option for seamless processing of messages from --8<-- "examples/batch_processing/src/getting_started_sqs_fifo_context_manager.py" ``` -=== "As a decorator (deprecated)" - - ```python hl_lines="5-6 11 26" - --8<-- "examples/batch_processing/src/getting_started_sqs_fifo_decorator.py" - ``` - === "Enabling skip_group_on_error flag" ```python hl_lines="2-6 9 23" @@ -197,12 +185,6 @@ Processing batches from Kinesis works in three stages: --8<-- "examples/batch_processing/src/getting_started_kinesis_context_manager.py" ``` -=== "As a decorator (deprecated)" - - ```python hl_lines="2-9 12 18 26" - --8<-- "examples/batch_processing/src/getting_started_kinesis_decorator.py" - ``` - === "Sample response" The second record failed to be processed, therefore the processor added its sequence number in the response. @@ -241,12 +223,6 @@ Processing batches from DynamoDB Streams works in three stages: --8<-- "examples/batch_processing/src/getting_started_dynamodb_context_manager.py" ``` -=== "As a decorator (deprecated)" - - ```python hl_lines="4-11 14 20 31" - --8<-- "examples/batch_processing/src/getting_started_dynamodb_decorator.py" - ``` - === "Sample response" The second record failed to be processed, therefore the processor added its sequence number in the response. @@ -538,12 +514,6 @@ We can automatically inject the [Lambda context](https://docs.aws.amazon.com/lam --8<-- "examples/batch_processing/src/advanced_accessing_lambda_context.py" ``` -=== "As a decorator (deprecated)" - - ```python hl_lines="18 26" - --8<-- "examples/batch_processing/src/advanced_accessing_lambda_context_decorator.py" - ``` - === "As a context manager" ```python hl_lines="14 24" @@ -671,12 +641,6 @@ Given a SQS batch where the first batch record succeeds and the second fails pro Use context manager when you want access to the processed messages or handle `BatchProcessingError` exception when all records within the batch fail to be processed. -### What's the difference between the decorator and process_partial_response functions? - -`batch_processor` and `async_batch_processor` decorators are now marked as deprecated. Historically, they were kept due to backwards compatibility and to minimize code changes between V2 and V3. We will remove both in the next major release. - -As 2.12.0, `process_partial_response` and `async_process_partial_response` are the recommended instead. It reduces boilerplate, smaller memory/CPU cycles, and it makes it less error prone - e.g., decorators required an additional return. - ### Integrating exception handling with Sentry.io When using Sentry.io for error monitoring, you can override `failure_handler` to capture each processing exception with Sentry SDK: diff --git a/examples/batch_processing/src/advanced_accessing_lambda_context_decorator.py b/examples/batch_processing/src/advanced_accessing_lambda_context_decorator.py deleted file mode 100644 index 267e9ddbd62..00000000000 --- a/examples/batch_processing/src/advanced_accessing_lambda_context_decorator.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.utilities.batch import ( - BatchProcessor, - EventType, - batch_processor, -) -from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord -from aws_lambda_powertools.utilities.typing import LambdaContext - -processor = BatchProcessor(event_type=EventType.SQS) -tracer = Tracer() -logger = Logger() - - -@tracer.capture_method -def record_handler(record: SQSRecord, lambda_context: Optional[LambdaContext] = None): - if lambda_context is not None: - remaining_time = lambda_context.get_remaining_time_in_millis() - logger.info(remaining_time) - - -@logger.inject_lambda_context -@tracer.capture_lambda_handler -@batch_processor(record_handler=record_handler, processor=processor) -def lambda_handler(event, context: LambdaContext): - return processor.response() diff --git a/examples/batch_processing/src/getting_started_dynamodb_decorator.py b/examples/batch_processing/src/getting_started_dynamodb_decorator.py deleted file mode 100644 index a2df6a11f8c..00000000000 --- a/examples/batch_processing/src/getting_started_dynamodb_decorator.py +++ /dev/null @@ -1,33 +0,0 @@ -import json - -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.utilities.batch import ( - BatchProcessor, - EventType, - batch_processor, -) -from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import ( - DynamoDBRecord, -) -from aws_lambda_powertools.utilities.typing import LambdaContext - -processor = BatchProcessor(event_type=EventType.DynamoDBStreams) -tracer = Tracer() -logger = Logger() - - -@tracer.capture_method -def record_handler(record: DynamoDBRecord): - if record.dynamodb and record.dynamodb.new_image: - logger.info(record.dynamodb.new_image) - message = record.dynamodb.new_image.get("Message") - if message: - payload: dict = json.loads(message) - logger.info(payload) - - -@logger.inject_lambda_context -@tracer.capture_lambda_handler -@batch_processor(record_handler=record_handler, processor=processor) -def lambda_handler(event, context: LambdaContext): - return processor.response() diff --git a/examples/batch_processing/src/getting_started_kinesis_decorator.py b/examples/batch_processing/src/getting_started_kinesis_decorator.py deleted file mode 100644 index 107c94ffbad..00000000000 --- a/examples/batch_processing/src/getting_started_kinesis_decorator.py +++ /dev/null @@ -1,28 +0,0 @@ -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.utilities.batch import ( - BatchProcessor, - EventType, - batch_processor, -) -from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import ( - KinesisStreamRecord, -) -from aws_lambda_powertools.utilities.typing import LambdaContext - -processor = BatchProcessor(event_type=EventType.KinesisDataStreams) -tracer = Tracer() -logger = Logger() - - -@tracer.capture_method -def record_handler(record: KinesisStreamRecord): - logger.info(record.kinesis.data_as_text) - payload: dict = record.kinesis.data_as_json() - logger.info(payload) - - -@logger.inject_lambda_context -@tracer.capture_lambda_handler -@batch_processor(record_handler=record_handler, processor=processor) -def lambda_handler(event, context: LambdaContext): - return processor.response() diff --git a/examples/batch_processing/src/getting_started_sqs_decorator.py b/examples/batch_processing/src/getting_started_sqs_decorator.py deleted file mode 100644 index 4f058beb862..00000000000 --- a/examples/batch_processing/src/getting_started_sqs_decorator.py +++ /dev/null @@ -1,29 +0,0 @@ -import json - -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.utilities.batch import ( - BatchProcessor, - EventType, - batch_processor, -) -from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord -from aws_lambda_powertools.utilities.typing import LambdaContext - -processor = BatchProcessor(event_type=EventType.SQS) -tracer = Tracer() -logger = Logger() - - -@tracer.capture_method -def record_handler(record: SQSRecord): - payload: str = record.body - if payload: - item: dict = json.loads(payload) - logger.info(item) - - -@logger.inject_lambda_context -@tracer.capture_lambda_handler -@batch_processor(record_handler=record_handler, processor=processor) -def lambda_handler(event, context: LambdaContext): - return processor.response() diff --git a/examples/batch_processing/src/getting_started_sqs_fifo_decorator.py b/examples/batch_processing/src/getting_started_sqs_fifo_decorator.py deleted file mode 100644 index 22448d2ce8a..00000000000 --- a/examples/batch_processing/src/getting_started_sqs_fifo_decorator.py +++ /dev/null @@ -1,28 +0,0 @@ -import json - -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.utilities.batch import ( - SqsFifoPartialProcessor, - batch_processor, -) -from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord -from aws_lambda_powertools.utilities.typing import LambdaContext - -processor = SqsFifoPartialProcessor() -tracer = Tracer() -logger = Logger() - - -@tracer.capture_method -def record_handler(record: SQSRecord): - payload: str = record.body - if payload: - item: dict = json.loads(payload) - logger.info(item) - - -@logger.inject_lambda_context -@tracer.capture_lambda_handler -@batch_processor(record_handler=record_handler, processor=processor) -def lambda_handler(event, context: LambdaContext): - return processor.response() diff --git a/tests/unit/data_classes/test_api_gateway_authorizer_event.py b/tests/unit/data_classes/test_api_gateway_authorizer_event.py index 4ae44643474..9362129b8d5 100644 --- a/tests/unit/data_classes/test_api_gateway_authorizer_event.py +++ b/tests/unit/data_classes/test_api_gateway_authorizer_event.py @@ -1,9 +1,12 @@ +import pytest + from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import ( APIGatewayAuthorizerEventV2, APIGatewayAuthorizerRequestEvent, APIGatewayAuthorizerResponseV2, APIGatewayAuthorizerTokenEvent, ) +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning from tests.functional.utils import load_event @@ -52,10 +55,17 @@ def test_api_gateway_authorizer_v2(): assert parsed_event.path_parameters == raw_event["pathParameters"] assert parsed_event.stage_variables == raw_event["stageVariables"] + # NEW METHOD assert parsed_event.headers["Authorization"] == "value" assert parsed_event.headers["authorization"] == "value" assert parsed_event.headers.get("missing") is None + # DEPRECATED METHOD - Remove in V4 + with pytest.warns(PowertoolsDeprecationWarning): + assert parsed_event.get_header_value("Authorization") == "value" + assert parsed_event.get_header_value("authorization") == "value" + assert parsed_event.get_header_value("missing") is None + # Check for optionals event_optionals = APIGatewayAuthorizerEventV2({"requestContext": {}}) assert event_optionals.identity_source == [] @@ -90,6 +100,11 @@ def test_api_gateway_authorizer_request_event(): assert parsed_event.path == raw_event["path"] assert parsed_event.http_method == raw_event["httpMethod"] assert parsed_event.headers == raw_event["headers"] + + # DEPRECATED METHOD - Remove in V4 + with pytest.warns(PowertoolsDeprecationWarning): + assert parsed_event.get_header_value("accept") == "*/*" + assert parsed_event.headers["accept"] == "*/*" assert parsed_event.query_string_parameters == raw_event["queryStringParameters"] assert parsed_event.path_parameters == raw_event["pathParameters"] diff --git a/tests/unit/data_classes/test_appsync_resolver_event.py b/tests/unit/data_classes/test_appsync_resolver_event.py index da607d05379..8d753e9a6fb 100644 --- a/tests/unit/data_classes/test_appsync_resolver_event.py +++ b/tests/unit/data_classes/test_appsync_resolver_event.py @@ -1,3 +1,5 @@ +import pytest + from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import ( AppSyncIdentityCognito, @@ -5,6 +7,7 @@ AppSyncResolverEventInfo, get_identity_object, ) +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning from tests.functional.utils import load_event @@ -17,9 +20,16 @@ def test_appsync_resolver_event(): assert parsed_event.arguments.get("name") == raw_event["arguments"]["name"] assert parsed_event.identity.claims.get("token_use") == raw_event["identity"]["claims"]["token_use"] assert parsed_event.source.get("name") == raw_event["source"]["name"] + + # NEW METHOD assert parsed_event.request_headers["X-amzn-trace-id"] == "Root=1-60488877-0b0c4e6727ab2a1c545babd0" - assert parsed_event.request_headers["x-amzn-trace-id"] == "Root=1-60488877-0b0c4e6727ab2a1c545babd0" assert parsed_event.request_headers.get("missing", "Foo") == "Foo" + + # DEPRECATED METHOD - Remove in V4 + with pytest.warns(PowertoolsDeprecationWarning): + assert parsed_event.get_header_value("X-amzn-trace-id") == "Root=1-60488877-0b0c4e6727ab2a1c545babd0" + assert parsed_event.get_header_value("missing", default_value="Foo") == "Foo" + assert parsed_event.prev_result == {} assert parsed_event.stash == {} diff --git a/tests/unit/data_classes/test_vpc_lattice_eventv2.py b/tests/unit/data_classes/test_vpc_lattice_eventv2.py index 87a9a69be38..1824e1ec080 100644 --- a/tests/unit/data_classes/test_vpc_lattice_eventv2.py +++ b/tests/unit/data_classes/test_vpc_lattice_eventv2.py @@ -1,4 +1,7 @@ +import pytest + from aws_lambda_powertools.utilities.data_classes.vpc_lattice import VPCLatticeEventV2 +from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning from tests.functional.utils import load_event @@ -14,6 +17,12 @@ def test_vpc_lattice_v2_event(): assert parsed_event.method == raw_event["method"] assert parsed_event.headers == raw_event["headers"] assert parsed_event.query_string_parameters == raw_event["queryStringParameters"] + assert parsed_event.get_query_string_value("order-id") == "1" + + # DEPRECATED METHOD - Remove in V4 + with pytest.warns(PowertoolsDeprecationWarning): + assert parsed_event.get_header_value("user_agent") == "curl/7.64.1" + assert parsed_event.body == raw_event["body"] assert parsed_event.is_base64_encoded == raw_event["isBase64Encoded"] assert parsed_event.request_context.region == raw_event["requestContext"]["region"] From d1a81af1fc8ba67bf97ffb7d37ab53f9c0fb2b1a Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 27 Aug 2024 23:49:14 +0100 Subject: [PATCH 2/2] Fix unexpected bugs in v3 --- .../event_handler_rest/src/accessing_request_details_headers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event_handler_rest/src/accessing_request_details_headers.py b/examples/event_handler_rest/src/accessing_request_details_headers.py index de5df2fed0b..8fedef57b88 100644 --- a/examples/event_handler_rest/src/accessing_request_details_headers.py +++ b/examples/event_handler_rest/src/accessing_request_details_headers.py @@ -16,7 +16,7 @@ def get_todos(): endpoint = "https://jsonplaceholder.typicode.com/todos" - api_key = app.current_event.headers["X-Api-Key"] + api_key = app.current_event.headers.get("X-Api-Key") todos: Response = requests.get(endpoint, headers={"X-Api-Key": api_key}) todos.raise_for_status()