Skip to content

Treat functions that return None as returning None#20350

Merged
hauntsaninja merged 1 commit intopython:masterfrom
hauntsaninja:none-return-fix
Dec 2, 2025
Merged

Treat functions that return None as returning None#20350
hauntsaninja merged 1 commit intopython:masterfrom
hauntsaninja:none-return-fix

Conversation

@hauntsaninja
Copy link
Copy Markdown
Collaborator

@hauntsaninja hauntsaninja commented Dec 2, 2025

Fixes #20346

I assume the behaviour here came from the bad old days before strict optional :-)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 2, 2025

Diff from mypy_primer, showing the effect of this PR on open source code:

spark (https://github.com/apache/spark)
- python/pyspark/core/rdd.py:1664: error: Unused "type: ignore" comment  [unused-ignore]

mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
+ pymongo/pyopenssl_context.py:277: error: Incompatible return value type (got "None", expected "int")  [return-value]
+ pymongo/synchronous/topology.py:1054: error: No overload variant of "gather" matches argument types "list[None]", "bool"  [call-overload]
+ pymongo/synchronous/topology.py:1054: note: Error code "call-overload" not covered by "type: ignore" comment
+ pymongo/synchronous/topology.py:1054: note: Possible overload variants:
+ pymongo/synchronous/topology.py:1054: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: Literal[False] = ...) -> Future[list[_T]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException, _T6 | BaseException]]
+ pymongo/synchronous/topology.py:1054: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]
+ pymongo/synchronous/pool.py:865: error: No overload variant of "gather" matches argument types "list[None]", "bool"  [call-overload]
+ pymongo/synchronous/pool.py:865: note: Possible overload variants:
+ pymongo/synchronous/pool.py:865: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]
+ pymongo/synchronous/pool.py:865: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: Literal[False] = ...) -> Future[list[_T]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException]]
+ pymongo/synchronous/pool.py:865: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException, _T6 | BaseException]]
+ pymongo/synchronous/pool.py:865: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]
+ pymongo/synchronous/pool.py:902: error: No overload variant of "gather" matches argument types "list[None]", "bool"  [call-overload]
+ pymongo/synchronous/pool.py:902: note: Possible overload variants:
+ pymongo/synchronous/pool.py:902: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]
+ pymongo/synchronous/pool.py:902: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: Literal[False] = ...) -> Future[list[_T]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException]]
+ pymongo/synchronous/pool.py:902: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException, _T6 | BaseException]]
+ pymongo/synchronous/pool.py:902: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]
+ pymongo/synchronous/pool.py:953: error: No overload variant of "gather" matches argument types "list[None]", "bool"  [call-overload]
+ pymongo/synchronous/pool.py:953: note: Possible overload variants:
+ pymongo/synchronous/pool.py:953: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]
+ pymongo/synchronous/pool.py:953: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: Literal[False] = ...) -> Future[list[_T]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3, _T4] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3, _T4, _T5] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException]]
+ pymongo/synchronous/pool.py:953: note:     def [_T1, _T2, _T3, _T4, _T5, _T6] gather(Future[_T1] | Awaitable[_T1], Future[_T2] | Awaitable[_T2], Future[_T3] | Awaitable[_T3], Future[_T4] | Awaitable[_T4], Future[_T5] | Awaitable[_T5], Future[_T6] | Awaitable[_T6], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException, _T6 | BaseException]]
+ pymongo/synchronous/pool.py:953: note:     def [_T] gather(*coros_or_futures: Future[_T] | Awaitable[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]
+ pymongo/synchronous/monitor.py:197: error: No overload variant of "gather" matches argument types "None", "None", "bool"  [call-overload]
+ pymongo/synchronous/monitor.py:197: note: Error code "call-overload" not covered by "type: ignore" comment
+ pymongo/synchronous/monitor.py:197: note: Possible overload variants:
+ pymongo/synchronous/monitor.py:197: note:     def [_T1] gather(Future[_T1] | Awaitable[_T1], /, *, return_exceptions: Literal[False] = ...) -> Future[tuple[_T1]]

... (truncated 29 lines) ...

core (https://github.com/home-assistant/core)
+ homeassistant/components/ping/helpers.py:165: error: Incompatible types in "await" (actual type "None", expected type "Awaitable[Any]")  [misc]
+ homeassistant/components/ping/helpers.py:165: note: Error code "misc" not covered by "type: ignore" comment

ibis (https://github.com/ibis-project/ibis)
+ ibis/backends/__init__.py:1273: error: "None" has no attribute "__iter__" (not iterable)  [attr-defined]
+ ibis/backends/clickhouse/__init__.py:253: error: "None" has no attribute "__iter__" (not iterable)  [attr-defined]

@hauntsaninja hauntsaninja merged commit 4eb6b50 into python:master Dec 2, 2025
22 checks passed
@hauntsaninja hauntsaninja deleted the none-return-fix branch December 2, 2025 22:50
@github-project-automation github-project-automation Bot moved this from Todo to Done in GC-Content-Calculator Dec 12, 2025
brianhelba added a commit to brianhelba/pytest-django that referenced this pull request Apr 14, 2026
mypy 1.20 (python/mypy#20350) changes how it types the return value of
functions that return `None`: previously it used `Any` (suppressing downstream
errors), now it propagates the actual `None` type. This means the existing
`-> None` annotation on `assertTemplateUsed` / `assertTemplateNotUsed` causes
type errors when these functions are used as context managers.

This adds `@overload` signatures to distinguish the two calling conventions,
matching the pattern already used by `assertNumQueries` in this file:

* response is `HttpResponseBase`: direct assertion, returns `None`
* response is a `str` (shorthand for `template_name`): returns context manager
* response is `None` (`template_name` passed by keyword): returns context manager
brianhelba added a commit to brianhelba/pytest-django that referenced this pull request Apr 14, 2026
mypy 1.20 (python/mypy#20350) changes how it types the return value of
functions that return `None`: previously it used `Any` (suppressing downstream
errors), now it propagates the actual `None` type. This means the existing
`-> None` annotation on `assertTemplateUsed` / `assertTemplateNotUsed` causes
type errors when these functions are used as context managers.

This adds `@overload` signatures to distinguish the two calling conventions,
matching the pattern already used by `assertNumQueries` in this file:

* `response` is `HttpResponseBase`: direct assertion, returns `None`
* `response` is a `str` (shorthand for `template_name`): returns context manager
* `response` is `None` (`template_name` passed by keyword): returns context manager
brianhelba added a commit to brianhelba/pytest-django that referenced this pull request Apr 14, 2026
mypy 1.20 (python/mypy#20350) changes how it types the return value of
functions that return `None`: previously it used `Any` (suppressing downstream
errors), now it propagates the actual `None` type. This means the existing
`-> None` annotation on `assertTemplateUsed` / `assertTemplateNotUsed` causes
type errors when these functions are used as context managers.

This adds `@overload` signatures to distinguish the two calling conventions,
matching the pattern already used by `assertNumQueries` in this file:

* `response` is `HttpResponseBase`: direct assertion, returns `None`
* `response` is a `str` (shorthand for `template_name`): returns context manager
* `response` is `None` (`template_name` passed by keyword): returns context manager
brianhelba added a commit to brianhelba/pytest-django that referenced this pull request Apr 14, 2026
mypy 1.20 (python/mypy#20350) changes how it types the return value of
functions that return `None`: previously it used `Any` (suppressing downstream
errors), now it propagates the actual `None` type. This means the existing
`-> None` annotation on `assertTemplateUsed` / `assertTemplateNotUsed` causes
type errors when these functions are used as context managers.

This adds `@overload` signatures to distinguish the two calling conventions,
matching the pattern already used by `assertNumQueries` in this file:

* `response` is `HttpResponseBase`: direct assertion, returns `None`
* `response` is a `str` (shorthand for `template_name`): returns context manager
* `response` is `None` (`template_name` passed by keyword): returns context manager
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Function returning None gives Any even with func-returns-value error disabled

4 participants