facebook / pyre-check

Performant type-checking for python.
https://pyre-check.org/
MIT License
6.85k stars 437 forks source link

[Enhancement] Kwargs TypeChecking Fails When Using **dict #242

Closed stevenjackson121 closed 2 weeks ago

stevenjackson121 commented 4 years ago

MyPy doesn't handle this either, but it might be a smaller lift to add support in pyre because (superficially anyways) it seems related to ParameterSpecification and https://www.python.org/dev/peps/pep-0612/.

The issue is that there's no way to annotate the params dictonary such that errors will be raised on line 19 (invoke(**params)) if and only if they'd be raised on line 11 (invoke(FunctionName=<...>)

from typing import Mapping

import boto3
from mypy_boto3.lambda_ import LambdaClient
import logging

from typing_extensions import TypedDict, Literal, Final

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

lambda_client.invoke(FunctionName="foobar", InvocationType="RequestResponse", Payload=b"")

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

I know ParameterSpecification is currently only usable in function definitions, but it would be wonderful to either broaden the scope slightly or to have a related ArgumentSpecification that borrows heavily on the design and would enable syntax like InvokeArgs = ParameterSpecification("InvokeArgs", lambda_client.invoke) which could then be used like:

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

This may be overkill for the situation where you're only handling kwargs in a dictionary (a workaround like I asked for in mypy might be easier, which is using a Final[frozendict] annotation to let the typechecker know the dictionary will not change at all before being used as kwargs and some special case rules), but I think a solution patterned after ParameterSpecification might be cleaner for related use cases.

For example, python now has positional only arguments. If we needed to pass in both positional and keyword arguments, we could use something like:

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

I'll admit I didn't fully understand the ParameterSpecification Pep, although I did try to read all of it. They seem related to me as a user, but I know that doesn't necessarily mean that it would be a related implementation.

MaggieMoss commented 4 years ago

cc: @mrkmndz do you have some context here?

yangdanny97 commented 2 weeks ago

Precise typechecking for heterogeneous kwargs should be easier with PEP 692 (https://peps.python.org/pep-0692/) which was recently added to Pyre and will be available in the next release.

But for the first example with a regular dictionary, Pyre will typecheck it correctly if you use the actual literal inside the **.

For example

 lambda_client.invoke(**{
    "FunctionName": "foobar",
    "InvocationType": "RequestResponse",
    "Payload": b"",
})

If the issue persists after the next release, please reopen