From d099f995035e6d480e10bfa4e0844a6b080f2b2f Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Wed, 5 Feb 2025 15:33:02 -0700 Subject: [PATCH 1/8] Update typing for HostnameResolver In https://github.com/python/typeshed/pull/13372, the type of the stdlib's `socket.getaddrinfo` was updated to add a union member for cases where Python was compiled with `--disable-ipv6`. This causes custom resolvers that use the stdlib function to fail mypy This is probably going to (mypy) break everyone's custom `HostnameResolver`, since the parent type is changing --- src/trio/_abc.py | 2 +- src/trio/_socket.py | 2 +- src/trio/_tests/test_highlevel_open_tcp_listeners.py | 2 +- src/trio/_tests/test_highlevel_open_tcp_stream.py | 6 +++--- src/trio/_tests/test_highlevel_ssl_helpers.py | 4 ++-- src/trio/testing/_fake_net.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/trio/_abc.py b/src/trio/_abc.py index d981b8c9e..abb682438 100644 --- a/src/trio/_abc.py +++ b/src/trio/_abc.py @@ -178,7 +178,7 @@ async def getaddrinfo( socket.SocketKind, int, str, - tuple[str, int] | tuple[str, int, int, int], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ] ]: """A custom implementation of :func:`~trio.socket.getaddrinfo`. diff --git a/src/trio/_socket.py b/src/trio/_socket.py index 4dde51298..9b870c3f7 100644 --- a/src/trio/_socket.py +++ b/src/trio/_socket.py @@ -182,7 +182,7 @@ async def getaddrinfo( SocketKind, int, str, - tuple[str, int] | tuple[str, int, int, int], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ] ]: """Look up a numeric address given a name. diff --git a/src/trio/_tests/test_highlevel_open_tcp_listeners.py b/src/trio/_tests/test_highlevel_open_tcp_listeners.py index 57a2e530d..a77021709 100644 --- a/src/trio/_tests/test_highlevel_open_tcp_listeners.py +++ b/src/trio/_tests/test_highlevel_open_tcp_listeners.py @@ -255,7 +255,7 @@ async def getaddrinfo( SocketKind, int, str, - tuple[str, int] | tuple[str, int, int, int], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ] ]: assert isinstance(port, int) diff --git a/src/trio/_tests/test_highlevel_open_tcp_stream.py b/src/trio/_tests/test_highlevel_open_tcp_stream.py index d03e47250..eaec52ad4 100644 --- a/src/trio/_tests/test_highlevel_open_tcp_stream.py +++ b/src/trio/_tests/test_highlevel_open_tcp_stream.py @@ -287,9 +287,9 @@ def _ip_to_gai_entry(self, ip: str) -> tuple[ SocketKind, int, str, - tuple[str, int, int, int] | tuple[str, int], + tuple[str, int, int, int] | tuple[str, int] | tuple[int, bytes], ]: - sockaddr: tuple[str, int] | tuple[str, int, int, int] + sockaddr: tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes] if ":" in ip: family = trio.socket.AF_INET6 sockaddr = (ip, self.port, 0, 0) @@ -312,7 +312,7 @@ async def getaddrinfo( SocketKind, int, str, - tuple[str, int, int, int] | tuple[str, int], + tuple[str, int, int, int] | tuple[str, int] | tuple[int, bytes], ] ]: assert host == b"test.example.com" diff --git a/src/trio/_tests/test_highlevel_ssl_helpers.py b/src/trio/_tests/test_highlevel_ssl_helpers.py index e42f31198..8c60c25f2 100644 --- a/src/trio/_tests/test_highlevel_ssl_helpers.py +++ b/src/trio/_tests/test_highlevel_ssl_helpers.py @@ -45,7 +45,7 @@ async def echo_handler(stream: Stream) -> None: # you ask for. @attrs.define(slots=False) class FakeHostnameResolver(trio.abc.HostnameResolver): - sockaddr: tuple[str, int] | tuple[str, int, int, int] + sockaddr: tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes] async def getaddrinfo( self, @@ -61,7 +61,7 @@ async def getaddrinfo( SocketKind, int, str, - tuple[str, int] | tuple[str, int, int, int], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ] ]: return [(AF_INET, SOCK_STREAM, IPPROTO_TCP, "", self.sockaddr)] diff --git a/src/trio/testing/_fake_net.py b/src/trio/testing/_fake_net.py index 2f5bd624a..e036c78c0 100644 --- a/src/trio/testing/_fake_net.py +++ b/src/trio/testing/_fake_net.py @@ -155,7 +155,7 @@ async def getaddrinfo( SocketKind, int, str, - tuple[str, int] | tuple[str, int, int, int], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ] ]: raise NotImplementedError("FakeNet doesn't do fake DNS yet") From 922cf372ab1aa80be78d4a5f966b1f2ca306598b Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Wed, 5 Feb 2025 15:39:45 -0700 Subject: [PATCH 2/8] add newsfragment --- newsfragments/3201.misc.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 newsfragments/3201.misc.rst diff --git a/newsfragments/3201.misc.rst b/newsfragments/3201.misc.rst new file mode 100644 index 000000000..309f27cdf --- /dev/null +++ b/newsfragments/3201.misc.rst @@ -0,0 +1,5 @@ +The typing of :func:`trio.abc.HostnameResolver.getaddrinfo` has been corrected to +match that of the stdlib `socket.getaddrinfo`, which was updated in mypy 1.15 (via +a typeshed update) to include the possibility of ``tuple[int, bytes]`` for the +``sockaddr`` field of the result. This happens in situations where Python was compiled +with ``--disable-ipv6``. From 32ec6de8db336cba82048c29d5f514186670ad53 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:00:34 -0600 Subject: [PATCH 3/8] Bump mypy to `1.15.0` --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 92ec73577..06287aff2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -81,7 +81,7 @@ markupsafe==3.0.2 # via jinja2 mccabe==0.7.0 # via pylint -mypy==1.14.1 +mypy==1.15.0 # via -r test-requirements.in mypy-extensions==1.0.0 # via From 3a45f2c87f90259b23e711b2dde4c72e3db2eaf1 Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Fri, 7 Feb 2025 15:40:23 -0700 Subject: [PATCH 4/8] All the rest of the mypy 1.15 updates --- .pre-commit-config.yaml | 2 ++ src/trio/_core/_entry_queue.py | 3 +- src/trio/_core/_instrumentation.py | 14 +++++---- src/trio/_core/_run.py | 41 ++++++++++---------------- src/trio/_core/_tests/test_asyncgen.py | 13 +++++--- src/trio/_core/_tests/test_ki.py | 13 ++++---- src/trio/_core/_tests/test_run.py | 8 ++--- src/trio/_core/_thread_cache.py | 3 +- src/trio/_core/_traps.py | 3 +- src/trio/_file_io.py | 2 +- src/trio/_highlevel_open_tcp_stream.py | 7 ++--- src/trio/_highlevel_serve_listeners.py | 6 ++-- src/trio/_path.py | 9 +++--- src/trio/_socket.py | 3 +- src/trio/_ssl.py | 3 +- src/trio/_tests/test_socket.py | 12 ++++++-- src/trio/_tests/test_ssl.py | 3 +- src/trio/_tests/test_subprocess.py | 11 ++++--- src/trio/_tests/test_threads.py | 14 ++++----- src/trio/_threads.py | 34 ++++++++++----------- src/trio/_util.py | 17 ++++------- src/trio/testing/_fake_net.py | 3 +- 22 files changed, 103 insertions(+), 121 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 371134ae5..7aed67fcf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,6 +34,8 @@ repos: rev: v2.4.1 hooks: - id: codespell + additional_dependencies: + - tomli - repo: https://github.com/crate-ci/typos rev: typos-dict-v0.12.4 hooks: diff --git a/src/trio/_core/_entry_queue.py b/src/trio/_core/_entry_queue.py index 0691de351..988b45ca0 100644 --- a/src/trio/_core/_entry_queue.py +++ b/src/trio/_core/_entry_queue.py @@ -16,8 +16,7 @@ PosArgsT = TypeVarTuple("PosArgsT") -# Explicit "Any" is not allowed -Function = Callable[..., object] # type: ignore[misc] +Function = Callable[..., object] # type: ignore[explicit-any] Job = tuple[Function, tuple[object, ...]] diff --git a/src/trio/_core/_instrumentation.py b/src/trio/_core/_instrumentation.py index 40bddd1a2..afdff19cd 100644 --- a/src/trio/_core/_instrumentation.py +++ b/src/trio/_core/_instrumentation.py @@ -2,23 +2,25 @@ import logging import types -from collections.abc import Callable, Sequence -from typing import TypeVar +from typing import TYPE_CHECKING, TypeVar from .._abc import Instrument # Used to log exceptions in instruments INSTRUMENT_LOGGER = logging.getLogger("trio.abc.Instrument") +if TYPE_CHECKING: + from collections.abc import Callable, Sequence -# Explicit "Any" is not allowed -F = TypeVar("F", bound=Callable[..., object]) # type: ignore[misc] + from typing_extensions import ParamSpec + + P = ParamSpec("P") + T = TypeVar("T") # Decorator to mark methods public. This does nothing by itself, but # trio/_tools/gen_exports.py looks for it. -# Explicit "Any" is not allowed -def _public(fn: F) -> F: # type: ignore[misc] +def _public(fn: Callable[P, T]) -> Callable[P, T]: return fn diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py index eedb99644..8649c0f70 100644 --- a/src/trio/_core/_run.py +++ b/src/trio/_core/_run.py @@ -61,7 +61,6 @@ from collections.abc import ( Awaitable, Callable, - Coroutine, Generator, Iterator, Sequence, @@ -1303,8 +1302,7 @@ def start_soon( GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name) # Typing changes blocked by https://github.com/python/mypy/pull/17512 - # Explicit "Any" is not allowed - async def start( # type: ignore[misc] + async def start( # type: ignore[explicit-any] self, async_fn: Callable[..., Awaitable[object]], *args: object, @@ -1404,10 +1402,9 @@ def __del__(self) -> None: @final @attrs.define(eq=False, repr=False) -class Task(metaclass=NoPublicConstructor): # type: ignore[misc] +class Task(metaclass=NoPublicConstructor): # type: ignore[explicit-any] _parent_nursery: Nursery | None - # Explicit "Any" is not allowed - coro: Coroutine[Any, Outcome[object], Any] # type: ignore[misc] + coro: types.CoroutineType[Any, Outcome[object], Any] # type: ignore[explicit-any] _runner: Runner name: str context: contextvars.Context @@ -1425,11 +1422,10 @@ class Task(metaclass=NoPublicConstructor): # type: ignore[misc] # tracebacks with extraneous frames. # - for scheduled tasks, custom_sleep_data is None # Tasks start out unscheduled. - # Explicit "Any" is not allowed - _next_send_fn: Callable[[Any], object] | None = None # type: ignore[misc] - _next_send: Outcome[Any] | BaseException | None = None # type: ignore[misc] + _next_send_fn: Callable[[Any], object] | None = None # type: ignore[explicit-any] + _next_send: Outcome[Any] | BaseException | None = None # type: ignore[explicit-any] _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = None - custom_sleep_data: Any = None # type: ignore[misc] + custom_sleep_data: Any = None # type: ignore[explicit-any] # For introspection and nursery.start() _child_nurseries: list[Nursery] = attrs.Factory(list) @@ -1497,7 +1493,7 @@ def print_stack_for_task(task): """ # Ignore static typing as we're doing lots of dynamic introspection - coro: Any = self.coro # type: ignore[misc] + coro: Any = self.coro # type: ignore[explicit-any] while coro is not None: if hasattr(coro, "cr_frame"): # A real coroutine @@ -1642,16 +1638,13 @@ class RunStatistics: @attrs.define(eq=False) -# Explicit "Any" is not allowed -class GuestState: # type: ignore[misc] +class GuestState: # type: ignore[explicit-any] runner: Runner run_sync_soon_threadsafe: Callable[[Callable[[], object]], object] run_sync_soon_not_threadsafe: Callable[[Callable[[], object]], object] - # Explicit "Any" is not allowed - done_callback: Callable[[Outcome[Any]], object] # type: ignore[misc] + done_callback: Callable[[Outcome[Any]], object] # type: ignore[explicit-any] unrolled_run_gen: Generator[float, EventResult, None] - # Explicit "Any" is not allowed - unrolled_run_next_send: Outcome[Any] = attrs.Factory(lambda: Value(None)) # type: ignore[misc] + unrolled_run_next_send: Outcome[Any] = attrs.Factory(lambda: Value(None)) # type: ignore[explicit-any] def guest_tick(self) -> None: prev_library, sniffio_library.name = sniffio_library.name, "trio" @@ -1696,8 +1689,7 @@ def in_main_thread() -> None: @attrs.define(eq=False) -# Explicit "Any" is not allowed -class Runner: # type: ignore[misc] +class Runner: # type: ignore[explicit-any] clock: Clock instruments: Instruments io_manager: TheIOManager @@ -1705,8 +1697,7 @@ class Runner: # type: ignore[misc] strict_exception_groups: bool # Run-local values, see _local.py - # Explicit "Any" is not allowed - _locals: dict[_core.RunVar[Any], object] = attrs.Factory(dict) # type: ignore[misc] + _locals: dict[_core.RunVar[Any], object] = attrs.Factory(dict) # type: ignore[explicit-any] runq: deque[Task] = attrs.Factory(deque) tasks: set[Task] = attrs.Factory(set) @@ -1895,7 +1886,7 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT: return await orig_coro coro = python_wrapper(coro) - assert coro.cr_frame is not None, "Coroutine frame should exist" + assert coro.cr_frame is not None, "Coroutine frame should exist" # type: ignore[attr-defined] ###### # Set up the Task object @@ -2133,8 +2124,7 @@ def _deliver_ki_cb(self) -> None: # sortedcontainers doesn't have types, and is reportedly very hard to type: # https://github.com/grantjenks/python-sortedcontainers/issues/68 - # Explicit "Any" is not allowed - waiting_for_idle: Any = attrs.Factory(SortedDict) # type: ignore[misc] + waiting_for_idle: Any = attrs.Factory(SortedDict) # type: ignore[explicit-any] @_public async def wait_all_tasks_blocked(self, cushion: float = 0.0) -> None: @@ -2435,8 +2425,7 @@ def run( raise AssertionError(runner.main_task_outcome) -# Explicit .../"Any" not allowed -def start_guest_run( # type: ignore[misc] +def start_guest_run( # type: ignore[explicit-any] async_fn: Callable[..., Awaitable[RetT]], *args: object, run_sync_soon_threadsafe: Callable[[Callable[[], object]], object], diff --git a/src/trio/_core/_tests/test_asyncgen.py b/src/trio/_core/_tests/test_asyncgen.py index 588c54dae..232dd5b1e 100644 --- a/src/trio/_core/_tests/test_asyncgen.py +++ b/src/trio/_core/_tests/test_asyncgen.py @@ -4,13 +4,16 @@ import sys import weakref from math import inf -from typing import TYPE_CHECKING, NoReturn +from types import AsyncGeneratorType +from typing import TYPE_CHECKING, NoReturn, TypeAlias, cast import pytest from ... import _core from .tutil import gc_collect_harder, restore_unraisablehook +AGT: TypeAlias = AsyncGeneratorType[int, None] + if TYPE_CHECKING: from collections.abc import AsyncGenerator @@ -68,20 +71,20 @@ async def async_main() -> None: # No problems saving the geniter when using either of these patterns aiter_ = example("exhausted 3") try: - saved.append(aiter_) + saved.append(cast("AGT", aiter_)) assert await aiter_.asend(None) == 42 finally: await aiter_.aclose() assert collected.pop() == "exhausted 3" # Also fine if you exhaust it at point of use - saved.append(example("exhausted 4")) + saved.append(cast("AGT", example("exhausted 4"))) async for val in saved[-1]: assert val == 42 assert collected.pop() == "exhausted 4" # Leave one referenced-but-unexhausted and make sure it gets cleaned up - saved.append(example("outlived run")) + saved.append(cast("AGT", example("outlived run"))) assert await saved[-1].asend(None) == 42 assert collected == [] @@ -301,9 +304,11 @@ def test_delegation_to_existing_hooks() -> None: record = [] def my_firstiter(agen: AsyncGenerator[object, NoReturn]) -> None: + assert isinstance(agen, AsyncGeneratorType) record.append("firstiter " + agen.ag_frame.f_locals["arg"]) def my_finalizer(agen: AsyncGenerator[object, NoReturn]) -> None: + assert isinstance(agen, AsyncGeneratorType) record.append("finalizer " + agen.ag_frame.f_locals["arg"]) async def example(arg: str) -> AsyncGenerator[int, None]: diff --git a/src/trio/_core/_tests/test_ki.py b/src/trio/_core/_tests/test_ki.py index 67c83e835..07a755872 100644 --- a/src/trio/_core/_tests/test_ki.py +++ b/src/trio/_core/_tests/test_ki.py @@ -165,7 +165,7 @@ def protected_manager() -> Iterator[None]: async def test_async_generator_agen_protection() -> None: @_core.enable_ki_protection @async_generator # type: ignore[misc] # untyped generator - async def agen_protected1() -> None: + async def agen_protected1() -> None: # type: ignore[misc] # untyped generator assert _core.currently_ki_protected() try: await yield_() @@ -174,7 +174,7 @@ async def agen_protected1() -> None: @_core.disable_ki_protection @async_generator # type: ignore[misc] # untyped generator - async def agen_unprotected1() -> None: + async def agen_unprotected1() -> None: # type: ignore[misc] # untyped generator assert not _core.currently_ki_protected() try: await yield_() @@ -184,7 +184,7 @@ async def agen_unprotected1() -> None: # Swap the order of the decorators: @async_generator # type: ignore[misc] # untyped generator @_core.enable_ki_protection - async def agen_protected2() -> None: + async def agen_protected2() -> None: # type: ignore[misc] # untyped generator assert _core.currently_ki_protected() try: await yield_() @@ -193,7 +193,7 @@ async def agen_protected2() -> None: @async_generator # type: ignore[misc] # untyped generator @_core.disable_ki_protection - async def agen_unprotected2() -> None: + async def agen_unprotected2() -> None: # type: ignore[misc] # untyped generator assert not _core.currently_ki_protected() try: await yield_() @@ -677,9 +677,8 @@ async def _consume_async_generator(agen: AsyncGenerator[None, None]) -> None: await agen.aclose() -# Explicit .../"Any" is not allowed -def _consume_function_for_coverage( # type: ignore[misc] - fn: Callable[..., object], +def _consume_function_for_coverage( + fn: Callable[[], object], ) -> None: result = fn() if inspect.isasyncgen(result): diff --git a/src/trio/_core/_tests/test_run.py b/src/trio/_core/_tests/test_run.py index 576b807f9..289a35667 100644 --- a/src/trio/_core/_tests/test_run.py +++ b/src/trio/_core/_tests/test_run.py @@ -1658,8 +1658,7 @@ async def func1(expected: str) -> None: async def func2() -> None: # pragma: no cover pass - # Explicit .../"Any" is not allowed - async def check( # type: ignore[misc] + async def check( # type: ignore[explicit-any] spawn_fn: Callable[..., object], ) -> None: spawn_fn(func1, "func1") @@ -1696,14 +1695,13 @@ async def test_current_effective_deadline(mock_clock: _core.MockClock) -> None: def test_nice_error_on_bad_calls_to_run_or_spawn() -> None: - # Explicit .../"Any" is not allowed - def bad_call_run( # type: ignore[misc] + def bad_call_run( # type: ignore[explicit-any] func: Callable[..., Awaitable[object]], *args: tuple[object, ...], ) -> None: _core.run(func, *args) - def bad_call_spawn( # type: ignore[misc] + def bad_call_spawn( # type: ignore[explicit-any] func: Callable[..., Awaitable[object]], *args: tuple[object, ...], ) -> None: diff --git a/src/trio/_core/_thread_cache.py b/src/trio/_core/_thread_cache.py index 189d5a583..cbbfbd53e 100644 --- a/src/trio/_core/_thread_cache.py +++ b/src/trio/_core/_thread_cache.py @@ -215,8 +215,7 @@ class ThreadCache: __slots__ = ("_idle_workers",) def __init__(self) -> None: - # Explicit "Any" not allowed - self._idle_workers: dict[WorkerThread[Any], None] = {} # type: ignore[misc] + self._idle_workers: dict[WorkerThread[Any], None] = {} # type: ignore[explicit-any] def start_thread_soon( self, diff --git a/src/trio/_core/_traps.py b/src/trio/_core/_traps.py index 1ddd5628b..60f72d129 100644 --- a/src/trio/_core/_traps.py +++ b/src/trio/_core/_traps.py @@ -104,8 +104,7 @@ class Abort(enum.Enum): # Should always return the type a Task "expects", unless you willfully reschedule it # with a bad value. -# Explicit "Any" is not allowed -async def wait_task_rescheduled( # type: ignore[misc] +async def wait_task_rescheduled( # type: ignore[explicit-any] abort_func: Callable[[RaiseCancelT], Abort], ) -> Any: """Put the current task to sleep, with cancellation support. diff --git a/src/trio/_file_io.py b/src/trio/_file_io.py index 5307fb942..e6a75c7ff 100644 --- a/src/trio/_file_io.py +++ b/src/trio/_file_io.py @@ -428,7 +428,7 @@ async def open_file( @overload -async def open_file( # type: ignore[misc] # Any usage matches builtins.open(). +async def open_file( # type: ignore[explicit-any,misc] # Any usage matches builtins.open(). file: _OpenFile, mode: str, buffering: int = -1, diff --git a/src/trio/_highlevel_open_tcp_stream.py b/src/trio/_highlevel_open_tcp_stream.py index d4ec98355..5723180e4 100644 --- a/src/trio/_highlevel_open_tcp_stream.py +++ b/src/trio/_highlevel_open_tcp_stream.py @@ -8,7 +8,7 @@ from trio.socket import SOCK_STREAM, SocketType, getaddrinfo, socket if TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Generator, MutableSequence from socket import AddressFamily, SocketKind from trio._socket import AddressFormat @@ -134,9 +134,8 @@ def close_all() -> Generator[set[SocketType], None, None]: raise BaseExceptionGroup("", errs) -# Explicit "Any" is not allowed -def reorder_for_rfc_6555_section_5_4( # type: ignore[misc] - targets: list[tuple[AddressFamily, SocketKind, int, str, Any]], +def reorder_for_rfc_6555_section_5_4( # type: ignore[explicit-any] + targets: MutableSequence[tuple[AddressFamily, SocketKind, int, str, Any]], ) -> None: # RFC 6555 section 5.4 says that if getaddrinfo returns multiple address # families (e.g. IPv4 and IPv6), then you should make sure that your first diff --git a/src/trio/_highlevel_serve_listeners.py b/src/trio/_highlevel_serve_listeners.py index 9b17f8d53..008caaabe 100644 --- a/src/trio/_highlevel_serve_listeners.py +++ b/src/trio/_highlevel_serve_listeners.py @@ -25,8 +25,7 @@ StreamT = TypeVar("StreamT", bound=trio.abc.AsyncResource) -# Explicit "Any" is not allowed -ListenerT = TypeVar("ListenerT", bound=trio.abc.Listener[Any]) # type: ignore[misc] +ListenerT = TypeVar("ListenerT", bound=trio.abc.Listener[Any]) # type: ignore[explicit-any] Handler = Callable[[StreamT], Awaitable[object]] @@ -68,8 +67,7 @@ async def _serve_one_listener( # https://github.com/python/typing/issues/548 -# Explicit "Any" is not allowed -async def serve_listeners( # type: ignore[misc] +async def serve_listeners( # type: ignore[explicit-any] handler: Handler[StreamT], listeners: list[ListenerT], *, diff --git a/src/trio/_path.py b/src/trio/_path.py index a58136b75..53bd0a700 100644 --- a/src/trio/_path.py +++ b/src/trio/_path.py @@ -30,8 +30,7 @@ T = TypeVar("T") -# Explicit .../"Any" is not allowed -def _wraps_async( # type: ignore[misc] +def _wraps_async( # type: ignore[explicit-any] wrapped: Callable[..., object], ) -> Callable[[Callable[P, T]], Callable[P, Awaitable[T]]]: def decorator(fn: Callable[P, T]) -> Callable[P, Awaitable[T]]: @@ -184,7 +183,7 @@ async def open( ) -> AsyncIOWrapper[BinaryIO]: ... @overload - async def open( # type: ignore[misc] # Any usage matches builtins.open(). + async def open( # type: ignore[misc, explicit-any] # Any usage matches builtins.open(). self, mode: str, buffering: int = -1, @@ -193,8 +192,8 @@ async def open( # type: ignore[misc] # Any usage matches builtins.open(). newline: str | None = None, ) -> AsyncIOWrapper[IO[Any]]: ... - @_wraps_async(pathlib.Path.open) # type: ignore[misc] # Overload return mismatch. - def open(self, *args: Any, **kwargs: Any) -> AsyncIOWrapper[IO[Any]]: + @_wraps_async(pathlib.Path.open) + def open(self, *args: Any, **kwargs: Any) -> AsyncIOWrapper[IO[Any]]: # type: ignore[misc, explicit-any] # Overload return mismatch. return wrap_file(self._wrapped_cls(self).open(*args, **kwargs)) def __repr__(self) -> str: diff --git a/src/trio/_socket.py b/src/trio/_socket.py index 9b870c3f7..003f6c41d 100644 --- a/src/trio/_socket.py +++ b/src/trio/_socket.py @@ -46,8 +46,7 @@ # most users, so currently we just specify it as `Any`. Otherwise we would write: # `AddressFormat = TypeVar("AddressFormat")` # but instead we simply do: -# Explicit "Any" is not allowed -AddressFormat: TypeAlias = Any # type: ignore[misc] +AddressFormat: TypeAlias = Any # type: ignore[explicit-any] # Usage: diff --git a/src/trio/_ssl.py b/src/trio/_ssl.py index 0a0419fbc..52c5137ea 100644 --- a/src/trio/_ssl.py +++ b/src/trio/_ssl.py @@ -423,8 +423,7 @@ def __init__( "version", } - # Explicit "Any" is not allowed - def __getattr__( # type: ignore[misc] + def __getattr__( # type: ignore[explicit-any] self, name: str, ) -> Any: diff --git a/src/trio/_tests/test_socket.py b/src/trio/_tests/test_socket.py index f4c963f4a..b26d5dde7 100644 --- a/src/trio/_tests/test_socket.py +++ b/src/trio/_tests/test_socket.py @@ -30,7 +30,7 @@ SocketKind, int, str, - Union[tuple[str, int], tuple[str, int, int, int]], + Union[tuple[str, int], tuple[str, int, int, int], tuple[int, bytes]], ] GetAddrInfoResponse: TypeAlias = list[GaiTuple] GetAddrInfoArgs: TypeAlias = tuple[ @@ -186,7 +186,10 @@ def interesting_fields( ) -> tuple[ AddressFamily, SocketKind, - tuple[str, int] | tuple[str, int, int] | tuple[str, int, int, int], + tuple[str, int] + | tuple[str, int, int] + | tuple[str, int, int, int] + | tuple[int, bytes], ]: # (family, type, proto, canonname, sockaddr) family, type_, _proto, _canonname, sockaddr = gai_tup @@ -198,7 +201,10 @@ def filtered( tuple[ AddressFamily, SocketKind, - tuple[str, int] | tuple[str, int, int] | tuple[str, int, int, int], + tuple[str, int] + | tuple[str, int, int] + | tuple[str, int, int, int] + | tuple[int, bytes], ] ]: return [interesting_fields(gai_tup) for gai_tup in gai_list] diff --git a/src/trio/_tests/test_ssl.py b/src/trio/_tests/test_ssl.py index d271743c7..5427ee65a 100644 --- a/src/trio/_tests/test_ssl.py +++ b/src/trio/_tests/test_ssl.py @@ -381,8 +381,7 @@ def virtual_ssl_echo_server( yield SSLStream(fakesock, client_ctx, server_hostname="trio-test-1.example.org") -# Explicit "Any" is not allowed -def ssl_wrap_pair( # type: ignore[misc] +def ssl_wrap_pair( # type: ignore[explicit-any] client_ctx: SSLContext, client_transport: T_Stream, server_transport: T_Stream, diff --git a/src/trio/_tests/test_subprocess.py b/src/trio/_tests/test_subprocess.py index 638ba4c14..15c88be25 100644 --- a/src/trio/_tests/test_subprocess.py +++ b/src/trio/_tests/test_subprocess.py @@ -82,8 +82,8 @@ def SLEEP(seconds: int) -> list[str]: return python(f"import time; time.sleep({seconds})") -@asynccontextmanager # type: ignore[misc] # Any in decorated -async def open_process_then_kill( +@asynccontextmanager +async def open_process_then_kill( # type: ignore[misc, explicit-any] *args: Any, **kwargs: Any, ) -> AsyncIterator[Process]: @@ -95,8 +95,8 @@ async def open_process_then_kill( await proc.wait() -@asynccontextmanager # type: ignore[misc] # Any in decorated -async def run_process_in_nursery( +@asynccontextmanager +async def run_process_in_nursery( # type: ignore[misc, explicit-any] *args: Any, **kwargs: Any, ) -> AsyncIterator[Process]: @@ -115,8 +115,7 @@ async def run_process_in_nursery( ids=["open_process", "run_process in nursery"], ) -# Explicit .../"Any" is not allowed -BackgroundProcessType: TypeAlias = Callable[ # type: ignore[misc] +BackgroundProcessType: TypeAlias = Callable[ # type: ignore[explicit-any] ..., AbstractAsyncContextManager[Process], ] diff --git a/src/trio/_tests/test_threads.py b/src/trio/_tests/test_threads.py index 822385a3a..380da3833 100644 --- a/src/trio/_tests/test_threads.py +++ b/src/trio/_tests/test_threads.py @@ -55,8 +55,7 @@ async def test_do_in_trio_thread() -> None: trio_thread = threading.current_thread() - # Explicit "Any" is not allowed - async def check_case( # type: ignore[misc] + async def check_case( # type: ignore[explicit-any] do_in_trio_thread: Callable[..., threading.Thread], fn: Callable[..., T | Awaitable[T]], expected: tuple[str, T], @@ -207,7 +206,7 @@ def inner(name: str = "inner" + ending) -> threading.Thread: assert threading.current_thread().name == name return threading.current_thread() - def f(name: str) -> Callable[[None], threading.Thread]: + def f(name: str) -> Callable[[], threading.Thread]: return partial(inner, name) # test defaults @@ -285,7 +284,7 @@ def inner(name: str) -> threading.Thread: return threading.current_thread() - def f(name: str) -> Callable[[None], threading.Thread]: + def f(name: str) -> Callable[[], threading.Thread]: return partial(inner, name) # test defaults @@ -344,7 +343,7 @@ def g() -> NoReturn: async def test_run_in_worker_thread_cancellation() -> None: register: list[str | None] = [None] - def f(q: stdlib_queue.Queue[str]) -> None: + def f(q: stdlib_queue.Queue[None]) -> None: # Make the thread block for a controlled amount of time register[0] = "blocking" q.get() @@ -731,7 +730,7 @@ def sync_fn() -> None: # pragma: no cover pass with pytest.raises(TypeError, match="appears to be synchronous"): - await to_thread_run_sync(from_thread_run, sync_fn) + await to_thread_run_sync(from_thread_run, sync_fn) # type: ignore[arg-type] async def test_trio_from_thread_token() -> None: @@ -930,8 +929,7 @@ async def test_recursive_to_thread() -> None: def get_tid_then_reenter() -> int: nonlocal tid tid = threading.get_ident() - # The nesting of wrapper functions loses the return value of threading.get_ident - return from_thread_run(to_thread_run_sync, threading.get_ident) # type: ignore[no-any-return] + return from_thread_run(to_thread_run_sync, threading.get_ident) assert tid != await to_thread_run_sync(get_tid_then_reenter) diff --git a/src/trio/_threads.py b/src/trio/_threads.py index 7afd7b612..2b4eb9115 100644 --- a/src/trio/_threads.py +++ b/src/trio/_threads.py @@ -29,8 +29,12 @@ if TYPE_CHECKING: from collections.abc import Awaitable, Callable, Generator + from typing_extensions import TypeVarTuple, Unpack + from trio._core._traps import RaiseCancelT + Ts = TypeVarTuple("Ts") + RetT = TypeVar("RetT") @@ -146,9 +150,8 @@ class ThreadPlaceholder: # Types for the to_thread_run_sync message loop @attrs.frozen(eq=False, slots=False) -# Explicit .../"Any" is not allowed -class Run(Generic[RetT]): # type: ignore[misc] - afn: Callable[..., Awaitable[RetT]] # type: ignore[misc] +class Run(Generic[RetT]): # type: ignore[explicit-any] + afn: Callable[..., Awaitable[RetT]] # type: ignore[explicit-any] args: tuple[object, ...] context: contextvars.Context = attrs.field( init=False, @@ -206,9 +209,8 @@ def in_trio_thread() -> None: @attrs.frozen(eq=False, slots=False) -# Explicit .../"Any" is not allowed -class RunSync(Generic[RetT]): # type: ignore[misc] - fn: Callable[..., RetT] # type: ignore[misc] +class RunSync(Generic[RetT]): # type: ignore[explicit-any] + fn: Callable[..., RetT] # type: ignore[explicit-any] args: tuple[object, ...] context: contextvars.Context = attrs.field( init=False, @@ -252,9 +254,9 @@ def run_in_system_nursery(self, token: TrioToken) -> None: @enable_ki_protection # Decorator used on function with Coroutine[Any, Any, RetT] -async def to_thread_run_sync( # type: ignore[misc] - sync_fn: Callable[..., RetT], - *args: object, +async def to_thread_run_sync( + sync_fn: Callable[[Unpack[Ts]], RetT], + *args: Unpack[Ts], thread_name: str | None = None, abandon_on_cancel: bool = False, limiter: CapacityLimiter | None = None, @@ -524,10 +526,9 @@ def _send_message_to_trio( return message_to_trio.queue.get().unwrap() -# Explicit "Any" is not allowed -def from_thread_run( # type: ignore[misc] - afn: Callable[..., Awaitable[RetT]], - *args: object, +def from_thread_run( + afn: Callable[[Unpack[Ts]], Awaitable[RetT]], + *args: Unpack[Ts], trio_token: TrioToken | None = None, ) -> RetT: """Run the given async function in the parent Trio thread, blocking until it @@ -569,10 +570,9 @@ def from_thread_run( # type: ignore[misc] return _send_message_to_trio(trio_token, Run(afn, args)) -# Explicit "Any" is not allowed -def from_thread_run_sync( # type: ignore[misc] - fn: Callable[..., RetT], - *args: object, +def from_thread_run_sync( + fn: Callable[[Unpack[Ts]], RetT], + *args: Unpack[Ts], trio_token: TrioToken | None = None, ) -> RetT: """Run the given sync function in the parent Trio thread, blocking until it diff --git a/src/trio/_util.py b/src/trio/_util.py index 8da2ef4ad..9b8b1d7a4 100644 --- a/src/trio/_util.py +++ b/src/trio/_util.py @@ -21,7 +21,7 @@ import trio # Explicit "Any" is not allowed -CallT = TypeVar("CallT", bound=Callable[..., Any]) # type: ignore[misc] +CallT = TypeVar("CallT", bound=Callable[..., Any]) # type: ignore[explicit-any] T = TypeVar("T") RetT = TypeVar("RetT") @@ -177,16 +177,14 @@ def __exit__( self._held = False -# Explicit "Any" is not allowed -def async_wraps( # type: ignore[misc] +def async_wraps( # type: ignore[explicit-any] cls: type[object], wrapped_cls: type[object], attr_name: str, ) -> Callable[[CallT], CallT]: """Similar to wraps, but for async wrappers of non-async functions.""" - # Explicit "Any" is not allowed - def decorator(func: CallT) -> CallT: # type: ignore[misc] + def decorator(func: CallT) -> CallT: # type: ignore[explicit-any] func.__name__ = attr_name func.__qualname__ = f"{cls.__qualname__}.{attr_name}" @@ -249,8 +247,7 @@ def open_memory_channel(max_buffer_size: int) -> Tuple[ but at least it becomes possible to write those. """ - # Explicit .../"Any" is not allowed - def __init__( # type: ignore[misc] + def __init__( # type: ignore[explicit-any] self, fn: Callable[..., RetT], ) -> None: @@ -346,11 +343,9 @@ def name_asyncgen(agen: AsyncGeneratorType[object, NoReturn]) -> str: # work around a pyright error if TYPE_CHECKING: - # Explicit .../"Any" is not allowed - Fn = TypeVar("Fn", bound=Callable[..., object]) # type: ignore[misc] + Fn = TypeVar("Fn", bound=Callable[..., object]) # type: ignore[explicit-any] - # Explicit .../"Any" is not allowed - def wraps( # type: ignore[misc] + def wraps( # type: ignore[explicit-any] wrapped: Callable[..., object], assigned: Sequence[str] = ..., updated: Sequence[str] = ..., diff --git a/src/trio/testing/_fake_net.py b/src/trio/testing/_fake_net.py index e036c78c0..d9d061c54 100644 --- a/src/trio/testing/_fake_net.py +++ b/src/trio/testing/_fake_net.py @@ -507,8 +507,7 @@ async def sendto( __address: tuple[object, ...] | str | Buffer | None, ) -> int: ... - # Explicit "Any" is not allowed - async def sendto( # type: ignore[misc] + async def sendto( # type: ignore[explicit-any] self, *args: Any, ) -> int: From 8209ec1cd77e4a0820f069682e93f49ea2a10a1d Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Fri, 7 Feb 2025 16:06:30 -0700 Subject: [PATCH 5/8] cr --- src/trio/_core/_run.py | 5 +++-- src/trio/_core/_tests/test_asyncgen.py | 11 +++++------ src/trio/_file_io.py | 2 +- src/trio/_tests/test_socket.py | 10 ++-------- src/trio/_threads.py | 2 +- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py index 8649c0f70..24f928507 100644 --- a/src/trio/_core/_run.py +++ b/src/trio/_core/_run.py @@ -7,6 +7,7 @@ import random import select import sys +import types import warnings from collections import deque from contextlib import AbstractAsyncContextManager, contextmanager, suppress @@ -57,7 +58,6 @@ if TYPE_CHECKING: import contextvars - import types from collections.abc import ( Awaitable, Callable, @@ -1886,7 +1886,8 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT: return await orig_coro coro = python_wrapper(coro) - assert coro.cr_frame is not None, "Coroutine frame should exist" # type: ignore[attr-defined] + assert isinstance(coro, types.CoroutineType) + assert coro.cr_frame is not None, "Coroutine frame should exist" ###### # Set up the Task object diff --git a/src/trio/_core/_tests/test_asyncgen.py b/src/trio/_core/_tests/test_asyncgen.py index 232dd5b1e..8147a0e57 100644 --- a/src/trio/_core/_tests/test_asyncgen.py +++ b/src/trio/_core/_tests/test_asyncgen.py @@ -5,15 +5,13 @@ import weakref from math import inf from types import AsyncGeneratorType -from typing import TYPE_CHECKING, NoReturn, TypeAlias, cast +from typing import TYPE_CHECKING, NoReturn import pytest from ... import _core from .tutil import gc_collect_harder, restore_unraisablehook -AGT: TypeAlias = AsyncGeneratorType[int, None] - if TYPE_CHECKING: from collections.abc import AsyncGenerator @@ -71,26 +69,27 @@ async def async_main() -> None: # No problems saving the geniter when using either of these patterns aiter_ = example("exhausted 3") try: - saved.append(cast("AGT", aiter_)) + saved.append(aiter_) assert await aiter_.asend(None) == 42 finally: await aiter_.aclose() assert collected.pop() == "exhausted 3" # Also fine if you exhaust it at point of use - saved.append(cast("AGT", example("exhausted 4"))) + saved.append(example("exhausted 4")) async for val in saved[-1]: assert val == 42 assert collected.pop() == "exhausted 4" # Leave one referenced-but-unexhausted and make sure it gets cleaned up - saved.append(cast("AGT", example("outlived run"))) + saved.append(example("outlived run")) assert await saved[-1].asend(None) == 42 assert collected == [] _core.run(async_main) assert collected.pop() == "outlived run" for agen in saved: + assert isinstance(agen, AsyncGeneratorType) assert agen.ag_frame is None # all should now be exhausted diff --git a/src/trio/_file_io.py b/src/trio/_file_io.py index e6a75c7ff..443dcdfb4 100644 --- a/src/trio/_file_io.py +++ b/src/trio/_file_io.py @@ -428,7 +428,7 @@ async def open_file( @overload -async def open_file( # type: ignore[explicit-any,misc] # Any usage matches builtins.open(). +async def open_file( # type: ignore[explicit-any, misc] # Any usage matches builtins.open(). file: _OpenFile, mode: str, buffering: int = -1, diff --git a/src/trio/_tests/test_socket.py b/src/trio/_tests/test_socket.py index b26d5dde7..5cd48ad1b 100644 --- a/src/trio/_tests/test_socket.py +++ b/src/trio/_tests/test_socket.py @@ -186,10 +186,7 @@ def interesting_fields( ) -> tuple[ AddressFamily, SocketKind, - tuple[str, int] - | tuple[str, int, int] - | tuple[str, int, int, int] - | tuple[int, bytes], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ]: # (family, type, proto, canonname, sockaddr) family, type_, _proto, _canonname, sockaddr = gai_tup @@ -201,10 +198,7 @@ def filtered( tuple[ AddressFamily, SocketKind, - tuple[str, int] - | tuple[str, int, int] - | tuple[str, int, int, int] - | tuple[int, bytes], + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], ] ]: return [interesting_fields(gai_tup) for gai_tup in gai_list] diff --git a/src/trio/_threads.py b/src/trio/_threads.py index 2b4eb9115..394e5b06a 100644 --- a/src/trio/_threads.py +++ b/src/trio/_threads.py @@ -253,7 +253,7 @@ def run_in_system_nursery(self, token: TrioToken) -> None: token.run_sync_soon(self.run_sync) -@enable_ki_protection # Decorator used on function with Coroutine[Any, Any, RetT] +@enable_ki_protection async def to_thread_run_sync( sync_fn: Callable[[Unpack[Ts]], RetT], *args: Unpack[Ts], From 2b58b547dad88a4d4ab9234dcb3b159d343dd3aa Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Mon, 10 Feb 2025 08:54:21 -0700 Subject: [PATCH 6/8] more cr --- .pre-commit-config.yaml | 1 + newsfragments/3201.misc.rst | 5 +++++ src/trio/_core/_run.py | 5 ++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7aed67fcf..b76b62176 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,7 @@ repos: hooks: - id: codespell additional_dependencies: + # tomli needed on 3.10. tomllib is available in stdlib on 3.11+ - tomli - repo: https://github.com/crate-ci/typos rev: typos-dict-v0.12.4 diff --git a/newsfragments/3201.misc.rst b/newsfragments/3201.misc.rst index 309f27cdf..de8545115 100644 --- a/newsfragments/3201.misc.rst +++ b/newsfragments/3201.misc.rst @@ -3,3 +3,8 @@ match that of the stdlib `socket.getaddrinfo`, which was updated in mypy 1.15 (v a typeshed update) to include the possibility of ``tuple[int, bytes]`` for the ``sockaddr`` field of the result. This happens in situations where Python was compiled with ``--disable-ipv6``. + +Additionally, when using mypy 1.15, the static typing of +:func:`trio.to_thread.run_sync`, :func:`trio.from_thread.run` and +:func:`trio.from_thread.run_sync` has been improved and should reflect the underlying +function being run. diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py index 24f928507..8649c0f70 100644 --- a/src/trio/_core/_run.py +++ b/src/trio/_core/_run.py @@ -7,7 +7,6 @@ import random import select import sys -import types import warnings from collections import deque from contextlib import AbstractAsyncContextManager, contextmanager, suppress @@ -58,6 +57,7 @@ if TYPE_CHECKING: import contextvars + import types from collections.abc import ( Awaitable, Callable, @@ -1886,8 +1886,7 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT: return await orig_coro coro = python_wrapper(coro) - assert isinstance(coro, types.CoroutineType) - assert coro.cr_frame is not None, "Coroutine frame should exist" + assert coro.cr_frame is not None, "Coroutine frame should exist" # type: ignore[attr-defined] ###### # Set up the Task object From 3caa5d1325fd00500bbd3c4b494e6f38cfb508c0 Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Mon, 10 Feb 2025 09:27:25 -0700 Subject: [PATCH 7/8] more cr --- newsfragments/3201.misc.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/newsfragments/3201.misc.rst b/newsfragments/3201.misc.rst index de8545115..5b77206ad 100644 --- a/newsfragments/3201.misc.rst +++ b/newsfragments/3201.misc.rst @@ -4,7 +4,6 @@ a typeshed update) to include the possibility of ``tuple[int, bytes]`` for the ``sockaddr`` field of the result. This happens in situations where Python was compiled with ``--disable-ipv6``. -Additionally, when using mypy 1.15, the static typing of -:func:`trio.to_thread.run_sync`, :func:`trio.from_thread.run` and -:func:`trio.from_thread.run_sync` has been improved and should reflect the underlying -function being run. +Additionally, the static typing of :func:`trio.to_thread.run_sync`, +:func:`trio.from_thread.run` and :func:`trio.from_thread.run_sync` has been +improved and should reflect the underlying function being run. From 3d61e028f2afaa388da0794a84c5215bd1ca0136 Mon Sep 17 00:00:00 2001 From: Tim Stumbaugh Date: Tue, 11 Feb 2025 09:28:14 -0700 Subject: [PATCH 8/8] _public --- src/trio/_core/_instrumentation.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/trio/_core/_instrumentation.py b/src/trio/_core/_instrumentation.py index afdff19cd..57f351d18 100644 --- a/src/trio/_core/_instrumentation.py +++ b/src/trio/_core/_instrumentation.py @@ -10,17 +10,14 @@ INSTRUMENT_LOGGER = logging.getLogger("trio.abc.Instrument") if TYPE_CHECKING: - from collections.abc import Callable, Sequence + from collections.abc import Sequence - from typing_extensions import ParamSpec - - P = ParamSpec("P") T = TypeVar("T") # Decorator to mark methods public. This does nothing by itself, but # trio/_tools/gen_exports.py looks for it. -def _public(fn: Callable[P, T]) -> Callable[P, T]: +def _public(fn: T) -> T: return fn