pythonfastapipython-typingpyright

No overloads for "update" match the provided arguments


I'm currently reading FastAPI's tutorial user guide and pylance is throwing the following warning:

No overloads for "update" match the provided argumentsPylancereportCallIssue
typing.pyi(690, 9): Overload 2 is the closest match

Here is the code that is throwing the warning:

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(q: str | None = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q}) # warning here
    return results

I tried changing Python › Analysis: Type Checking Mode to basic and using the Pre-Release version of Pylance but warning persists.


Solution

  • You're bitten by intelligent type inference. To make it clear, I'll get rid of FastAPI dependency and reproduce on a simpler case:

    foo = {"foo": ["bar"]}
    foo["bar"] = "baz"
    

    Whoops, mypy (or pylance) error. I don't have Pylance available anywhere nearby, so let's stick with mypy - the issue is the same, wording may differ.

    E: Incompatible types in assignment (expression has type "str", target has type "list[str]") [assignment]

    Now it should be clear: foo is inferred as dict[str, list[str]] on first assignment. We can add reveal_type to confirm, here's the playground. Now type checker rightfully complains: you're trying to set a str as a value in dict with value type of list[str]. Bad idea.

    So, what now? You need to tell typechecker that you don't want inference to be this precise. You have few options (any of the following will work):

    from typing import Any, TypedDict, NotRequired
    
    class Possible(TypedDict):
        foo: list[str]
        bar: NotRequired[str]
    
    foo: dict[str, Any] = {"foo": ["bar"]}
    foo: dict[str, object] = {"foo": ["bar"]}
    foo: dict[str, list[str] | str] = {"foo": ["bar"]}
    foo: Possible = {"foo": ["bar"]}
    

    What's the difference?

    So, in your specific case

    @app.get("/items/")
    async def read_items(q: str | None = None):
        results: dict[str, object] = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
        if q:
            results.update({"q": q}) # warning here
        return results
    

    should pass.

    Since you and other answerers got misled by overload, here's how MutableMapping.update (dict subclasses MutableMapping in stubs) looks in typeshed:

    class MutableMapping(Mapping[_KT, _VT]):
        ...  # More methods
        
        @overload
        def update(self, __m: SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ...
        @overload
        def update(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
        @overload
        def update(self, **kwargs: _VT) -> None: ...
    

    I have no idea why "Overload 2 is the closest match", according to Pylance, but you should hit the first one, of course.