You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Warn about unused Awaitables (and potentially other objects) #2499
is incorrect because f() isn't awaited. This will produce a runtime warning with the right debug settings, but mypy doesn't give any errors.
It would be nice if mypy could catch this, since I think it's a pretty common error in async code. This could be generalized to warning on other types or function return values that shouldn't be ignored (maybe open?), similar to __attribute__((warn_unused_result)) in GCC.
Editing in a 2025 update to the OP
Try --enable-error-code unused-awaitable --enable-error-code truthy-bool and see if that catches the thing you want it to catch
Reacted by Alanna Stone, James Ferguson, Mathieu PATUREL, Tuomas Suutari, Nadav Zingerman, Alexey, Lenny, Serge Antipov, Sam Bull, Steven DeMartini and 19 more
I'm pretty sure we've got another similar issue somewhere, but I can't find it. (About results that should never be discarded -- perhaps uncalled functions?)
I'm not sure how easy this would be to do -- especially not the generalization, which probably would require us to invent some acceptable syntax for marking functions whose result should never be ignored.
I think skipping generalization and focusing on Awaitable first would still catch bugs. Once there's a second case people want to specialize it'll be easier to see what needs to be made general
Awaitable is a bit special too, because "is the value used" isn't quite the right check. For example, here's one of the cpython bots that had a missing await:
The code was something like f"Sorry, {get_participants()}, ...", but should have been f"Sorry, {await get_participants()}, ...". So the awaitable value was used, and was even used in a type-valid way (pretty much any python object can be interpolated into an f-string), but clearly not what was intended. On the other hand, in asyncio, passing an awaitable into a function like asyncio.gather is a totally normal way to consume it.
I was looking at this recently in the context of Trio, where the rules for using awaitables are much simpler: basically just, any call returning an awaitable should have an await present, full stop. This is obviously too simple to become a general always-on feature in mypy, since it won't work for asyncio users, but it'd be nice to have it as an optional feature or a plugin or something. I made some more detailed notes here on what exactly we'd want and how it might work: python-trio/trio#671
Is it possible to have a mypy plugin that adds a check like this? And can anyone point me at roughly where in the type checking process we'd want to be to perform a check like that? (I assume somewhere in mypy/checker.py?)
Is it possible to have a mypy plugin that adds a check like this?
I am not sure writing it as a plugin is the best solution. This will need addition of at least one new plugin hook. I think a better idea is to just add a flag to mypy. This looks like a common scenario. Also the implementation in mypy is very simple. We can add a flag is_awaited on CallExpr that will be populated during semantic analysis (by enclosing visit_await). An then an error can be given in visit_call_expr in checkexpr.py if the inferred resulting type is Awaitable, but the is_awaited flag is not set.
That would probably be the easiest approach, yeah. But... this check really is only useful to projects using trio, not to projects using asyncio. Is mypy interested in carrying features like that?
I don't like the idea of just adding another opt-in warning for this. I'd much prefer if we could have something that is enabled by default, even if it doesn't catch all errors. There's a risk that the majority of users would not realize that such an option exists.
What about always rejecting code such as the original example, as the first iteration of this feature:
asyncdeff() ->None:
passasyncdefg() ->None:
f() # Should be an error
Is there ever a good reason to write f() without doing anything with the result? This wouldn't reject things like x = f(), only bare f(). x = f() would likely generate an error already if the code does anything with x later on.
We could later warn about things like "{}".format(should_be_awaited()) -- again, this is something that would hardly ever be right. We could special case some common operations that work with arbitrary objects, such as str.format, % formatting, str(x) and f-strings. We could extend this to check for things like "%s" % foo.method as well (i.e., forgot to call a method).
Later, we could consider adding a trio-specific stricter check (possible as a plugin).
I'd much prefer if we could have something that is enabled by default, even if it doesn't catch all errors. There's a risk that the majority of users would not realize that such an option exists.
In Trio's case, we'd probably make it enabled by default in our official project template and advertise it prominently in the docs (we already have a whole discussion of this issue right in the tutorial). But yeah, I know what you mean.
Is there ever a good reason to write f() without doing anything with the result?
If f returns a coroutine object, then you're correct, a bare f() is always a mistake. If f() returns a Future, then a bare f() is a relatively normal thing to write. So I guess a check like this keyed off Coroutine rather than Awaitable would make sense.
We could later [...] later [...]
I hear where you're coming from. But, it is frustrating that mypy has all the information to do a very straightforward, precise, and high-coverage check here, but it's blocked because we can't get at it :-/. Would there be any simple, acceptable change to the plugin interface that would make it possible to implement this as a plugin relatively soon?
It looks to me like the two main blockers are:
You can define hooks that run on function and method calls, but any given function/method can only have 1 such hook. Here we want a hook that runs on all functions+methods, so we can do that, but if we do then it will effectively disable any other plugins that want to use those hooks. Maybe there could be some way to run multiple hooks in sequence? For the function and method hooks in particular it doesn't look like this would be terribly complicated semantically. This does create some issue about what order to run hooks in. (Ideally the Awaitable check would run last, I guess, so it can see the inferred ret_type after all other hooks have run?) But in practice it may not matter much. For this plugin, the only time it would matter would be if another plugin changed a ret_type from Awaitable to non-Awaitable, or vice-versa, and that seems rare.
There's no way for the hook to tell whether a given call is the immediate child of an await node. I guess this could be hacked into the Context?
Would there be any simple, acceptable change to the plugin interface that would make it possible to implement this as a plugin relatively soon?
Small plugin API changes are typically not controversial, so this is a possibility.
Maybe there could be some way to run multiple hooks in sequence?
That would be one way to do it, but ordering issues would make this a bit awkward, as you point out. Another option would to add a new "check only" hook that is called after the signature hooks have already run and we have a final signature. The hook wouldn't be able to change the signature but it can perform additional checks, and it's guaranteed to run after the signature-related hooks. We could allow multiple plugins to define these hooks for a function.
There's no way for the hook to tell whether a given call is the immediate child of an await node. I guess this could be hacked into the Context?
We'd need another attribute in the relevant context named tuple, I guess.
Can you create a new issue for the plugin feature? We can then focus on the non-trio/non-plugin aspects in this issue.
* `--enforce-await=local` (easy to implement) will complain if a coroutine is not _immediately_ awaited.
What about a local check, with the added assumption that a function expecting an Awaitable is likely to await on it? Plus a tunable unknown state when not enough typing information is available (parameter is Any).
This would automatically make functions like asyncio.gather() work without needing to special case them. I'm imagining that cases where a function expects an Awaitable and then doesn't await on it are going to be very rare (plus if they are within the user's code it would still highlight an error within that function).
For example:
async def do_something(): ...
async def func_with_await(foo: Awaitable):
return foo # Error: foo not awaited
async def unknown_func(foo: Any):
print(foo)
async def main():
# These start as awaited=False
a = do_something()
b = do_something()
c = do_something()
d = do_something()
e = do_something()
await func_with_await(a) # a.awaited=True
asyncio.create_task(b) # b.awaited=True
await unknown_func(c) # c.awaited=Unknown
print(d) # d.awaited=False
# Error: e.awaited=False
Whenever the awaited state is False it should error, and if True it should not error. When it is in the unknown state, this should error depending on a tunable flag (similar to the various Any warnings that are configurable).
Is there likely to be any movement on this? I've been hitting this problem a lot lately, trying to refactor a bunch of sync code to be async and struggling to find every location that needs an await.
anyio has the concept of DeprecatedAwaitables that warn when you await them - they are list, float and Noneish subclasses otherwise. Is the advice to do this?
I think trying to find whether all possible control flows await an awaitable is difficult for a type checker. How does it know if I pass it to a function whether that function will await it or not?
The statement that passing it to a function is likely to await it is not true. For example appending to a list
I think trying to find whether all possible control flows await an awaitable is difficult for a type checker. How does it know if I pass it to a function whether that function will await it or not?
The statement that passing it to a function is likely to await it is not true. For example appending to a list
How frequently do you think a function will explicitly declare the type as Awaitable and then not await on it (either directly or through another function)? My assumption is that this will be very rare in real world use cases, likewise a typesafe function that doesn't accept an Awaitable (e.g. object) is very unlikely to await on it. If this works in real world use cases 99% of the time, then it's definitely worth putting in.
If a function accepts Any then we have no idea if it will await or not, so having a strictness setting to warn or not in these cases is helpful. We can default to not warning to avoid false positives, but people looking for something stricter/safer can enable warnings in this case (see the different examples in my comment).
That just handles unused awaitable expressions. Something as simple as a = foo() won't trigger the warning.
I think we still need to have something like #2499 (comment) to cover a lot more cases. It's a bit more complex as you need to track the awaited state through a function in order to see if it was awaited by the end, but would catch a lot more mistakes.
a = foo() where a remains unused should be covered by flake8 or other linters already under the unused variable umbrella (granted, maybe it'd be nice to have it in Mypy as well)
a = foo() where a remains unused should be covered by flake8 or other linters already under the unused variable umbrella (granted, maybe it'd be nice to have it in Mypy as well)
That only works if the variable is unused. You can then do print(a) or similar and flake8 will no longer catch it as unused and mypy will not catch it as unawaited.
My reasoning behind this request is to make delayed calls an active decision and to prevent errors from forgotten awaits.
I'd assume that this would provide a robust solution for a couple of "simple" use cases while also being kind of easy to implement.
I ran into an issue recently with the following construct:
asyncdefa() ->bool:
returnFalseifa():
pass
Because a was truthy, it would always pass. The way I interpret the conversation thus far is to move towards disambiguation, guiding the user to codify their intent so typecheckers can pick up the slack MustUse[...] is an interesting idea (and fits my aesthetic) but I agree with the author that that's actually the default expectation in most cases.
I think a sensible nudge here would be Awaitable[...] values requiring an explicit round-trip through a primitive type before being used in a primitive way. In my thinking currently, either bool or str. I don't think others apply.
bool
In my example above, if I truly wanted truthy behavior, I would change it to if bool(a()):.
error: Incompatible types in "await" (actual type "Awaitable[bool] | None", expected type "Awaitable[Any]") [misc]
str
This would also sensibly apply to string interpolation. The following could be disambiguated by suggesting the user explicitly choose str(a()) or repr(a()) depending on what they are trying to accomplish.
f"The value is currently {a()}."
This would help with code reviews as well, where more seasoned developers would see the out-of-place str(...) or bool(...) and start prodding.
I think broadly, if you're using a typechecker in Python at all you're opting in to removing classes of bugs.
What we are trying to do here is just a formalization of intent, and shouldn't remove expressivity from the language.
I could see pushback here being adding too much noise to existing programs, but I'd assert that the vast majority of cases where this would flag on existing code are actually already bugs (or debugging statements which can be patched by rote)
Since people at work were talking about this... Mypy already supports a version of this; it will complain about OP's case out of the box. For others, you might need --enable-error-code unused-awaitable. Also --enable-error-code truthy-bool can catch some similar issues like the one from @blast-hardcheese .
Activity
gvanrossum commentedon Nov 27, 2016
I'm pretty sure we've got another similar issue somewhere, but I can't find it. (About results that should never be discarded -- perhaps uncalled functions?)
I'm not sure how easy this would be to do -- especially not the generalization, which probably would require us to invent some acceptable syntax for marking functions whose result should never be ignored.
Daenyth commentedon Nov 20, 2017
I think skipping generalization and focusing on
Awaitablefirst would still catch bugs. Once there's a second case people want to specialize it'll be easier to see what needs to be made generalnjsmith commentedon Sep 17, 2018
Awaitable is a bit special too, because "is the value used" isn't quite the right check. For example, here's one of the cpython bots that had a missing
await:python/cpython#9168 (comment)
The code was something like
f"Sorry, {get_participants()}, ...", but should have beenf"Sorry, {await get_participants()}, ...". So the awaitable value was used, and was even used in a type-valid way (pretty much any python object can be interpolated into an f-string), but clearly not what was intended. On the other hand, in asyncio, passing an awaitable into a function likeasyncio.gatheris a totally normal way to consume it.I was looking at this recently in the context of Trio, where the rules for using awaitables are much simpler: basically just, any call returning an awaitable should have an
awaitpresent, full stop. This is obviously too simple to become a general always-on feature in mypy, since it won't work for asyncio users, but it'd be nice to have it as an optional feature or a plugin or something. I made some more detailed notes here on what exactly we'd want and how it might work: python-trio/trio#671Is it possible to have a mypy plugin that adds a check like this? And can anyone point me at roughly where in the type checking process we'd want to be to perform a check like that? (I assume somewhere in
mypy/checker.py?)ilevkivskyi commentedon Sep 17, 2018
@njsmith
I am not sure writing it as a plugin is the best solution. This will need addition of at least one new plugin hook. I think a better idea is to just add a flag to mypy. This looks like a common scenario. Also the implementation in mypy is very simple. We can add a flag
is_awaitedonCallExprthat will be populated during semantic analysis (by enclosingvisit_await). An then an error can be given invisit_call_exprincheckexpr.pyif the inferred resulting type isAwaitable, but theis_awaitedflag is not set.njsmith commentedon Sep 17, 2018
That would probably be the easiest approach, yeah. But... this check really is only useful to projects using trio, not to projects using asyncio. Is mypy interested in carrying features like that?
ilevkivskyi commentedon Sep 18, 2018
I have heard similar things from other people. We can start with something, and then maybe generalize the flag to be tunable, for example:
--enforce-await=none(default) does nothing--enforce-await=global(hard to implement) will complain only if a coroutine is never awaited--enforce-await=local(easy to implement) will complain if a coroutine is not immediately awaited.But we can start just with a binary flag
--enforce-awaitand then iterate on the result later.JukkaL commentedon Sep 18, 2018
I don't like the idea of just adding another opt-in warning for this. I'd much prefer if we could have something that is enabled by default, even if it doesn't catch all errors. There's a risk that the majority of users would not realize that such an option exists.
What about always rejecting code such as the original example, as the first iteration of this feature:
Is there ever a good reason to write
f()without doing anything with the result? This wouldn't reject things likex = f(), only baref().x = f()would likely generate an error already if the code does anything withxlater on.We could later warn about things like
"{}".format(should_be_awaited())-- again, this is something that would hardly ever be right. We could special case some common operations that work with arbitrary objects, such asstr.format,%formatting,str(x)and f-strings. We could extend this to check for things like"%s" % foo.methodas well (i.e., forgot to call a method).Later, we could consider adding a trio-specific stricter check (possible as a plugin).
njsmith commentedon Sep 19, 2018
In Trio's case, we'd probably make it enabled by default in our official project template and advertise it prominently in the docs (we already have a whole discussion of this issue right in the tutorial). But yeah, I know what you mean.
If
freturns a coroutine object, then you're correct, a baref()is always a mistake. Iff()returns aFuture, then a baref()is a relatively normal thing to write. So I guess a check like this keyed offCoroutinerather thanAwaitablewould make sense.I hear where you're coming from. But, it is frustrating that mypy has all the information to do a very straightforward, precise, and high-coverage check here, but it's blocked because we can't get at it :-/. Would there be any simple, acceptable change to the plugin interface that would make it possible to implement this as a plugin relatively soon?
It looks to me like the two main blockers are:
You can define hooks that run on function and method calls, but any given function/method can only have 1 such hook. Here we want a hook that runs on all functions+methods, so we can do that, but if we do then it will effectively disable any other plugins that want to use those hooks. Maybe there could be some way to run multiple hooks in sequence? For the function and method hooks in particular it doesn't look like this would be terribly complicated semantically. This does create some issue about what order to run hooks in. (Ideally the
Awaitablecheck would run last, I guess, so it can see the inferredret_typeafter all other hooks have run?) But in practice it may not matter much. For this plugin, the only time it would matter would be if another plugin changed aret_typefromAwaitableto non-Awaitable, or vice-versa, and that seems rare.There's no way for the hook to tell whether a given call is the immediate child of an
awaitnode. I guess this could be hacked into theContext?JukkaL commentedon Sep 19, 2018
Small plugin API changes are typically not controversial, so this is a possibility.
That would be one way to do it, but ordering issues would make this a bit awkward, as you point out. Another option would to add a new "check only" hook that is called after the signature hooks have already run and we have a final signature. The hook wouldn't be able to change the signature but it can perform additional checks, and it's guaranteed to run after the signature-related hooks. We could allow multiple plugins to define these hooks for a function.
We'd need another attribute in the relevant context named tuple, I guess.
Can you create a new issue for the plugin feature? We can then focus on the non-trio/non-plugin aspects in this issue.
njsmith commentedon Sep 20, 2018
OK, forked this off into #5650 and replied there.
Dreamsorcerer commentedon Jun 8, 2021
What about a local check, with the added assumption that a function expecting an
Awaitableis likely toawaiton it? Plus a tunable unknown state when not enough typing information is available (parameter isAny).This would automatically make functions like
asyncio.gather()work without needing to special case them. I'm imagining that cases where a function expects anAwaitableand then doesn'tawaiton it are going to be very rare (plus if they are within the user's code it would still highlight an error within that function).For example:
Whenever the awaited state is
Falseit should error, and ifTrueit should not error. When it is in the unknown state, this should error depending on a tunable flag (similar to the variousAnywarnings that are configurable).Dreamsorcerer commentedon Nov 8, 2021
Is there likely to be any movement on this? I've been hitting this problem a lot lately, trying to refactor a bunch of sync code to be async and struggling to find every location that needs an
await.JelleZijlstra commentedon Nov 8, 2021
As you see, not much is happening.
I did implement this check in Quora's typechecker, pyanalyze (https://github.com/quora/pyanalyze); you can see the relevant tests at https://github.com/quora/pyanalyze/blob/26e622e249d5b101323647a29ede386d4de06f1e/pyanalyze/test_name_check_visitor.py#L1868. It is not nearly as well-tested as mypy, though, but feel free to give it a try.
6 remaining items
Dreamsorcerer commentedon Mar 29, 2022
I think you have basically just described the purpose of
# type: ignorecomments.This is what I proposed above (#2499 (comment)), with an option to change whether an error occurs when passed to a function with
Any.graingert commentedon Mar 29, 2022
anyio has the concept of DeprecatedAwaitables that warn when you await them - they are list, float and Noneish subclasses otherwise. Is the advice to do this?
jhance commentedon Mar 29, 2022
I think trying to find whether all possible control flows await an awaitable is difficult for a type checker. How does it know if I pass it to a function whether that function will await it or not?
The statement that passing it to a function is likely to await it is not true. For example appending to a list
Dreamsorcerer commentedon Mar 29, 2022
How frequently do you think a function will explicitly declare the type as
Awaitableand then not await on it (either directly or through another function)? My assumption is that this will be very rare in real world use cases, likewise a typesafe function that doesn't accept anAwaitable(e.g.object) is very unlikely to await on it. If this works in real world use cases 99% of the time, then it's definitely worth putting in.If a function accepts
Anythen we have no idea if it will await or not, so having a strictness setting to warn or not in these cases is helpful. We can default to not warning to avoid false positives, but people looking for something stricter/safer can enable warnings in this case (see the different examples in my comment).graingert commentedon May 12, 2022
closed in #12279 ?
see docs here https://mypy.readthedocs.io/en/stable/error_code_list2.html#check-that-awaitable-return-value-is-used-unused-awaitable
Dreamsorcerer commentedon May 12, 2022
That just handles unused awaitable expressions. Something as simple as
a = foo()won't trigger the warning.I think we still need to have something like #2499 (comment) to cover a lot more cases. It's a bit more complex as you need to track the awaited state through a function in order to see if it was awaited by the end, but would catch a lot more mistakes.
jstasiak commentedon May 12, 2022
a = foo()wherearemains unused should be covered by flake8 or other linters already under the unused variable umbrella (granted, maybe it'd be nice to have it in Mypy as well)Dreamsorcerer commentedon May 12, 2022
That only works if the variable is unused. You can then do
print(a)or similar and flake8 will no longer catch it as unused and mypy will not catch it as unawaited.tback commentedon Mar 19, 2024
Can mypy be configured to require type hints for unawaited assigns?
Dreamsorcerer commentedon Mar 19, 2024
That is not an unusual thing to do though, so not sure how many people would find such an option useful.
tback commentedon Mar 19, 2024
My reasoning behind this request is to make delayed calls an active decision and to prevent errors from forgotten awaits.
I'd assume that this would provide a robust solution for a couple of "simple" use cases while also being kind of easy to implement.
blast-hardcheese commentedon Dec 10, 2024
I ran into an issue recently with the following construct:
Because
awas truthy, it would always pass. The way I interpret the conversation thus far is to move towards disambiguation, guiding the user to codify their intent so typecheckers can pick up the slackMustUse[...]is an interesting idea (and fits my aesthetic) but I agree with the author that that's actually the default expectation in most cases.I think a sensible nudge here would be
Awaitable[...]values requiring an explicit round-trip through a primitive type before being used in a primitive way. In my thinking currently, eitherboolorstr. I don't think others apply.bool
In my example above, if I truly wanted truthy behavior, I would change it to
if bool(a()):.Interestingly, the
Nonecase is already handled:as we already emit
str
This would also sensibly apply to string interpolation. The following could be disambiguated by suggesting the user explicitly choose
str(a())orrepr(a())depending on what they are trying to accomplish.f"The value is currently {a()}."This would help with code reviews as well, where more seasoned developers would see the out-of-place
str(...)orbool(...)and start prodding.I think broadly, if you're using a typechecker in Python at all you're opting in to removing classes of bugs.
What we are trying to do here is just a formalization of intent, and shouldn't remove expressivity from the language.
I could see pushback here being adding too much noise to existing programs, but I'd assert that the vast majority of cases where this would flag on existing code are actually already bugs (or debugging statements which can be patched by rote)
hauntsaninja commentedon Oct 11, 2025
Since people at work were talking about this... Mypy already supports a version of this; it will complain about OP's case out of the box. For others, you might need
--enable-error-code unused-awaitable. Also--enable-error-code truthy-boolcan catch some similar issues like the one from @blast-hardcheese .