Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: APIGatewayProxyEventV2Model not deserializing str to custom model #1518

Closed
nleipi opened this issue Sep 15, 2022 · 14 comments
Closed

Bug: APIGatewayProxyEventV2Model not deserializing str to custom model #1518

nleipi opened this issue Sep 15, 2022 · 14 comments
Assignees

Comments

@nleipi
Copy link

nleipi commented Sep 15, 2022

Expected Behaviour

It should be possible to use own model classes for body in APIGatewayProxyEventV2Model

Current Behaviour

When I extend APIGatewayProxyEventV2Model model to include my own model for the body following exception is raised 1 validation error for Event\nbody\n value is not a valid dict (type=type_error.dict). I suppose the reason for that is that body of apigw v2 event is a string

Code snippet

from aws_lambda_powertools.utilities.parser import BaseModel
from aws_lambda_powertools.utilities.parser.models import (
    APIGatewayProxyEventV2Model
)


class OrderCancelationRequest(BaseModel):
    reason: str
    orderNo: str


class MyEventModel(APIGatewayProxyEventV2Model):
    body: OrderCancelationRequest

Possible Solution

No response

Steps to Reproduce

parse(model=MyEventModel, event=any valid apigw event)

AWS Lambda Powertools for Python version

latest

AWS Lambda function runtime

3.9

Packaging format used

PyPi

Debugging logs

No response

@nleipi nleipi added bug Something isn't working triage Pending triage from maintainers labels Sep 15, 2022
@boring-cyborg
Copy link

boring-cyborg bot commented Sep 15, 2022

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 AWS Lambda Powertools Discord: Invite link

@ran-isenberg
Copy link
Contributor

hey @nleipi ,
There's no bug, i got it working. There's a slight gotcha here.
The body is defined as a string, and since you extend the model to be a dataclass and dont use the envelope, you need to transform the string into a dict yourself. That's what the envelope does for you.

Here's a sample code that will get it working for you:

from aws_lambda_powertools.utilities.parser import BaseModel
from aws_lambda_powertools.utilities.parser.models import (
    APIGatewayProxyEventV2Model
)
import json
from pydantic import validator

class OrderCancelationRequest(BaseModel):
    reason: str
    orderNo: str


class MyEventModel(APIGatewayProxyEventV2Model):
    body: OrderCancelationRequest

    @validator("body", pre=True)
    def transform_body_to_dict(cls, value: str):
        return json.loads(value)

@leandrodamascena maybe we should include this example in the docs for extending the models that have a non dict base type like this one.

@nleipi
Copy link
Author

nleipi commented Sep 16, 2022

I think the correct way to handle this is to use pydantics own Json type as a wrapper for the model.

class MyEventModel(APIGatewayProxyEventV2Model):
    body: Json[OrderCancelationRequest]

That should be definitely highlighted in the documentation.

@ran-isenberg
Copy link
Contributor

I think the correct way to handle this is to use pydantics own Json type as a wrapper for the model.

class MyEventModel(APIGatewayProxyEventV2Model):
    body: Json[OrderCancelationRequest]

That should be definitely highlighted in the documentation.

Nice! even prettier

@rubenfonseca
Copy link
Contributor

Hi @nleipi, thanks for opening this! As @ran-isenberg mentioned, parser envelopes were created exactly with this use case in mind.

In your case, the code would look something like this:

from aws_lambda_powertools.utilities.parser import event_parser, BaseModel, envelopes
from aws_lambda_powertools.utilities.typing import LambdaContext

class OrderCancelationRequest(BaseModel):
    reason: str
    orderNo: str

@event_parser(model=OrderCancelationRequest, envelope=envelopes.ApiGatewayV2Envelope)
def handler(event: OrderCancelationRequest, context: LambdaContext):
    assert event.reason != ""

What do you think of giving this a try?

@nleipi
Copy link
Author

nleipi commented Sep 16, 2022

I would have used envelope approach, but I need access to pathParamters. Can I access them in other way than through event model?

@ran-isenberg
Copy link
Contributor

In order to get the path params, you must extend the model like you did and not use the envelope.
The APIGatewayProxyEventV2Model defines path params too. You can even change them from dict to a custom class and get even stronger validation.

class MyEventModel(APIGatewayProxyEventV2Model):
    body: OrderCancelationRequest
    pathParameters: MyPathParams

    @validator("body", pre=True)
    def transform_body_to_dict(cls, value: str):
        return json.loads(value)

@nleipi
Copy link
Author

nleipi commented Sep 16, 2022

Stronger validation of pathParameters is a really good point, thanks!

@rubenfonseca
Copy link
Contributor

Thanks! We already have some documentation about extending built-in modules. Do you think people could benefit from this example too?

@ran-isenberg
Copy link
Contributor

The example describes a simple use case where the business payload param is a dict.
Here, it's a string and requires an extra trick.
It makes to sense to add it.

@rubenfonseca rubenfonseca self-assigned this Sep 20, 2022
@rubenfonseca rubenfonseca removed the triage Pending triage from maintainers label Sep 20, 2022
@rubenfonseca
Copy link
Contributor

Thank you so much for your input! I've opened a PR now to add this use case to our parser docs :)

@rubenfonseca
Copy link
Contributor

I think the correct way to handle this is to use pydantics own Json type as a wrapper for the model.

class MyEventModel(APIGatewayProxyEventV2Model):
    body: Json[OrderCancelationRequest]

That should be definitely highlighted in the documentation.

Hi @nleipi @ran-isenberg I was trying out this snippet, but I still got a mypy error on the body field:

error: "str" expects no type arguments, but 1 given [type-arg]

Can you check if it's the same, or if I'm missing something? I'm starting to feel that the only way around this is to use the validator route.

@ran-isenberg
Copy link
Contributor

I think the correct way to handle this is to use pydantics own Json type as a wrapper for the model.

class MyEventModel(APIGatewayProxyEventV2Model):
    body: Json[OrderCancelationRequest]

That should be definitely highlighted in the documentation.

Hi @nleipi @ran-isenberg I was trying out this snippet, but I still got a mypy error on the body field:

error: "str" expects no type arguments, but 1 given [type-arg]

Can you check if it's the same, or if I'm missing something? I'm starting to feel that the only way around this is to use the validator route.

Sorry, I dont use mypy that extensively.
If memory serves right, mypy and pydantic work fine only when you do a union of str, BaseModel etc. I still think the example should be just Json. maybe add a mypy ignore?

@heitorlessa heitorlessa changed the title Bug: APIGatewayProxyEventV2Model extension not working Bug: APIGatewayProxyEventV2Model extension not deserializing str to model Sep 21, 2022
@heitorlessa heitorlessa changed the title Bug: APIGatewayProxyEventV2Model extension not deserializing str to model Bug: APIGatewayProxyEventV2Model not deserializing str to custom model Sep 21, 2022
@rubenfonseca rubenfonseca removed the bug Something isn't working label Sep 22, 2022
@github-actions github-actions bot added the pending-release Fix or implementation already in dev waiting to be released label Sep 23, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Oct 5, 2022

This is now released under 1.30.0 version!

@github-actions github-actions bot closed this as completed Oct 5, 2022
@github-actions github-actions bot removed the pending-release Fix or implementation already in dev waiting to be released label Oct 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants