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

Type Check Function Invocations with **Kwargs #8485

Closed
stevenjackson121 opened this issue Mar 3, 2020 · 3 comments
Closed

Type Check Function Invocations with **Kwargs #8485

stevenjackson121 opened this issue Mar 3, 2020 · 3 comments

Comments

@stevenjackson121
Copy link

stevenjackson121 commented Mar 3, 2020

This seems superficially related to other kwargs issues, but from what I've read those mostly related to annotating a function so that its signature matches another function. I want to annotate a dictionary so that it's type is whatever is allowed as kwargs to a particular function (or even better, if mypy can simply handle the case where a dictionary d is created and then immediately passed into a function with ** with nothing in between).

We recently refactored

import boto3
from mypy_boto3.lambda_ import LambdaClient
import logging

lambda_client: LambdaClient = boto3.client("lambda")

try:
    lambda_client.invoke(FunctionName="Foobar", InvocationType="RequestResponse", Payload=b"")
except:
   logging.error(f"Failed to invoke lambda")

to

import boto3
from mypy_boto3.lambda_ import LambdaClient
import logging

lambda_client: LambdaClient = boto3.client("lambda")

params = {
    "FunctionName": "foobar",
    "InvocationType": "RequestResponse",
    "Payload": b"",
}
try:
    lambda_client.invoke(**params)
except:
   logging.error(f"Failed to invoke lambda with parameters {params}")

In order to improve our log messages without being redundant. We were surprised that mypy is no longer useful for checking that the arguments we pass in match the parameters that LambdaClient.invoke expects.

Is there a (non-redundant) way to type hint params such that mypy doesn't give me errors, but if a key is added to params dict which would be illegal to pass to LambdaClient.invoke then it is caught by mypy? I assume that if its possible at all, I would need something like TypeOf[LambdaClient.invoke].

Currently I get the following output from mypy post-refactor:

mypy mypy_test.py 
mypy_test.py:13: error: Argument 1 to "invoke" of "LambdaClient" has incompatible type "**Dict[str, str]"; expected "Union[Literal['Event'], Literal['RequestResponse'], Literal['DryRun'], None]"
mypy_test.py:13: error: Argument 1 to "invoke" of "LambdaClient" has incompatible type "**Dict[str, str]"; expected "Union[Literal['None'], Literal['Tail'], None]"
mypy_test.py:13: error: Argument 1 to "invoke" of "LambdaClient" has incompatible type "**Dict[str, str]"; expected "Union[bytes, IO[Any], None]"
Found 3 errors in 1 file (checked 1 source file)

To get mypy to validate the call to invoke I need to do something very redundant like:

import boto3
from mypy_boto3.lambda_ import LambdaClient
import logging

from typing_extensions import TypedDict, Literal

lambda_client: LambdaClient = boto3.client("lambda")

ParamsType = TypedDict(
    "ParamsType",
    {
        "FunctionName": str,
        "InvocationType": Literal["RequestResponse"],
        "Payload": bytes,
    },
)
params: ParamsType = {
    "FunctionName": "foobar",
    "InvocationType": "RequestResponse",
    "Payload": b"",
}
try:
    lambda_client.invoke(**params)
except:
    logging.error(f"Failed to invoke lambda with parameters {params}")

I did also try annotating params with Final to help mypy understand that it would not change between declaration and the invocation, however that did not change the type checking result.

@hauntsaninja
Copy link
Collaborator

Yeah, it's possible mypy could infer the TypedDict type there, on the lines of #5382 (comment)

@stevenjackson121
Copy link
Author

I realized Final is a useless annotation. It means variable will not be rebound, but doesn't indicate the dictionary won't be updated.

I tried using frozendict but that just complained about not finding stubs. I also tried annotating with TypedDict directly (hoping for some inference) but mypy claims that's not legal either.

@ilevkivskyi
Copy link
Member

This is essentially a duplicate of #5382

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