Code Monkey home page Code Monkey logo

Comments (7)

leandrodamascena avatar leandrodamascena commented on August 9, 2024 1

Hi @heitorlessa and @aitchnyu, after investigating further I discovered that the issue goes beyond fixing the RequestValidationError or not. Our exception_handler function captures a list of exceptions passed to it, however, it is important to note that most exceptions in Python inherit from Exception (some from BaseException). So if we set exception_handler to catch Exception, it will also catch exceptions like ValueError, KeyError, TypeError, RequestValidationError, and others.

Ideally, it's advisable to catch more specific exceptions, especially if you want to generate custom messages for specific error types like RequestValidationError. I'm not sure if there's a straightforward solution at the moment, I'll need to investigate further. One potential action arising from this issue is to update our documentation to reflect this behavior, where catching Exception acts as a catch-all mechanism for all exceptions.

code

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.event_handler.api_gateway import Response


tracer = Tracer()
logger = Logger()
app = APIGatewayRestResolver()


@app.get("/hello")
def get_todos():
    raise ValueError

@app.exception_handler(Exception)
def handle_invalid_limit_qs(ex: Exception):  # receives exception raised

    return Response(
        status_code=400,
        body="Catching Exception, but raising ValueError",
    )

@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
    return app.resolve(event, context)

execution

sam local invoke --event events/event.json --skip-pull-image
Invoking app.lambda_handler (python3.11)                                                                                                                                                                                                                                                                                 
Requested to skip pulling images ...                                                                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                                                                         
Mounting /home/leandro/DEVEL-PYTHON/tmp/exception-different-router/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container                                                                                                                                                                 
START RequestId: e408630c-0660-4d24-a3c5-d6e590f6d959 Version: $LATEST
START RequestId: e408630c-0660-4d24-a3c5-d6e590f6d959 Version: $LATEST
END RequestId: e408630c-0660-4d24-a3c5-d6e590f6d959
REPORT RequestId: e408630c-0660-4d24-a3c5-d6e590f6d959	Init Duration: 0.11 ms	Duration: 407.67 ms	Billed Duration: 408 ms	Memory Size: 128 MB	Max Memory Used: 128 MB	
{"statusCode": 400, "body": "Catching Exception, but raising ValueError ", "isBase64Encoded": false, "multiValueHeaders": {}}

I'm adding labels such as “need help” and “revisit” to keep this issue on our radar.

Thank you

from powertools-lambda-python.

boring-cyborg avatar boring-cyborg commented on August 9, 2024

Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

from powertools-lambda-python.

heitorlessa avatar heitorlessa commented on August 9, 2024

hey @aitchnyu - thank you for submitting an extensive bug report!! It should work with RequestValidationError exception.

I'll investigate the behaviour today when catching a catch-all Exception like you did, and come back to you.

As per docs, catching RequestValidatioError should work as you want; tested locally too.

from typing import Optional

import requests
from pydantic import BaseModel, Field

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext

tracer = Tracer()
logger = Logger()
app = APIGatewayRestResolver(enable_validation=True)


class Todo(BaseModel):
    userId: int
    id_: Optional[int] = Field(alias="id", default=None)
    title: str
    completed: bool


@app.exception_handler(RequestValidationError)  
def handle_validation_error(ex: RequestValidationError):
    logger.error("Request failed validation", path=app.current_event.path, errors=ex.errors())

    return Response(
        status_code=422,
        content_type=content_types.APPLICATION_JSON,
        body="Invalid data",
    )


@app.post("/todos")
def create_todo(todo: Todo) -> int:
    response = requests.post("https://jsonplaceholder.typicode.com/todos", json=todo.dict(by_alias=True))
    response.raise_for_status()

    return response.json()["id"]


@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> dict:
    return app.resolve(event, context)
image

from powertools-lambda-python.

heitorlessa avatar heitorlessa commented on August 9, 2024

Can't reproduce with either general Exception and the suggested RequestValidationException -- need more details.

Example below on how to get those exact details what the documentation suggests with RequestValidationError.

from typing import Annotated, Optional

import requests
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import (
    APIGatewayRestResolver,
    Response,
    content_types,
)
from aws_lambda_powertools.event_handler.openapi.exceptions import (
    RequestValidationError,
)
from aws_lambda_powertools.event_handler.openapi.params import Path, Query
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.parser import Field, BaseModel


tracer = Tracer()
logger = Logger()
app = APIGatewayRestResolver(enable_validation=True)
app.enable_swagger(path="/swagger")


class Todo(BaseModel):
    userId: int
    id_: Optional[int] = Field(alias="id", default=None)
    title: str
    completed: bool


@app.exception_handler(RequestValidationError)
def catch_validation(ex: RequestValidationError):
    logger.info("Catch all exception handler", error=ex)

    err = ex.errors()[0]
    location, message, type = err["loc"], err["msg"], err["type"]

    return Response(
        status_code=400,
        content_type=content_types.TEXT_PLAIN,
        body=f"Uh oh! Received an exception",
    )


@app.get("/todos")
@tracer.capture_method
def get_todos(
    completed: Annotated[str | None, Query(min_length=4)] = None
) -> list[Todo]:
    url = "https://jsonplaceholder.typicode.com/todos"

    if completed is not None:
        url = f"{url}/?completed={completed}"

    todo = requests.get(url)
    todo.raise_for_status()

    return todo.json()


@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> dict:
    return app.resolve(event, context)

from powertools-lambda-python.

aitchnyu avatar aitchnyu commented on August 9, 2024

My workaround is having handlers for both RequestValidationError and Exception. If I were to disable the RequestValidationError handler, the Exception would handle it (against my expectations).

app = APIGatewayRestResolver(enable_validation=True)
...

@app.exception_handler(Exception)
def handle_exception(ex: Exception):  # pragma: no cover
    ...
    return Response(
        status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
        content_type=content_types.APPLICATION_JSON,
        body=json.dumps({"message": "internal server error"}),
    )


@app.exception_handler(RequestValidationError)
def handle_request_validation_error(ex: RequestValidationError):
    # Copy of ApiGatewayResolver _call_exception_handler
    errors = [{"loc": e["loc"], "type": e["type"]} for e in ex.errors()]
    return Response(
        status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
        content_type=content_types.APPLICATION_JSON,
        body={"statusCode": HTTPStatus.UNPROCESSABLE_ENTITY, "detail": errors},
    )

from powertools-lambda-python.

leandrodamascena avatar leandrodamascena commented on August 9, 2024

Hey @heitorlessa and @aitchnyu!

After a long investigation, we have concluded that this is a fundamental aspect of how Python handles exceptions. In Python, most exceptions inherit from the base class Exception, which means that catching Exception will capture a wide range of exception types, including ValueError, KeyError, TypeError, and RequestValidationError. This behavior is an integral part of the Python language design. Modifying how Powertools for AWS Lambda handles and catches exceptions is not a viable solution at this time, and even if we attempted to change it, we have no idea of the potential side effects.

For cases like this, the recommended approach is to catch more specific exceptions, to better handle and respond to different error types.

I'm closing this issue and please reopen if you find a solution or have an idea how to address this.

Thanks

from powertools-lambda-python.

github-actions avatar github-actions commented on August 9, 2024

⚠️COMMENT VISIBILITY WARNING⚠️

This issue is now closed. Please be mindful that future comments are hard for our team to see.

If you need more assistance, please either tag a team member or open a new issue that references this one.

If you wish to keep having a conversation with other community members under this issue feel free to do so.

from powertools-lambda-python.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.