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.
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.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.