Skip to content

Commit

Permalink
fix(parser): remove AttributeError validation from event_parser funct…
Browse files Browse the repository at this point in the history
…ion (#5742)

* add pydatinc validationError

* removed unnecessary exc from exceptions 

Signed-off-by: Ana Falcão <[email protected]>

* add two more validation error tests

* add more specific error tests

* remove one specific error test

* fix doc and add more test

* change funct doc and more tests

* remove unused import

* change error message

* remove exceptions not needed

* remove error handling from decorator

* revert from the original file

* revert another test

---------

Signed-off-by: Ana Falcão <[email protected]>
Co-authored-by: Ana Falcao <[email protected]>
Co-authored-by: Leandro Damascena <[email protected]>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent aded1a9 commit a44bd6d
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 17 deletions.
24 changes: 11 additions & 13 deletions aws_lambda_powertools/utilities/parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ 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
When the model given does not implement BaseModel, is not provided
InvalidEnvelopeError
When envelope given does not implement BaseEnvelope
"""
Expand All @@ -103,17 +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 AttributeError as exc:
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
Expand Down Expand Up @@ -192,6 +189,7 @@ def handler(event: Order, context: LambdaContext):
adapter = _retrieve_or_set_model_from_cache(model=model)

logger.debug("Parsing and validating event model; no envelope used")

return _parse_and_validate_event(data=event, adapter=adapter)

# Pydantic raises PydanticSchemaGenerationError when the model is not a Pydantic model
Expand All @@ -204,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}",
) from exc
) from exc
75 changes: 71 additions & 4 deletions tests/functional/parser/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import pydantic
import pytest
from pydantic import ValidationError
from typing_extensions import Annotated
from pydantic import ValidationError, BaseModel

from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse
from aws_lambda_powertools.utilities.parser.envelopes.sqs import SqsEnvelope
Expand Down Expand Up @@ -130,6 +130,68 @@ 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())


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)


@pytest.mark.parametrize(
"test_input,expected",
Expand All @@ -138,7 +200,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):
Expand All @@ -163,15 +228,17 @@ def handler(event, _: Any) -> str:
ret = handler(test_input, None)
assert ret == expected


@pytest.mark.parametrize(
"test_input,expected",
[
(
{"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):
Expand Down

0 comments on commit a44bd6d

Please sign in to comment.