fam
fam

Reputation: 93

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.

Upvotes: 4

Views: 3813

Answers (1)

STerliakov
STerliakov

Reputation: 7943

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?

  • object is a "just something" type - such type that you know almost nothing about it. You can't add or multiply objects or pass them to functions requiring some specific types, but can e.g. print it. I'd argue that this solution is best for your use case, because you don't do anything with the dict afterwards.
  • Possible TypedDict is a runner-up here: if this dict defines some important structure that you heavily use in processing later, you'd better say explicitly what each key means.
  • Any means "leave me alone, I know what I'm doing". It'd probably be a good solution if you use this dict somehow later - object will require a lot of type narrowing to work with, and Any will just work.
  • Union solution is the worst, unless your keys are really dynamic (no semantic meaning). Type checker will shout at you whenever you try to use a value as either of union types - it can always be of another type. (this is not a general advice: dict[str, int | str] may be a great type for something where keys do not represent a structure like JS object)

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.

Upvotes: 4

Related Questions