From 61589b8728bbdbdacc7cbf56c497eb4fed72e550 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Mon, 16 Dec 2024 17:04:58 -0300 Subject: [PATCH 01/13] add pydatinc validationError --- .../utilities/parser/parser.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/parser.py b/aws_lambda_powertools/utilities/parser/parser.py index fd0b298bd7f..9bd18a4c1ef 100644 --- a/aws_lambda_powertools/utilities/parser/parser.py +++ b/aws_lambda_powertools/utilities/parser/parser.py @@ -4,7 +4,7 @@ import typing from typing import TYPE_CHECKING, Any, Callable, overload -from pydantic import PydanticSchemaGenerationError +from pydantic import PydanticSchemaGenerationError, ValidationError from aws_lambda_powertools.middleware_factory import lambda_handler_decorator from aws_lambda_powertools.utilities.parser.exceptions import InvalidEnvelopeError, InvalidModelTypeError @@ -108,10 +108,16 @@ def handler(event: Order, context: LambdaContext): logger.debug(f"Calling handler {handler.__name__}") return handler(parsed_event, context, **kwargs) - except AttributeError as exc: + except ValidationError as exc: + # Raise Pydantic validation errors as is + raise + except InvalidModelTypeError as exc: + # Raise invalid model type errors as is + raise + except (TypeError, ValueError) as exc: + # Catch type and value errors that might occur during model instantiation raise InvalidModelTypeError(f"Error: {str(exc)}. Please ensure the type you're trying to parse into is correct") - @overload def parse(event: dict[str, Any], model: type[T]) -> T: ... # pragma: no cover @@ -193,13 +199,17 @@ def handler(event: Order, context: LambdaContext): return adapter.validate_json(event) return adapter.validate_python(event) + + except ValidationError as exc: + # Raise validation errors as is + raise # Pydantic raises PydanticSchemaGenerationError when the model is not a Pydantic model # This is seen in the tests where we pass a non-Pydantic model type to the parser or # when we pass a data structure that does not match the model (trying to parse a true/false/etc into a model) except PydanticSchemaGenerationError as exc: raise InvalidModelTypeError(f"The event supplied is unable to be validated into {type(model)}") from exc - except AttributeError as exc: + except (TypeError, ValueError) as exc: raise InvalidModelTypeError( f"Error: {str(exc)}. Please ensure the Input model inherits from BaseModel,\n" "and your payload adheres to the specified Input model structure.\n" From 169eae9400f2ebd8ea96d3cd840565771c0476fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=20Falc=C3=A3o?= Date: Mon, 16 Dec 2024 17:32:24 -0300 Subject: [PATCH 02/13] removed unnecessary exc from exceptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ana Falcão --- aws_lambda_powertools/utilities/parser/parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/parser.py b/aws_lambda_powertools/utilities/parser/parser.py index f1fd800a8d8..974c78e059e 100644 --- a/aws_lambda_powertools/utilities/parser/parser.py +++ b/aws_lambda_powertools/utilities/parser/parser.py @@ -111,10 +111,10 @@ def handler(event: Order, context: LambdaContext): logger.debug(f"Calling handler {handler.__name__}") return handler(parsed_event, context, **kwargs) - except ValidationError as exc: + except ValidationError: # Raise Pydantic validation errors as is raise - except InvalidModelTypeError as exc: + except InvalidModelTypeError: # Raise invalid model type errors as is raise except (TypeError, ValueError) as exc: @@ -201,7 +201,7 @@ def handler(event: Order, context: LambdaContext): return _parse_and_validate_event(data=event, adapter=adapter) - except ValidationError as exc: + except ValidationError: # Raise validation errors as is raise From 63840cca6d1f9edfc5691bea7e4f3013ff004408 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 10:11:52 -0300 Subject: [PATCH 03/13] add two more validation error tests --- tests/functional/parser/test_parser.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index aa7efde9528..c1c8ceb15f4 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -130,6 +130,41 @@ def handler(event, _): with pytest.raises(ValidationError): handler({"project": "powertools"}, LambdaContext()) +def test_parser_validation_error(): + class StrictModel(pydantic.BaseModel): + age: int + name: str + + @event_parser(model=StrictModel) + def handle_validation(event: Dict, _: LambdaContext): + return event + + invalid_event = {"age": "not_a_number", "name": 123} # intentionally wrong types + + with pytest.raises(ValidationError) as exc_info: + handle_validation(event=invalid_event, context=LambdaContext()) + + assert "age" in str(exc_info.value) # Verify the error mentions the invalid field + +def test_parser_type_value_errors(): + class CustomModel(pydantic.BaseModel): + timestamp: datetime + status: Literal["SUCCESS", "FAILURE"] + + @event_parser(model=CustomModel) + def handle_type_validation(event: Dict, _: LambdaContext): + return event + + # Test both TypeError and ValueError scenarios + invalid_events = [ + {"timestamp": "invalid-date", "status": "SUCCESS"}, # Will raise ValueError for invalid date + {"timestamp": datetime.now(), "status": "INVALID"} # Will raise ValueError for invalid literal + ] + + for invalid_event in invalid_events: + with pytest.raises((TypeError, ValueError)): + handle_type_validation(event=invalid_event, context=LambdaContext()) + @pytest.mark.parametrize( "test_input,expected", From c5ce0752fa7510ed7f84fb77364d1b1e123dd5eb Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 14:01:49 -0300 Subject: [PATCH 04/13] add more specific error tests --- tests/functional/parser/test_parser.py | 49 +++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index c1c8ceb15f4..4080e30d7fa 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -4,7 +4,7 @@ import pydantic import pytest -from pydantic import ValidationError +from pydantic import ValidationError, BaseModel from typing_extensions import Annotated from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse @@ -165,6 +165,53 @@ def handle_type_validation(event: Dict, _: LambdaContext): with pytest.raises((TypeError, ValueError)): handle_type_validation(event=invalid_event, context=LambdaContext()) +def test_parse_no_model(): + with pytest.raises(exceptions.InvalidModelTypeError): + parse({}, model=None) + + +def test_event_parser_no_model(): + with pytest.raises(exceptions.InvalidModelTypeError): + @event_parser + def handler(event, _): + return event + + handler({}, None) + + +class Shopping(BaseModel): + id: int + description: str + +def test_event_parser_invalid_event(): + event = {"id": "forgot-the-id", "description": "really nice blouse"} # 'id' is invalid + + @event_parser(model=Shopping) + def handler(event, _): + return event + + with pytest.raises(ValidationError): + handler(event, None) + + with pytest.raises(ValidationError): + parse(event, model=Shopping) + + +class NonPydanticModel: + pass + +def test_event_parser_invalid_model_type(): + event = {"id": 123, "breed": "Staffie", "bath": False} + + @event_parser(model=NonPydanticModel) + def handler(event, _): + return event + + with pytest.raises(exceptions.InvalidModelTypeError): + handler(event, None) + + with pytest.raises(exceptions.InvalidEnvelopeError): + parse(event, model=NonPydanticModel, envelope=NonPydanticModel) @pytest.mark.parametrize( "test_input,expected", From 182988b32c416d939e8cb1e2d2d3fc9f199e79a9 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 16:08:43 -0300 Subject: [PATCH 05/13] remove one specific error test --- tests/functional/parser/test_parser.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index 4080e30d7fa..cda8751b3f9 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -165,10 +165,6 @@ def handle_type_validation(event: Dict, _: LambdaContext): with pytest.raises((TypeError, ValueError)): handle_type_validation(event=invalid_event, context=LambdaContext()) -def test_parse_no_model(): - with pytest.raises(exceptions.InvalidModelTypeError): - parse({}, model=None) - def test_event_parser_no_model(): with pytest.raises(exceptions.InvalidModelTypeError): From 38f7722233917174351842698c06cbee6ba5c392 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 19:21:02 -0300 Subject: [PATCH 06/13] fix doc and add more test --- tests/functional/parser/test_parser.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index cda8751b3f9..d963cd0332f 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -4,7 +4,7 @@ import pydantic import pytest -from pydantic import ValidationError, BaseModel +from pydantic import ValidationError, BaseModel, InvalidModelTypeError from typing_extensions import Annotated from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse @@ -167,7 +167,7 @@ def handle_type_validation(event: Dict, _: LambdaContext): def test_event_parser_no_model(): - with pytest.raises(exceptions.InvalidModelTypeError): + with pytest.raises(InvalidModelTypeError): @event_parser def handler(event, _): return event @@ -203,11 +203,12 @@ def test_event_parser_invalid_model_type(): def handler(event, _): return event - with pytest.raises(exceptions.InvalidModelTypeError): + with pytest.raises(InvalidModelTypeError): handler(event, None) - with pytest.raises(exceptions.InvalidEnvelopeError): + with pytest.raises(InvalidModelTypeError) as excinfo: parse(event, model=NonPydanticModel, envelope=NonPydanticModel) + assert "Please ensure the type you're trying to parse into is correct" in str(excinfo.value) @pytest.mark.parametrize( "test_input,expected", From a8ddb39fc8fa43161f196e500200d711732ccd66 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 19:44:41 -0300 Subject: [PATCH 07/13] change funct doc and more tests --- .../utilities/parser/parser.py | 12 ++-- tests/functional/parser/test_parser.py | 60 ++++++++++++------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/parser.py b/aws_lambda_powertools/utilities/parser/parser.py index 974c78e059e..6f6fc4a7d83 100644 --- a/aws_lambda_powertools/utilities/parser/parser.py +++ b/aws_lambda_powertools/utilities/parser/parser.py @@ -84,11 +84,11 @@ def handler(event: Order, context: LambdaContext): Raises ------ ValidationError - When input event does not conform with model provided + When input event does not conform with the provided model InvalidModelTypeError - When model given does not implement BaseModel or is not provided - InvalidEnvelopeError - When envelope given does not implement BaseEnvelope + When the model given does not implement BaseModel, is not provided + TypeError, ValueError + When there's an error during model instantiation, re-raised as InvalidModelTypeError """ if model is None: @@ -180,8 +180,8 @@ def handler(event: Order, context: LambdaContext): When input event does not conform with model provided InvalidModelTypeError When model given does not implement BaseModel - InvalidEnvelopeError - When envelope given does not implement BaseEnvelope + TypeError, ValueError + When there's an error during model instantiation, re-raised as InvalidModelTypeError """ if envelope and callable(envelope): try: diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index d963cd0332f..dce3ff106db 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -4,7 +4,7 @@ import pydantic import pytest -from pydantic import ValidationError, BaseModel, InvalidModelTypeError +from pydantic import ValidationError, BaseModel from typing_extensions import Annotated from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse @@ -167,7 +167,7 @@ def handle_type_validation(event: Dict, _: LambdaContext): def test_event_parser_no_model(): - with pytest.raises(InvalidModelTypeError): + with pytest.raises(exceptions.InvalidModelTypeError): @event_parser def handler(event, _): return event @@ -192,23 +192,31 @@ def handler(event, _): with pytest.raises(ValidationError): parse(event, model=Shopping) - class NonPydanticModel: pass def test_event_parser_invalid_model_type(): + """Test that event_parser raises InvalidModelTypeError when given an invalid model type""" event = {"id": 123, "breed": "Staffie", "bath": False} @event_parser(model=NonPydanticModel) def handler(event, _): return event - with pytest.raises(InvalidModelTypeError): + # Test direct handler invocation + with pytest.raises(exceptions.InvalidModelTypeError) as exc_info: handler(event, None) + assert "Please ensure" in str(exc_info.value) - with pytest.raises(InvalidModelTypeError) as excinfo: - parse(event, model=NonPydanticModel, envelope=NonPydanticModel) - assert "Please ensure the type you're trying to parse into is correct" in str(excinfo.value) + # Test parse function + with pytest.raises(exceptions.InvalidModelTypeError) as exc_info: + parse(event=event, model=NonPydanticModel) + assert "Please ensure" in str(exc_info.value) + + # Test with both model and envelope + with pytest.raises(exceptions.InvalidEnvelopeError) as exc_info: + parse(event=event, model=NonPydanticModel, envelope=NonPydanticModel) + assert "Please ensure" in str(exc_info.value) @pytest.mark.parametrize( "test_input,expected", @@ -217,7 +225,10 @@ def handler(event, _): {"status": "succeeded", "name": "Clifford", "breed": "Labrador"}, "Successfully retrieved Labrador named Clifford", ), - ({"status": "failed", "error": "oh some error"}, "Uh oh. Had a problem: oh some error"), + ( + {"status": "failed", "error": "oh some error"}, + "Uh oh. Had a problem: oh some error", + ), ], ) def test_parser_unions(test_input, expected): @@ -230,19 +241,20 @@ class FailedCallback(pydantic.BaseModel): status: Literal["failed"] error: str - DogCallback = Annotated[Union[SuccessfulCallback, FailedCallback], pydantic.Field(discriminator="status")] + class DogCallback(pydantic.BaseModel): + root: Union[SuccessfulCallback, FailedCallback] = pydantic.Field(discriminator="status") @event_parser(model=DogCallback) def handler(event, _: Any) -> str: - if isinstance(event, FailedCallback): - return f"Uh oh. Had a problem: {event.error}" + if isinstance(event.root, FailedCallback): + return f"Uh oh. Had a problem: {event.root.error}" - return f"Successfully retrieved {event.breed} named {event.name}" + return f"Successfully retrieved {event.root.breed} named {event.root.name}" - ret = handler(test_input, None) + wrapped_input = {"root": test_input} + ret = handler(wrapped_input, None) assert ret == expected - @pytest.mark.parametrize( "test_input,expected", [ @@ -250,7 +262,10 @@ def handler(event, _: Any) -> str: {"status": "succeeded", "name": "Clifford", "breed": "Labrador"}, "Successfully retrieved Labrador named Clifford", ), - ({"status": "failed", "error": "oh some error"}, "Uh oh. Had a problem: oh some error"), + ( + {"status": "failed", "error": "oh some error"}, + "Uh oh. Had a problem: oh some error", + ), ], ) def test_parser_unions_with_type_adapter_instance(test_input, expected): @@ -263,17 +278,18 @@ class FailedCallback(pydantic.BaseModel): status: Literal["failed"] error: str - DogCallback = Annotated[Union[SuccessfulCallback, FailedCallback], pydantic.Field(discriminator="status")] - DogCallbackTypeAdapter = pydantic.TypeAdapter(DogCallback) + class DogCallbackModel(pydantic.BaseModel): + data: Union[SuccessfulCallback, FailedCallback] = pydantic.Field(discriminator="status") - @event_parser(model=DogCallbackTypeAdapter) + @event_parser(model=DogCallbackModel) def handler(event, _: Any) -> str: - if isinstance(event, FailedCallback): - return f"Uh oh. Had a problem: {event.error}" + if isinstance(event.data, FailedCallback): + return f"Uh oh. Had a problem: {event.data.error}" - return f"Successfully retrieved {event.breed} named {event.name}" + return f"Successfully retrieved {event.data.breed} named {event.data.name}" - ret = handler(test_input, None) + wrapped_input = {"data": test_input} + ret = handler(wrapped_input, None) assert ret == expected From 6106aeda201e74fca80b3e830de25e48d7d5917c Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 19:47:39 -0300 Subject: [PATCH 08/13] remove unused import --- tests/functional/parser/test_parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index dce3ff106db..4bd71c059c0 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -5,7 +5,6 @@ import pydantic import pytest from pydantic import ValidationError, BaseModel -from typing_extensions import Annotated from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse from aws_lambda_powertools.utilities.parser.envelopes.sqs import SqsEnvelope From dd22d3f31aa5e13a6884cc2097c5b333b3f9218c Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Tue, 17 Dec 2024 19:57:21 -0300 Subject: [PATCH 09/13] change error message --- tests/functional/parser/test_parser.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index 4bd71c059c0..c75c23c43af 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -195,7 +195,12 @@ class NonPydanticModel: pass def test_event_parser_invalid_model_type(): - """Test that event_parser raises InvalidModelTypeError when given an invalid model type""" + class NonPydanticModel: + pass + + class NonPydanticEnvelope: + pass + event = {"id": 123, "breed": "Staffie", "bath": False} @event_parser(model=NonPydanticModel) @@ -205,17 +210,18 @@ def handler(event, _): # Test direct handler invocation with pytest.raises(exceptions.InvalidModelTypeError) as exc_info: handler(event, None) - assert "Please ensure" in str(exc_info.value) + assert "unable to be validated" in str(exc_info.value) - # Test parse function + # Test parse function with invalid model with pytest.raises(exceptions.InvalidModelTypeError) as exc_info: parse(event=event, model=NonPydanticModel) - assert "Please ensure" in str(exc_info.value) + assert "unable to be validated" in str(exc_info.value) - # Test with both model and envelope + # Test parse function with invalid envelope with pytest.raises(exceptions.InvalidEnvelopeError) as exc_info: - parse(event=event, model=NonPydanticModel, envelope=NonPydanticModel) - assert "Please ensure" in str(exc_info.value) + parse(event=event, model=NonPydanticModel, envelope=NonPydanticEnvelope) + assert "Error" in str(exc_info.value) + @pytest.mark.parametrize( "test_input,expected", From 4955d884d23f23321c771dbcb555006c6a77db3c Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Thu, 19 Dec 2024 09:36:54 -0300 Subject: [PATCH 10/13] remove exceptions not needed --- tests/functional/parser/test_parser.py | 31 -------------------------- 1 file changed, 31 deletions(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index c75c23c43af..14e1ee41a57 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -191,37 +191,6 @@ def handler(event, _): with pytest.raises(ValidationError): parse(event, model=Shopping) -class NonPydanticModel: - pass - -def test_event_parser_invalid_model_type(): - class NonPydanticModel: - pass - - class NonPydanticEnvelope: - pass - - event = {"id": 123, "breed": "Staffie", "bath": False} - - @event_parser(model=NonPydanticModel) - def handler(event, _): - return event - - # Test direct handler invocation - with pytest.raises(exceptions.InvalidModelTypeError) as exc_info: - handler(event, None) - assert "unable to be validated" in str(exc_info.value) - - # Test parse function with invalid model - with pytest.raises(exceptions.InvalidModelTypeError) as exc_info: - parse(event=event, model=NonPydanticModel) - assert "unable to be validated" in str(exc_info.value) - - # Test parse function with invalid envelope - with pytest.raises(exceptions.InvalidEnvelopeError) as exc_info: - parse(event=event, model=NonPydanticModel, envelope=NonPydanticEnvelope) - assert "Error" in str(exc_info.value) - @pytest.mark.parametrize( "test_input,expected", From 5e02582165f4834d297ca710da59817c36111854 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Thu, 19 Dec 2024 09:38:18 -0300 Subject: [PATCH 11/13] remove error handling from decorator --- .../utilities/parser/parser.py | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/parser.py b/aws_lambda_powertools/utilities/parser/parser.py index 6f6fc4a7d83..7c1557d43e8 100644 --- a/aws_lambda_powertools/utilities/parser/parser.py +++ b/aws_lambda_powertools/utilities/parser/parser.py @@ -4,7 +4,7 @@ import typing from typing import TYPE_CHECKING, Any, Callable, overload -from pydantic import PydanticSchemaGenerationError, ValidationError +from pydantic import PydanticSchemaGenerationError from aws_lambda_powertools.middleware_factory import lambda_handler_decorator from aws_lambda_powertools.utilities.parser.exceptions import InvalidEnvelopeError, InvalidModelTypeError @@ -87,8 +87,8 @@ def handler(event: Order, context: LambdaContext): When input event does not conform with the provided model InvalidModelTypeError When the model given does not implement BaseModel, is not provided - TypeError, ValueError - When there's an error during model instantiation, re-raised as InvalidModelTypeError + InvalidEnvelopeError + When envelope given does not implement BaseEnvelope """ if model is None: @@ -103,23 +103,14 @@ def handler(event: Order, context: LambdaContext): "or as the type hint of `event` in the handler that it wraps", ) - try: - if envelope: - parsed_event = parse(event=event, model=model, envelope=envelope) - else: - parsed_event = parse(event=event, model=model) - - logger.debug(f"Calling handler {handler.__name__}") - return handler(parsed_event, context, **kwargs) - except ValidationError: - # Raise Pydantic validation errors as is - raise - except InvalidModelTypeError: - # Raise invalid model type errors as is - raise - except (TypeError, ValueError) as exc: - # Catch type and value errors that might occur during model instantiation - raise InvalidModelTypeError(f"Error: {str(exc)}. Please ensure the type you're trying to parse into is correct") + if envelope: + parsed_event = parse(event=event, model=model, envelope=envelope) + else: + parsed_event = parse(event=event, model=model) + + logger.debug(f"Calling handler {handler.__name__}") + return handler(parsed_event, context, **kwargs) + @overload def parse(event: dict[str, Any], model: type[T]) -> T: ... # pragma: no cover @@ -180,8 +171,8 @@ def handler(event: Order, context: LambdaContext): When input event does not conform with model provided InvalidModelTypeError When model given does not implement BaseModel - TypeError, ValueError - When there's an error during model instantiation, re-raised as InvalidModelTypeError + InvalidEnvelopeError + When envelope given does not implement BaseEnvelope """ if envelope and callable(envelope): try: @@ -200,19 +191,15 @@ def handler(event: Order, context: LambdaContext): logger.debug("Parsing and validating event model; no envelope used") return _parse_and_validate_event(data=event, adapter=adapter) - - except ValidationError: - # Raise validation errors as is - raise - + # Pydantic raises PydanticSchemaGenerationError when the model is not a Pydantic model # This is seen in the tests where we pass a non-Pydantic model type to the parser or # when we pass a data structure that does not match the model (trying to parse a true/false/etc into a model) except PydanticSchemaGenerationError as exc: raise InvalidModelTypeError(f"The event supplied is unable to be validated into {type(model)}") from exc - except (TypeError, ValueError) as exc: + except AttributeError as exc: raise InvalidModelTypeError( f"Error: {str(exc)}. Please ensure the Input model inherits from BaseModel,\n" "and your payload adheres to the specified Input model structure.\n" f"Model={model}", - ) from exc + ) \ No newline at end of file From 684b651639218286d1b28f51fb055bb3ad336c2e Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Fri, 20 Dec 2024 09:08:27 -0300 Subject: [PATCH 12/13] revert from the original file --- .../utilities/parser/parser.py | 2 +- tests/functional/parser/test_parser.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/parser.py b/aws_lambda_powertools/utilities/parser/parser.py index 7c1557d43e8..7aa7531edb9 100644 --- a/aws_lambda_powertools/utilities/parser/parser.py +++ b/aws_lambda_powertools/utilities/parser/parser.py @@ -202,4 +202,4 @@ def handler(event: Order, context: LambdaContext): f"Error: {str(exc)}. Please ensure the Input model inherits from BaseModel,\n" "and your payload adheres to the specified Input model structure.\n" f"Model={model}", - ) \ No newline at end of file + ) from exc \ No newline at end of file diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index 14e1ee41a57..db09e4b10ed 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -4,6 +4,7 @@ import pydantic import pytest +from typing_extensions import Annotated from pydantic import ValidationError, BaseModel from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse @@ -251,19 +252,18 @@ class SuccessfulCallback(pydantic.BaseModel): class FailedCallback(pydantic.BaseModel): status: Literal["failed"] error: str + + DogCallback = Annotated[Union[SuccessfulCallback, FailedCallback], pydantic.Field(discriminator="status")] + DogCallbackTypeAdapter = pydantic.TypeAdapter(DogCallback) - class DogCallbackModel(pydantic.BaseModel): - data: Union[SuccessfulCallback, FailedCallback] = pydantic.Field(discriminator="status") - - @event_parser(model=DogCallbackModel) + @event_parser(model=DogCallbackTypeAdapter) def handler(event, _: Any) -> str: - if isinstance(event.data, FailedCallback): - return f"Uh oh. Had a problem: {event.data.error}" + if isinstance(event, FailedCallback): + return f"Uh oh. Had a problem: {event.error}" - return f"Successfully retrieved {event.data.breed} named {event.data.name}" + return f"Successfully retrieved {event.breed} named {event.name}" - wrapped_input = {"data": test_input} - ret = handler(wrapped_input, None) + ret = handler(test_input, None) assert ret == expected From d3c63ca9dff4ac111c0b8d00cb8ccf4a276b4ad1 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Fri, 20 Dec 2024 11:23:58 -0300 Subject: [PATCH 13/13] revert another test --- tests/functional/parser/test_parser.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/functional/parser/test_parser.py b/tests/functional/parser/test_parser.py index db09e4b10ed..1b4a5770b28 100644 --- a/tests/functional/parser/test_parser.py +++ b/tests/functional/parser/test_parser.py @@ -216,18 +216,16 @@ class FailedCallback(pydantic.BaseModel): status: Literal["failed"] error: str - class DogCallback(pydantic.BaseModel): - root: Union[SuccessfulCallback, FailedCallback] = pydantic.Field(discriminator="status") + DogCallback = Annotated[Union[SuccessfulCallback, FailedCallback], pydantic.Field(discriminator="status")] @event_parser(model=DogCallback) def handler(event, _: Any) -> str: - if isinstance(event.root, FailedCallback): - return f"Uh oh. Had a problem: {event.root.error}" + if isinstance(event, FailedCallback): + return f"Uh oh. Had a problem: {event.error}" - return f"Successfully retrieved {event.root.breed} named {event.root.name}" + return f"Successfully retrieved {event.breed} named {event.name}" - wrapped_input = {"root": test_input} - ret = handler(wrapped_input, None) + ret = handler(test_input, None) assert ret == expected @pytest.mark.parametrize( @@ -252,7 +250,7 @@ class SuccessfulCallback(pydantic.BaseModel): class FailedCallback(pydantic.BaseModel): status: Literal["failed"] error: str - + DogCallback = Annotated[Union[SuccessfulCallback, FailedCallback], pydantic.Field(discriminator="status")] DogCallbackTypeAdapter = pydantic.TypeAdapter(DogCallback)