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

Allow extended parser models to pass mypy #849

Closed
Tracked by #1009
ClaytonJY opened this issue Nov 27, 2021 · 14 comments
Closed
Tracked by #1009

Allow extended parser models to pass mypy #849

ClaytonJY opened this issue Nov 27, 2021 · 14 comments
Labels
bug Something isn't working p1

Comments

@ClaytonJY
Copy link

ClaytonJY commented Nov 27, 2021

Is your feature request related to a problem? Please describe.

The docs example for extending parser models (https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parser/#extending-built-in-models) doesn't pass mypy, because the Order class is not compatible with the EventBridgeModel annotation detail: Dict[str, Any].

error: Incompatible types in assignment (expression has type "Order", base class "EventBridgeModel" defined the type as "Dict[str, Any]")

I would like to be able to both extend this model and have my code pass type checks without # type: ignore or similar.

I suspect this affects other parser models, like the Message field of SnsNotificationModel.

Describe the solution you'd like

One solution is to change only the detail type annotation, e.g. Union[Dict[str, Any], BaseModel], but there may be other things to add to that list and it doesn't feel great; there could be many other types that would parse fine and could be listed there, right? Or is that it, because you either use EventBridgeModel directly (Dict) or you subclass it (BaseModel)?

Is there anything that could be done with typing.Protocol, to check the class has certain methods regardless of name?

There might also be an argument to not change the type, but note in the docs that this will fail type-checks and instruct how to silence mypy in such instances.

Describe alternatives you've considered

I tried various solutions which didn't work, like adding dict or TypedDict to the parent class list for Order (incompatible methods), or changing it to TypedDict (same error as above).

Additional context

I'm using the pydantic.mypy extension.

@ClaytonJY ClaytonJY added feature-request feature request triage Pending triage from maintainers labels Nov 27, 2021
@boring-cyborg
Copy link

boring-cyborg bot commented Nov 27, 2021

Thanks for opening your first issue here! We'll come back to you as soon as we can.

@heitorlessa
Copy link
Contributor

hey @ClaytonJY thanks a lot for flagging this. I'm not entirely sure how to solve this yet, we'd have to maybe experiment with a generic type bound to BaseModel.

cc @ran-isenberg in case he knows as this should be a fairly common problem in Pydantic world

@heitorlessa heitorlessa added help wanted Could use a second pair of eyes/hands and removed triage Pending triage from maintainers labels Nov 29, 2021
@ran-isenberg
Copy link
Contributor

I didn't encounter this issue yet to be honest.

@ClaytonJY
Copy link
Author

FWIW I'm a very new user of this package, and since posting this have come to avoid the issue a different way: use the envelope and never bother subclassing that model. Still, given the benefits of this package around typing, it would be nice if examples in the docs like this were type compliant.

Happy to help, but may need to be pointed in the right direction. Very curious to see where this discussion goes, and thank you for a package that has cut at least half of my code just a few hours after discovering it 🙏

@ran-isenberg
Copy link
Contributor

ran-isenberg commented Dec 5, 2021

FWIW I'm a very new user of this package, and since posting this have come to avoid the issue a different way: use the envelope and never bother subclassing that model. Still, given the benefits of this package around typing, it would be nice if examples in the docs like this were type compliant.

Happy to help, but may need to be pointed in the right direction. Very curious to see where this discussion goes, and thank you for a package that has cut at least half of my code just a few hours after discovering it 🙏

Subclassing has a lot of advantages -if you require both metadata and business logic (body/details) attributes.

I guess mypy doesnt like that we change the type of the baseclass attribute. I'm not sure how you can avoid that, that's the whole point of the parsing trick we do there.

Union[Dict[str, Any], BaseModel] is fairly accurate but I'm afraid it might break something because I do want it to parse it a Dict for the regular envelope/model extraction flows.

One "ugly" solution is to add # type: ignore to the relevant files.

BTW, I use pylint, and it's was fine with the correct definition.

@heitorlessa
Copy link
Contributor

heitorlessa commented Dec 5, 2021 via email

@heitorlessa
Copy link
Contributor

heitorlessa commented Dec 5, 2021 via email

@ClaytonJY
Copy link
Author

@heitorlessa I no longer have access to that exact project where I discovered this, but I'm fairly certain the only mypy config I had set was using the pydantic plugin. I may have fed some command-line args to mypy like ignoring un-annotated imports. I'm quite certain I wasn't doing anything exotic!

I could whip up a minimal reprex if needed, would that help?

@heitorlessa
Copy link
Contributor

heitorlessa commented Dec 6, 2021 via email

@ClaytonJY
Copy link
Author

@heitorlessa here's that reprex, as a new repo: https://github.com/ClaytonJY/powertools-type-error-example

thanks for caring!

@heitorlessa
Copy link
Contributor

Looking at this today @ClaytonJY thank you so much again for creating the repro, it makes a lot easier for us :)

@heitorlessa
Copy link
Contributor

heitorlessa commented Dec 10, 2021

As expected, a generic type bound to BaseModel in an Union makes MyPy happy. If the actual payload is passed and no override happens, it correctly detects detail as a dictionary.

It'd be impractical to allow any other field to be overridden by type, so I'll go ahead and ensure all built-in Models can be overridden on fields where the payload is (e.g. detail in EventBridge, body in API Gateway, etc.)


# Model = TypeVar("Model", bound=BaseModel)

from datetime import datetime
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Field

from aws_lambda_powertools.utilities.parser.types import Model


class EventBridgeModel(BaseModel):
    version: str
    id: str  # noqa: A003,VNE003
    source: str
    account: str
    time: datetime
    region: str
    resources: List[str]
    detail_type: str = Field(None, alias="detail-type")
    detail: Union[Dict[str, Any], Model]
    replay_name: Optional[str] = Field(None, alias="replay-name")

@heitorlessa
Copy link
Contributor

PR available - should be available in the next release: #883

@heitorlessa heitorlessa added p1 bug Something isn't working pending-release Fix or implementation already in dev waiting to be released and removed help wanted Could use a second pair of eyes/hands feature-request feature request labels Dec 10, 2021
@heitorlessa
Copy link
Contributor

Now released as part of 1.23 version

@heitorlessa heitorlessa removed the pending-release Fix or implementation already in dev waiting to be released label Dec 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working p1
Projects
None yet
Development

No branches or pull requests

3 participants