diff --git a/check.sh b/check.sh index a0efa531b6..ace193a62a 100755 --- a/check.sh +++ b/check.sh @@ -27,9 +27,9 @@ fi flake8 trio/ || EXIT_STATUS=$? # Run mypy on all supported platforms -mypy -m trio -m trio.testing --platform linux || EXIT_STATUS=$? -mypy -m trio -m trio.testing --platform darwin || EXIT_STATUS=$? # tests FreeBSD too -mypy -m trio -m trio.testing --platform win32 || EXIT_STATUS=$? +mypy trio --platform linux || EXIT_STATUS=$? +mypy trio --platform darwin || EXIT_STATUS=$? # tests FreeBSD too +mypy trio --platform win32 || EXIT_STATUS=$? # Check pip compile is consistent pip-compile test-requirements.in diff --git a/pyproject.toml b/pyproject.toml index 073e2508d1..a212393452 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,70 +34,58 @@ warn_redundant_casts = true warn_return_any = true # Avoid subtle backsliding -#disallow_any_decorated = true -#disallow_incomplete_defs = true -#disallow_subclassing_any = true +disallow_any_decorated = true +disallow_any_generics = true +disallow_any_unimported = false # Enable once Outcome has stubs. +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_decorators = true +disallow_untyped_defs = true -# Enable gradually / for new modules +# Enable once other problems are dealt with check_untyped_defs = false disallow_untyped_calls = false -disallow_untyped_defs = false -# DO NOT use `ignore_errors`; it doesn't apply -# downstream and users have to deal with them. -[[tool.mypy.overrides]] -# Fully typed, enable stricter checks -module = [ - "trio._abc", - "trio._core._asyncgens", - "trio._core._entry_queue", - "trio._core._generated_io_epoll", - "trio._core._generated_io_kqueue", - "trio._core._generated_run", - "trio._core._io_epoll", - "trio._core._io_kqueue", - "trio._core._local", - "trio._core._multierror", - "trio._core._run", - "trio._core._thread_cache", - "trio._core._traps", - "trio._core._unbounded_queue", - "trio._core._unbounded_queue", - "trio._deprecate", - "trio._dtls", - "trio._file_io", - "trio._highlevel_open_tcp_stream", - "trio._ki", - "trio._socket", - "trio._subprocess", - "trio._subprocess_platform", - "trio._subprocess_platform.kqueue", - "trio._subprocess_platform.waitid", - "trio._subprocess_platform.windows", - "trio._sync", - "trio._threads", - "trio._tools.gen_exports", - "trio._util", -] -disallow_incomplete_defs = true -disallow_untyped_defs = true -disallow_untyped_decorators = true -disallow_any_generics = true -disallow_any_decorated = true -disallow_any_unimported = false # Enable once outcome has stubs. -disallow_subclassing_any = true +# files not yet fully typed [[tool.mypy.overrides]] -# Needs to use Any due to some complex introspection. module = [ - "trio._path", +# 2747 +"trio/testing/_network", +"trio/testing/_trio_test", +"trio/testing/_checkpoints", +"trio/testing/_check_streams", +"trio/testing/_memory_streams", +# 2745 +"trio/_ssl", +# 2756 +"trio/_highlevel_open_unix_stream", +"trio/_highlevel_serve_listeners", +"trio/_highlevel_ssl_helpers", +"trio/_highlevel_socket", +# 2755 +"trio/_core/_windows_cffi", +"trio/_wait_for_object", +# 2761 +"trio/_core/_generated_io_windows", +"trio/_core/_io_windows", + + +"trio/_signals", + +# internal +"trio/_windows_pipes", + +# tests +"trio/_core/_tests/*", +"trio/_tests/*", +"trio/testing/_fake_net", # 30 ] -disallow_incomplete_defs = true -disallow_untyped_defs = true -#disallow_any_generics = true -#disallow_any_decorated = true -disallow_any_unimported = true -disallow_subclassing_any = true +disallow_any_decorated = false +disallow_any_generics = false +disallow_any_unimported = false +disallow_incomplete_defs = false +disallow_untyped_defs = false [tool.pytest.ini_options] addopts = ["--strict-markers", "--strict-config"] diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py index 35ecd45a1b..bd5abbd639 100644 --- a/trio/_core/_generated_run.py +++ b/trio/_core/_generated_run.py @@ -88,7 +88,7 @@ def current_root_task() ->(Task | None): raise RuntimeError("must be called from async context") -def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None: # type: ignore[has-type] +def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None: """Reschedule the given task with the given :class:`outcome.Outcome`. diff --git a/trio/_core/_io_common.py b/trio/_core/_io_common.py index b141474fda..c1af293278 100644 --- a/trio/_core/_io_common.py +++ b/trio/_core/_io_common.py @@ -1,12 +1,18 @@ +from __future__ import annotations + import copy +from typing import TYPE_CHECKING import outcome from .. import _core +if TYPE_CHECKING: + from ._io_epoll import EpollWaiters + # Utility function shared between _io_epoll and _io_windows -def wake_all(waiters, exc): +def wake_all(waiters: EpollWaiters, exc: BaseException) -> None: try: current_task = _core.current_task() except RuntimeError: diff --git a/trio/_core/_ki.py b/trio/_core/_ki.py index 8ae83c287a..10172e4989 100644 --- a/trio/_core/_ki.py +++ b/trio/_core/_ki.py @@ -121,7 +121,7 @@ def currently_ki_protected() -> bool: # see python-trio/async_generator/async_generator/_impl.py def legacy_isasyncgenfunction( obj: object, -) -> TypeGuard[Callable[..., types.AsyncGeneratorType]]: +) -> TypeGuard[Callable[..., types.AsyncGeneratorType[object, object]]]: return getattr(obj, "_async_gen_function", None) == id(obj) @@ -196,7 +196,9 @@ def wrapper(*args: ArgsT.args, **kwargs: ArgsT.kwargs) -> RetT: @attr.s class KIManager: - handler = attr.ib(default=None) + handler: Callable[[int, types.FrameType | None], None] | None = attr.ib( + default=None + ) def install( self, @@ -221,7 +223,7 @@ def handler(signum: int, frame: types.FrameType | None) -> None: self.handler = handler signal.signal(signal.SIGINT, handler) - def close(self): + def close(self) -> None: if self.handler is not None: if signal.getsignal(signal.SIGINT) is self.handler: signal.signal(signal.SIGINT, signal.default_int_handler) diff --git a/trio/_core/_parking_lot.py b/trio/_core/_parking_lot.py index 74708433da..6510745e5b 100644 --- a/trio/_core/_parking_lot.py +++ b/trio/_core/_parking_lot.py @@ -139,7 +139,7 @@ async def park(self) -> None: self._parked[task] = None task.custom_sleep_data = self - def abort_fn(_): + def abort_fn(_: _core.RaiseCancelT) -> _core.Abort: del task.custom_sleep_data._parked[task] return _core.Abort.SUCCEEDED diff --git a/trio/_core/_tests/test_io.py b/trio/_core/_tests/test_io.py index 21a954941c..65b9b82bcf 100644 --- a/trio/_core/_tests/test_io.py +++ b/trio/_core/_tests/test_io.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import random import socket as stdlib_socket +from collections.abc import Callable from contextlib import suppress import pytest @@ -47,15 +50,15 @@ def fileno_wrapper(fileobj): return fileno_wrapper -wait_readable_options = [trio.lowlevel.wait_readable] -wait_writable_options = [trio.lowlevel.wait_writable] -notify_closing_options = [trio.lowlevel.notify_closing] +wait_readable_options: list[Callable] = [trio.lowlevel.wait_readable] +wait_writable_options: list[Callable] = [trio.lowlevel.wait_writable] +notify_closing_options: list[Callable] = [trio.lowlevel.notify_closing] -for options_list in [ +for options_list in ( wait_readable_options, wait_writable_options, notify_closing_options, -]: +): options_list += [using_fileno(f) for f in options_list] # Decorators that feed in different settings for wait_readable / wait_writable diff --git a/trio/_core/_tests/test_ki.py b/trio/_core/_tests/test_ki.py index fdbada4624..b6eef68e22 100644 --- a/trio/_core/_tests/test_ki.py +++ b/trio/_core/_tests/test_ki.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import contextlib import inspect import signal import threading +from typing import TYPE_CHECKING import outcome import pytest @@ -16,6 +19,9 @@ from ..._util import signal_raise from ...testing import wait_all_tasks_blocked +if TYPE_CHECKING: + from ..._core import Abort, RaiseCancelT + def ki_self(): signal_raise(signal.SIGINT) @@ -375,7 +381,7 @@ async def main(): ki_self() task = _core.current_task() - def abort(_): + def abort(_: RaiseCancelT) -> Abort: _core.reschedule(task, outcome.Value(1)) return _core.Abort.FAILED @@ -394,7 +400,7 @@ async def main(): ki_self() task = _core.current_task() - def abort(raise_cancel): + def abort(raise_cancel: RaiseCancelT) -> Abort: result = outcome.capture(raise_cancel) _core.reschedule(task, result) return _core.Abort.FAILED diff --git a/trio/_core/_tests/test_multierror.py b/trio/_core/_tests/test_multierror.py index 7a8bd2f9a8..52e5e39d1b 100644 --- a/trio/_core/_tests/test_multierror.py +++ b/trio/_core/_tests/test_multierror.py @@ -555,7 +555,7 @@ def test_apport_excepthook_monkeypatch_interaction(): @pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1)) -def test_pickle_multierror(protocol) -> None: +def test_pickle_multierror(protocol: int) -> None: # use trio.MultiError to make sure that pickle works through the deprecation layer import trio diff --git a/trio/_core/_tests/test_multierror_scripts/apport_excepthook.py b/trio/_core/_tests/test_multierror_scripts/apport_excepthook.py index 3e1d23ca8e..0e46f37e17 100644 --- a/trio/_core/_tests/test_multierror_scripts/apport_excepthook.py +++ b/trio/_core/_tests/test_multierror_scripts/apport_excepthook.py @@ -3,13 +3,13 @@ # make sure it's on sys.path. import sys -import _common +import _common # isort: split sys.path.append("/usr/lib/python3/dist-packages") import apport_python_hook apport_python_hook.install() -import trio +from trio._core._multierror import MultiError # Bypass deprecation warnings -raise trio.MultiError([KeyError("key_error"), ValueError("value_error")]) +raise MultiError([KeyError("key_error"), ValueError("value_error")]) diff --git a/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py b/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py index 80e42b6a2c..7ccb341dc9 100644 --- a/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py +++ b/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py @@ -3,7 +3,7 @@ # about it. import sys -import _common +import _common # isort: split def custom_excepthook(*args): @@ -29,8 +29,8 @@ def custom_exc_hook(etype, value, tb, tb_offset=None): ip.set_custom_exc((SomeError,), custom_exc_hook) -import trio +from trio._core._multierror import MultiError # Bypass deprecation warnings. # The custom excepthook should run, because Trio was polite and didn't # override it -raise trio.MultiError([ValueError(), KeyError()]) +raise MultiError([ValueError(), KeyError()]) diff --git a/trio/_core/_tests/test_multierror_scripts/simple_excepthook.py b/trio/_core/_tests/test_multierror_scripts/simple_excepthook.py index 94004525db..65371107bc 100644 --- a/trio/_core/_tests/test_multierror_scripts/simple_excepthook.py +++ b/trio/_core/_tests/test_multierror_scripts/simple_excepthook.py @@ -1,6 +1,6 @@ -import _common +import _common # isort: split -import trio +from trio._core._multierror import MultiError # Bypass deprecation warnings def exc1_fn(): @@ -18,4 +18,4 @@ def exc2_fn(): # This should be printed nicely, because Trio overrode sys.excepthook -raise trio.MultiError([exc1_fn(), exc2_fn()]) +raise MultiError([exc1_fn(), exc2_fn()]) diff --git a/trio/_core/_tests/test_run.py b/trio/_core/_tests/test_run.py index 81c3b73cc4..6d34d8f223 100644 --- a/trio/_core/_tests/test_run.py +++ b/trio/_core/_tests/test_run.py @@ -1954,7 +1954,7 @@ async def test_Nursery_private_init(): def test_Nursery_subclass(): with pytest.raises(TypeError): - class Subclass(_core._run.Nursery): + class Subclass(_core._run.Nursery): # type: ignore[misc] pass @@ -1984,7 +1984,7 @@ class Subclass(_core.Cancelled): def test_CancelScope_subclass(): with pytest.raises(TypeError): - class Subclass(_core.CancelScope): + class Subclass(_core.CancelScope): # type: ignore[misc] pass diff --git a/trio/_core/_wakeup_socketpair.py b/trio/_core/_wakeup_socketpair.py index 51a80ef024..2ad1a023fe 100644 --- a/trio/_core/_wakeup_socketpair.py +++ b/trio/_core/_wakeup_socketpair.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import signal import socket import warnings @@ -7,7 +9,7 @@ class WakeupSocketpair: - def __init__(self): + def __init__(self) -> None: self.wakeup_sock, self.write_sock = socket.socketpair() self.wakeup_sock.setblocking(False) self.write_sock.setblocking(False) @@ -27,26 +29,26 @@ def __init__(self): self.write_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) except OSError: pass - self.old_wakeup_fd = None + self.old_wakeup_fd: int | None = None - def wakeup_thread_and_signal_safe(self): + def wakeup_thread_and_signal_safe(self) -> None: try: self.write_sock.send(b"\x00") except BlockingIOError: pass - async def wait_woken(self): + async def wait_woken(self) -> None: await _core.wait_readable(self.wakeup_sock) self.drain() - def drain(self): + def drain(self) -> None: try: while True: self.wakeup_sock.recv(2**16) except BlockingIOError: pass - def wakeup_on_signals(self): + def wakeup_on_signals(self) -> None: assert self.old_wakeup_fd is None if not is_main_thread(): return @@ -64,7 +66,7 @@ def wakeup_on_signals(self): ) ) - def close(self): + def close(self) -> None: self.wakeup_sock.close() self.write_sock.close() if self.old_wakeup_fd is not None: diff --git a/trio/_path.py b/trio/_path.py index b7e6b16e4a..c2763e03af 100644 --- a/trio/_path.py +++ b/trio/_path.py @@ -116,9 +116,9 @@ async def wrapper(self: Path, *args: P.args, **kwargs: P.kwargs) -> Path: def classmethod_wrapper_factory( cls: AsyncAutoWrapperType, meth_name: str -) -> classmethod: +) -> classmethod: # type: ignore[type-arg] @async_wraps(cls, cls._wraps, meth_name) - async def wrapper(cls: type[Path], *args: Any, **kwargs: Any) -> Path: + async def wrapper(cls: type[Path], *args: Any, **kwargs: Any) -> Path: # type: ignore[misc] # contains Any meth = getattr(cls._wraps, meth_name) func = partial(meth, *args, **kwargs) value = await trio.to_thread.run_sync(func) @@ -163,7 +163,7 @@ def generate_forwards(cls, attrs: dict[str, object]) -> None: def generate_wraps(cls, attrs: dict[str, object]) -> None: # generate wrappers for functions of _wraps - wrapper: classmethod | Callable + wrapper: classmethod | Callable[..., object] # type: ignore[type-arg] for attr_name, attr in cls._wraps.__dict__.items(): # .z. exclude cls._wrap_iter if attr_name.startswith("_") or attr_name in attrs: @@ -188,7 +188,7 @@ def generate_magic(cls, attrs: dict[str, object]) -> None: def generate_iter(cls, attrs: dict[str, object]) -> None: # generate wrappers for methods that return iterators - wrapper: Callable + wrapper: Callable[..., object] for attr_name, attr in cls._wraps.__dict__.items(): if attr_name in cls._wrap_iter: wrapper = iter_wrapper_factory(cls, attr_name) diff --git a/trio/_tests/check_type_completeness.py b/trio/_tests/check_type_completeness.py index 7a65a4249e..abaabcf785 100755 --- a/trio/_tests/check_type_completeness.py +++ b/trio/_tests/check_type_completeness.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from __future__ import annotations + # this file is not run as part of the tests, instead it's run standalone from check.sh import argparse import json diff --git a/trio/_tests/test_contextvars.py b/trio/_tests/test_contextvars.py index 63853f5171..ae0c25f876 100644 --- a/trio/_tests/test_contextvars.py +++ b/trio/_tests/test_contextvars.py @@ -1,15 +1,19 @@ +from __future__ import annotations + import contextvars from .. import _core -trio_testing_contextvar = contextvars.ContextVar("trio_testing_contextvar") +trio_testing_contextvar: contextvars.ContextVar[str] = contextvars.ContextVar( + "trio_testing_contextvar" +) -async def test_contextvars_default(): +async def test_contextvars_default() -> None: trio_testing_contextvar.set("main") - record = [] + record: list[str] = [] - async def child(): + async def child() -> None: value = trio_testing_contextvar.get() record.append(value) @@ -18,11 +22,11 @@ async def child(): assert record == ["main"] -async def test_contextvars_set(): +async def test_contextvars_set() -> None: trio_testing_contextvar.set("main") - record = [] + record: list[str] = [] - async def child(): + async def child() -> None: trio_testing_contextvar.set("child") value = trio_testing_contextvar.get() record.append(value) @@ -34,13 +38,13 @@ async def child(): assert value == "main" -async def test_contextvars_copy(): +async def test_contextvars_copy() -> None: trio_testing_contextvar.set("main") context = contextvars.copy_context() trio_testing_contextvar.set("second_main") - record = [] + record: list[str] = [] - async def child(): + async def child() -> None: value = trio_testing_contextvar.get() record.append(value) diff --git a/trio/_tests/test_dtls.py b/trio/_tests/test_dtls.py index b8c32c6d5f..8cb06ccb3d 100644 --- a/trio/_tests/test_dtls.py +++ b/trio/_tests/test_dtls.py @@ -17,10 +17,10 @@ ca = trustme.CA() server_cert = ca.issue_cert("example.com") -server_ctx = SSL.Context(SSL.DTLS_METHOD) +server_ctx = SSL.Context(SSL.DTLS_METHOD) # type: ignore[attr-defined] server_cert.configure_cert(server_ctx) -client_ctx = SSL.Context(SSL.DTLS_METHOD) +client_ctx = SSL.Context(SSL.DTLS_METHOD) # type: ignore[attr-defined] ca.configure_trust(client_ctx) diff --git a/trio/_tests/test_exports.py b/trio/_tests/test_exports.py index c0da975fb4..2f1157db06 100644 --- a/trio/_tests/test_exports.py +++ b/trio/_tests/test_exports.py @@ -1,3 +1,4 @@ +from __future__ import annotations # isort: split import __future__ # Regular import, not special! import enum @@ -27,7 +28,7 @@ try: # If installed, check both versions of this class. from typing_extensions import Protocol as Protocol_ext except ImportError: # pragma: no cover - Protocol_ext = Protocol + Protocol_ext = Protocol # type: ignore[assignment] def _ensure_mypy_cache_updated(): @@ -240,7 +241,9 @@ def no_underscores(symbols): ) @pytest.mark.parametrize("module_name", PUBLIC_MODULE_NAMES) @pytest.mark.parametrize("tool", ["jedi", "mypy"]) -def test_static_tool_sees_class_members(tool, module_name, tmpdir) -> None: +def test_static_tool_sees_class_members( + tool: str, module_name: str, tmpdir: Path +) -> None: module = PUBLIC_MODULES[PUBLIC_MODULE_NAMES.index(module_name)] # ignore hidden, but not dunder, symbols @@ -481,7 +484,7 @@ def lookup_symbol(symbol): assert not errors -def test_classes_are_final(): +def test_classes_are_final() -> None: for module in PUBLIC_MODULES: for name, class_ in module.__dict__.items(): if not isinstance(class_, type): @@ -503,7 +506,7 @@ def test_classes_are_final(): continue # These are classes that are conceptually abstract, but # inspect.isabstract returns False for boring reasons. - if class_ in {trio.abc.Instrument, trio.socket.SocketType}: + if class_ is trio.abc.Instrument or class_ is trio.socket.SocketType: continue # Enums have their own metaclass, so we can't use our metaclasses. # And I don't think there's a lot of risk from people subclassing diff --git a/trio/_tests/test_fakenet.py b/trio/_tests/test_fakenet.py index bc691c9db5..d250a105a3 100644 --- a/trio/_tests/test_fakenet.py +++ b/trio/_tests/test_fakenet.py @@ -1,16 +1,18 @@ +import errno + import pytest import trio from trio.testing._fake_net import FakeNet -def fn(): +def fn() -> FakeNet: fn = FakeNet() fn.enable() return fn -async def test_basic_udp(): +async def test_basic_udp() -> None: fn() s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM) s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM) @@ -19,6 +21,11 @@ async def test_basic_udp(): ip, port = s1.getsockname() assert ip == "127.0.0.1" assert port != 0 + + with pytest.raises(OSError) as exc: # Cannot rebind. + await s1.bind(("192.0.2.1", 0)) + assert exc.value.errno == errno.EINVAL + await s2.sendto(b"xyz", s1.getsockname()) data, addr = await s1.recvfrom(10) assert data == b"xyz" @@ -29,7 +36,7 @@ async def test_basic_udp(): assert addr == s1.getsockname() -async def test_msg_trunc(): +async def test_msg_trunc() -> None: fn() s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM) s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM) @@ -38,7 +45,7 @@ async def test_msg_trunc(): data, addr = await s1.recvfrom(10) -async def test_basic_tcp(): +async def test_basic_tcp() -> None: fn() with pytest.raises(NotImplementedError): trio.socket.socket() diff --git a/trio/_tests/test_highlevel_serve_listeners.py b/trio/_tests/test_highlevel_serve_listeners.py index 4385263899..65804f4222 100644 --- a/trio/_tests/test_highlevel_serve_listeners.py +++ b/trio/_tests/test_highlevel_serve_listeners.py @@ -12,7 +12,9 @@ class MemoryListener(trio.abc.Listener): closed = attr.ib(default=False) accepted_streams = attr.ib(factory=list) - queued_streams = attr.ib(factory=(lambda: trio.open_memory_channel(1))) + queued_streams = attr.ib( + factory=(lambda: trio.open_memory_channel[trio.StapledStream](1)) + ) accept_hook = attr.ib(default=None) async def connect(self): diff --git a/trio/_tests/test_socket.py b/trio/_tests/test_socket.py index e9baff436a..f01b4fde14 100644 --- a/trio/_tests/test_socket.py +++ b/trio/_tests/test_socket.py @@ -360,7 +360,7 @@ async def test_SocketType_basics(): sock.close() -async def test_SocketType_setsockopt(): +async def test_SocketType_setsockopt() -> None: sock = tsocket.socket() with sock as _: # specifying optlen. Not supported on pypy, and I couldn't find diff --git a/trio/_tests/test_subprocess.py b/trio/_tests/test_subprocess.py index 4dfaef4c7f..7986dfd71e 100644 --- a/trio/_tests/test_subprocess.py +++ b/trio/_tests/test_subprocess.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import random import signal @@ -6,6 +8,7 @@ from contextlib import asynccontextmanager from functools import partial from pathlib import Path as SyncPath +from typing import TYPE_CHECKING import pytest @@ -24,8 +27,15 @@ from ..lowlevel import open_process from ..testing import assert_no_checkpoints, wait_all_tasks_blocked +if TYPE_CHECKING: + ... + from signal import Signals + posix = os.name == "posix" -if posix: +SIGKILL: Signals | None +SIGTERM: Signals | None +SIGUSR1: Signals | None +if (not TYPE_CHECKING and posix) or sys.platform != "win32": from signal import SIGKILL, SIGTERM, SIGUSR1 else: SIGKILL, SIGTERM, SIGUSR1 = None, None, None @@ -574,7 +584,7 @@ async def test_for_leaking_fds(): async def test_subprocess_pidfd_unnotified(): noticed_exit = None - async def wait_and_tell(proc) -> None: + async def wait_and_tell(proc: Process) -> None: nonlocal noticed_exit noticed_exit = Event() await proc.wait() diff --git a/trio/_tests/test_threads.py b/trio/_tests/test_threads.py index 21eb7b12e8..a2988ed0ff 100644 --- a/trio/_tests/test_threads.py +++ b/trio/_tests/test_threads.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextvars import queue as stdlib_queue import re @@ -170,7 +172,7 @@ async def main(): async def test_named_thread(): ending = " from trio._tests.test_threads.test_named_thread" - def inner(name="inner" + ending) -> threading.Thread: + def inner(name: str = "inner" + ending) -> threading.Thread: assert threading.current_thread().name == name return threading.current_thread() @@ -185,7 +187,7 @@ def f(name: str) -> Callable[[None], threading.Thread]: await to_thread_run_sync(f("None" + ending)) # test that you can set a custom name, and that it's reset afterwards - async def test_thread_name(name: str): + async def test_thread_name(name: str) -> None: thread = await to_thread_run_sync(f(name), thread_name=name) assert re.match("Trio thread [0-9]*", thread.name) @@ -235,7 +237,7 @@ def _get_thread_name(ident: Optional[int] = None) -> Optional[str]: # and most mac machines. So unless the platform is linux it will just skip # in case it fails to fetch the os thread name. async def test_named_thread_os(): - def inner(name) -> threading.Thread: + def inner(name: str) -> threading.Thread: os_thread_name = _get_thread_name() if os_thread_name is None and sys.platform != "linux": pytest.skip(f"no pthread OS support on {sys.platform}") @@ -253,7 +255,7 @@ def f(name: str) -> Callable[[None], threading.Thread]: await to_thread_run_sync(f(default), thread_name=None) # test that you can set a custom name, and that it's reset afterwards - async def test_thread_name(name: str, expected: Optional[str] = None): + async def test_thread_name(name: str, expected: Optional[str] = None) -> None: if expected is None: expected = name thread = await to_thread_run_sync(f(expected), thread_name=name) @@ -584,7 +586,9 @@ async def async_fn(): # pragma: no cover await to_thread_run_sync(async_fn) -trio_test_contextvar = contextvars.ContextVar("trio_test_contextvar") +trio_test_contextvar: contextvars.ContextVar[str] = contextvars.ContextVar( + "trio_test_contextvar" +) async def test_trio_to_thread_run_sync_contextvars(): diff --git a/trio/_tests/test_tracing.py b/trio/_tests/test_tracing.py index 07d1ff7609..e5110eaff3 100644 --- a/trio/_tests/test_tracing.py +++ b/trio/_tests/test_tracing.py @@ -1,26 +1,26 @@ import trio -async def coro1(event: trio.Event): +async def coro1(event: trio.Event) -> None: event.set() await trio.sleep_forever() -async def coro2(event: trio.Event): +async def coro2(event: trio.Event) -> None: await coro1(event) -async def coro3(event: trio.Event): +async def coro3(event: trio.Event) -> None: await coro2(event) -async def coro2_async_gen(event: trio.Event): +async def coro2_async_gen(event): yield await trio.lowlevel.checkpoint() yield await coro1(event) yield await trio.lowlevel.checkpoint() -async def coro3_async_gen(event: trio.Event): +async def coro3_async_gen(event: trio.Event) -> None: async for x in coro2_async_gen(event): pass diff --git a/trio/_tests/test_unix_pipes.py b/trio/_tests/test_unix_pipes.py index acee75aafb..0b0d2ceb23 100644 --- a/trio/_tests/test_unix_pipes.py +++ b/trio/_tests/test_unix_pipes.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import errno import os import select import sys +from typing import TYPE_CHECKING import pytest @@ -11,6 +14,9 @@ posix = os.name == "posix" pytestmark = pytest.mark.skipif(not posix, reason="posix only") + +assert not TYPE_CHECKING or sys.platform == "unix" + if posix: from .._unix_pipes import FdStream else: @@ -19,7 +25,7 @@ # Have to use quoted types so import doesn't crash on windows -async def make_pipe() -> "Tuple[FdStream, FdStream]": +async def make_pipe() -> "tuple[FdStream, FdStream]": """Makes a new pair of pipes.""" (r, w) = os.pipe() return FdStream(w), FdStream(r) diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py index 9d78cd5bd7..3c598e8eae 100755 --- a/trio/_tools/gen_exports.py +++ b/trio/_tools/gen_exports.py @@ -158,13 +158,6 @@ def gen_public_wrappers_source(file: File) -> str: if is_cm: # pragma: no cover func = func.replace("->Iterator", "->ContextManager") - # TODO: hacky workaround until we run mypy without `-m`, which breaks imports - # enough that it cannot figure out the type of _NO_SEND - if file.path.stem == "_run" and func.startswith( - "def reschedule" - ): # pragma: no cover - func = func.replace("None:\n", "None: # type: ignore[has-type]\n") - # Create export function body template = TEMPLATE.format( " await " if isinstance(method, ast.AsyncFunctionDef) else " ", diff --git a/trio/_unix_pipes.py b/trio/_unix_pipes.py index 716550790e..1a389e12dd 100644 --- a/trio/_unix_pipes.py +++ b/trio/_unix_pipes.py @@ -2,6 +2,7 @@ import errno import os +import sys from typing import TYPE_CHECKING import trio @@ -12,6 +13,8 @@ if TYPE_CHECKING: from typing import Final as FinalType +assert not TYPE_CHECKING or sys.platform != "win32" + if os.name != "posix": # We raise an error here rather than gating the import in lowlevel.py # in order to keep jedi static analysis happy. diff --git a/trio/py.typed b/trio/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/trio/testing/_fake_net.py b/trio/testing/_fake_net.py index b3bdfd85c0..ddf46174f3 100644 --- a/trio/testing/_fake_net.py +++ b/trio/testing/_fake_net.py @@ -19,6 +19,7 @@ from trio._util import Final, NoPublicConstructor if TYPE_CHECKING: + from socket import AddressFamily, SocketKind from types import TracebackType IPAddress = Union[ipaddress.IPv4Address, ipaddress.IPv6Address] @@ -104,7 +105,7 @@ def reply(self, payload): class FakeSocketFactory(trio.abc.SocketFactory): fake_net: "FakeNet" - def socket(self, family: int, type: int, proto: int) -> "FakeSocket": + def socket(self, family: int, type: int, proto: int) -> FakeSocket: # type: ignore[override] return FakeSocket._create(self.fake_net, family, type, proto) @@ -113,22 +114,38 @@ class FakeHostnameResolver(trio.abc.HostnameResolver): fake_net: "FakeNet" async def getaddrinfo( - self, host: str, port: Union[int, str], family=0, type=0, proto=0, flags=0 - ): + self, + host: bytes | str | None, + port: bytes | str | int | None, + family: int = 0, + type: int = 0, + proto: int = 0, + flags: int = 0, + ) -> list[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int], + ] + ]: raise NotImplementedError("FakeNet doesn't do fake DNS yet") - async def getnameinfo(self, sockaddr, flags: int): + async def getnameinfo( + self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int + ) -> tuple[str, str]: raise NotImplementedError("FakeNet doesn't do fake DNS yet") class FakeNet(metaclass=Final): - def __init__(self): + def __init__(self) -> None: # When we need to pick an arbitrary unique ip address/port, use these: self._auto_ipv4_iter = ipaddress.IPv4Network("1.0.0.0/8").hosts() - self._auto_ipv4_iter = ipaddress.IPv6Network("1::/16").hosts() + self._auto_ipv4_iter = ipaddress.IPv6Network("1::/16").hosts() # type: ignore[assignment] self._auto_port_iter = iter(range(50000, 65535)) - self._bound: Dict[UDPBinding, FakeSocket] = {} + self._bound: dict[UDPBinding, FakeSocket] = {} self.route_packet = None @@ -176,9 +193,9 @@ def __init__(self, fake_net: FakeNet, family: int, type: int, proto: int): self._closed = False - self._packet_sender, self._packet_receiver = trio.open_memory_channel( - float("inf") - ) + self._packet_sender, self._packet_receiver = trio.open_memory_channel[ + UDPPacket + ](float("inf")) # This is the source-of-truth for what port etc. this socket is bound to self._binding: Optional[UDPBinding] = None @@ -206,7 +223,7 @@ async def _resolve_address_nocp(self, address, *, local): local=local, ) - def _deliver_packet(self, packet: UDPPacket): + def _deliver_packet(self, packet: UDPPacket) -> None: try: self._packet_sender.send_nowait(packet) except trio.BrokenResourceError: @@ -220,7 +237,7 @@ def _deliver_packet(self, packet: UDPPacket): async def bind(self, addr): self._check_closed() if self._binding is not None: - _fake_error(errno.EINVAL) + _fake_err(errno.EINVAL) await trio.lowlevel.checkpoint() ip_str, port = await self._resolve_address_nocp(addr, local=True) ip = ipaddress.ip_address(ip_str)