From 105f192c56c4c9d812b2124da6d16609ceb23235 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 11:25:36 +1000 Subject: [PATCH 01/18] Add type hints to _subprocess --- trio/_subprocess.py | 105 +++++++++++++++++---------- trio/_subprocess_platform/waitid.py | 2 +- trio/_subprocess_platform/windows.py | 3 +- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 1f8d0a8253..688dbbba3c 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -1,10 +1,14 @@ +from __future__ import annotations + import os +import signal import subprocess import sys import warnings +from collections.abc import Awaitable, Callable from contextlib import ExitStack from functools import partial -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Final, Protocol import trio @@ -65,6 +69,11 @@ def pidfd_open(fd: int, flags: int) -> int: can_try_pidfd_open = False +class HasFileno(Protocol): + def fileno(self) -> int: + ... + + class Process(AsyncResource, metaclass=NoPublicConstructor): r"""A child process. Like :class:`subprocess.Popen`, but async. @@ -107,23 +116,29 @@ class Process(AsyncResource, metaclass=NoPublicConstructor): available; otherwise this will be None. """ - - universal_newlines = False - encoding = None - errors = None + # We're always in binary mode. + universal_newlines: Final = False + encoding: Final = None + errors: Final = None # Available for the per-platform wait_child_exiting() implementations # to stash some state; waitid platforms use this to avoid spawning # arbitrarily many threads if wait() keeps getting cancelled. - _wait_for_exit_data = None - - def __init__(self, popen, stdin, stdout, stderr): + _wait_for_exit_data: object = None + + def __init__( + self, + popen: subprocess.Popen[bytes], + stdin: SendStream | None, + stdout: ReceiveStream | None, + stderr: ReceiveStream | None, + ) -> None: self._proc = popen - self.stdin: Optional[SendStream] = stdin - self.stdout: Optional[ReceiveStream] = stdout - self.stderr: Optional[ReceiveStream] = stderr + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr - self.stdio: Optional[StapledStream] = None + self.stdio: StapledStream | None = None if self.stdin is not None and self.stdout is not None: self.stdio = StapledStream(self.stdin, self.stdout) @@ -147,7 +162,7 @@ def __init__(self, popen, stdin, stdout, stderr): self.args = self._proc.args self.pid = self._proc.pid - def __repr__(self): + def __repr__(self) -> str: returncode = self.returncode if returncode is None: status = f"running with PID {self.pid}" @@ -159,7 +174,7 @@ def __repr__(self): return f"" @property - def returncode(self): + def returncode(self) -> int | None: """The exit status of the process (an integer), or ``None`` if it's still running. @@ -186,13 +201,13 @@ def returncode(self): issue=1104, instead="run_process or nursery.start(run_process, ...)", ) - async def __aenter__(self): + async def __aenter__(self) -> Process: return self @deprecated( "0.20.0", issue=1104, instead="run_process or nursery.start(run_process, ...)" ) - async def aclose(self): + async def aclose(self) -> None: """Close any pipes we have to the process (both input and output) and wait for it to exit. @@ -214,13 +229,13 @@ async def aclose(self): with trio.CancelScope(shield=True): await self.wait() - def _close_pidfd(self): + def _close_pidfd(self) -> None: if self._pidfd is not None: trio.lowlevel.notify_closing(self._pidfd.fileno()) self._pidfd.close() self._pidfd = None - async def wait(self): + async def wait(self) -> int: """Block until the process exits. Returns: @@ -248,7 +263,7 @@ async def wait(self): assert self._proc.returncode is not None return self._proc.returncode - def poll(self): + def poll(self) -> int | None: """Returns the exit status of the process (an integer), or ``None`` if it's still running. @@ -260,7 +275,7 @@ def poll(self): """ return self.returncode - def send_signal(self, sig): + def send_signal(self, sig: signal.Signals | int) -> None: """Send signal ``sig`` to the process. On UNIX, ``sig`` may be any signal defined in the @@ -270,7 +285,7 @@ def send_signal(self, sig): """ self._proc.send_signal(sig) - def terminate(self): + def terminate(self) -> None: """Terminate the process, politely if possible. On UNIX, this is equivalent to @@ -281,7 +296,7 @@ def terminate(self): """ self._proc.terminate() - def kill(self): + def kill(self) -> None: """Immediately terminate the process. On UNIX, this is equivalent to @@ -295,7 +310,12 @@ def kill(self): async def open_process( - command, *, stdin=None, stdout=None, stderr=None, **options + command: list[str] | str, + *, + stdin: int | HasFileno | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + **options: object, ) -> Process: r"""Execute a child program in a new process. @@ -366,9 +386,9 @@ async def open_process( "on UNIX systems" ) - trio_stdin: Optional[ClosableSendStream] = None - trio_stdout: Optional[ClosableReceiveStream] = None - trio_stderr: Optional[ClosableReceiveStream] = None + trio_stdin: ClosableSendStream | None = None + trio_stdout: ClosableReceiveStream | None = None + trio_stderr: ClosableReceiveStream | None = None # Close the parent's handle for each child side of a pipe; we want the child to # have the only copy, so that when it exits we can read EOF on our side. The # trio ends of pipes will be transferred to the Process object, which will be @@ -414,14 +434,14 @@ async def open_process( return Process._create(popen, trio_stdin, trio_stdout, trio_stderr) -async def _windows_deliver_cancel(p): +async def _windows_deliver_cancel(p: Process) -> None: try: p.terminate() except OSError as exc: warnings.warn(RuntimeWarning(f"TerminateProcess on {p!r} failed with: {exc!r}")) -async def _posix_deliver_cancel(p): +async def _posix_deliver_cancel(p: Process) -> None: try: p.terminate() await trio.sleep(5) @@ -442,14 +462,14 @@ async def _posix_deliver_cancel(p): async def run_process( command, *, - stdin=b"", - capture_stdout=False, - capture_stderr=False, - check=True, - deliver_cancel=None, - task_status=trio.TASK_STATUS_IGNORED, + stdin: bytes | bytearray | memoryview | int | HasFileno | None = b"", + capture_stdout: bool = False, + capture_stderr: bool = False, + check: bool = True, + deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, + task_status=trio.TASK_STATUS_IGNORED, # trio.TaskStatus[Process] **options, -): +) -> subprocess.CompletedProcess: """Run ``command`` in a subprocess and wait for it to complete. This function can be called in two different ways. @@ -687,17 +707,21 @@ async def my_deliver_cancel(process): assert os.name == "posix" deliver_cancel = _posix_deliver_cancel - stdout_chunks = [] - stderr_chunks = [] + stdout_chunks: list[bytes | bytearray] = [] + stderr_chunks: list[bytes | bytearray] = [] - async def feed_input(stream): + async def feed_input(stream: SendStream) -> None: async with stream: try: + assert input is not None await stream.send_all(input) except trio.BrokenResourceError: pass - async def read_output(stream, chunks): + async def read_output( + stream: ReceiveStream, + chunks: list[bytes | bytearray], + ) -> None: async with stream: async for chunk in stream: chunks.append(chunk) @@ -722,7 +746,7 @@ async def read_output(stream, chunks): with trio.CancelScope(shield=True): killer_cscope = trio.CancelScope(shield=True) - async def killer(): + async def killer() -> None: with killer_cscope: await deliver_cancel(proc) @@ -739,4 +763,5 @@ async def killer(): proc.returncode, proc.args, output=stdout, stderr=stderr ) else: + assert proc.returncode is not None return subprocess.CompletedProcess(proc.args, proc.returncode, stdout, stderr) diff --git a/trio/_subprocess_platform/waitid.py b/trio/_subprocess_platform/waitid.py index ad69017219..2f4f30fc39 100644 --- a/trio/_subprocess_platform/waitid.py +++ b/trio/_subprocess_platform/waitid.py @@ -101,7 +101,7 @@ async def wait_child_exiting(process: "_subprocess.Process") -> None: # process. if process._wait_for_exit_data is None: - process._wait_for_exit_data = event = Event() # type: ignore + process._wait_for_exit_data = event = Event() _core.spawn_system_task(_waitid_system_task, process.pid, event) assert isinstance(process._wait_for_exit_data, Event) await process._wait_for_exit_data.wait() diff --git a/trio/_subprocess_platform/windows.py b/trio/_subprocess_platform/windows.py index 958be8675c..1634e74fa7 100644 --- a/trio/_subprocess_platform/windows.py +++ b/trio/_subprocess_platform/windows.py @@ -3,4 +3,5 @@ async def wait_child_exiting(process: "_subprocess.Process") -> None: - await WaitForSingleObject(int(process._proc._handle)) + # _handle is not in Popen stubs, though it is present on Windows. + await WaitForSingleObject(int(process._proc._handle)) # type: ignore[attr-defined] From 95cf60fc7f43cb1b1e157d05464041ea77561d4b Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 12:07:20 +1000 Subject: [PATCH 02/18] Define overloads for run/open _process, from trio-typing --- trio/_subprocess.py | 159 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 7 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 688dbbba3c..b63012799d 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -5,10 +5,11 @@ import subprocess import sys import warnings -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Mapping, Sequence from contextlib import ExitStack from functools import partial -from typing import TYPE_CHECKING, Final, Protocol +from io import TextIOWrapper +from typing import Literal, TYPE_CHECKING, Final, Protocol, Union, overload import trio @@ -24,6 +25,14 @@ from ._sync import Lock from ._util import NoPublicConstructor + +if TYPE_CHECKING: + from typing_extensions import TypeAlias + + +StrOrBytesPath: TypeAlias = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] + + # Linux-specific, but has complex lifetime management stuff so we hard-code it # here instead of hiding it behind the _subprocess_platform abstraction can_try_pidfd_open: bool @@ -144,10 +153,10 @@ def __init__( self._wait_lock = Lock() - self._pidfd = None + self._pidfd: TextIOWrapper | None = None if can_try_pidfd_open: try: - fd = pidfd_open(self._proc.pid, 0) + fd: int = pidfd_open(self._proc.pid, 0) except OSError: # Well, we tried, but it didn't work (probably because we're # running on an older kernel, or in an older sandbox, that @@ -309,7 +318,7 @@ def kill(self) -> None: self._proc.kill() -async def open_process( +async def _open_process( command: list[str] | str, *, stdin: int | HasFileno | None = None, @@ -459,7 +468,8 @@ async def _posix_deliver_cancel(p: Process) -> None: ) -async def run_process( +# Use a private name, so we can declare platform-specific stubs below. +async def _run_process( command, *, stdin: bytes | bytearray | memoryview | int | HasFileno | None = b"", @@ -469,7 +479,7 @@ async def run_process( deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, task_status=trio.TASK_STATUS_IGNORED, # trio.TaskStatus[Process] **options, -) -> subprocess.CompletedProcess: +) -> subprocess.CompletedProcess[bytes]: """Run ``command`` in a subprocess and wait for it to complete. This function can be called in two different ways. @@ -765,3 +775,138 @@ async def killer() -> None: else: assert proc.returncode is not None return subprocess.CompletedProcess(proc.args, proc.returncode, stdout, stderr) + + +# There's a lot of duplication here because type checkers don't +# have a good way to represent overloads that differ only +# slightly. A cheat sheet: +# - on Windows, command is Union[str, Sequence[str]]; +# on Unix, command is str if shell=True and Sequence[str] otherwise +# - on Windows, there are startupinfo and creationflags options; +# on Unix, there are preexec_fn, restore_signals, start_new_session, and pass_fds +# - run_process() has the signature of open_process() plus arguments +# capture_stdout, capture_stderr, check, deliver_cancel, and the ability to pass +# bytes as stdin + +if TYPE_CHECKING: + if sys.platform == "win32": + async def open_process( + command: Union[StrOrBytesPath, Sequence[StrOrBytesPath]], + *, + stdin: int | HasFileno | None = ..., + stdout: int | HasFileno | None = ..., + stderr: int | HasFileno | None = ..., + close_fds: bool = ..., + shell: bool = ..., + cwd: StrOrBytesPath | None = ..., + env: Mapping[str, str] | None = ..., + startupinfo: subprocess.STARTUPINFO = ..., + creationflags: int = ..., + ) -> trio.Process: ... + + async def run_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + task_status: object = ..., # TODO: TaskStatus[Process] + stdin: bytes | bytearray | memoryview | int | HasFileno | None = ..., + capture_stdout: bool = ..., + capture_stderr: bool = ..., + check: bool = ..., + deliver_cancel: Callable[[Process], Awaitable[object]] | None = ..., + stdout: int | HasFileno | None = ..., + stderr: int | HasFileno | None = ..., + close_fds: bool = ..., + shell: bool = ..., + cwd: StrOrBytesPath | None = ..., + env: Mapping[str, str] | None = ..., + startupinfo: subprocess.STARTUPINFO = ..., + creationflags: int = ..., + ) -> subprocess.CompletedProcess[bytes]: + ... + + else: # Unix + @overload # type: ignore[no-overload-impl] + async def open_process( + command: StrOrBytesPath, + *, + stdin: int | HasFileno | None = ..., + stdout: int | HasFileno | None = ..., + stderr: int | HasFileno | None = ..., + close_fds: bool = ..., + shell: Literal[True], + cwd: StrOrBytesPath | None = ..., + env: Mapping[str, str] | None = ..., + preexec_fn: Callable[[], object] | None = ..., + restore_signals: bool = ..., + start_new_session: bool = ..., + pass_fds: Sequence[int] = ..., + ) -> trio.Process: ... + @overload + async def open_process( + command: Sequence[StrOrBytesPath], + *, + stdin: int | HasFileno | None = ..., + stdout: int | HasFileno | None = ..., + stderr: int | HasFileno | None = ..., + close_fds: bool = ..., + shell: bool = ..., + cwd: StrOrBytesPath | None = ..., + env: Mapping[str, str] | None = ..., + preexec_fn: Callable[[], object] | None = ..., + restore_signals: bool = ..., + start_new_session: bool = ..., + pass_fds: Sequence[int] = ..., + ) -> trio.Process: ... + + @overload # type: ignore[no-overload-impl] + async def run_process( + command: StrOrBytesPath, + *, + task_status: object = ..., # TODO: TaskStatus[Process] + stdin: bytes | bytearray | memoryview | int | HasFileno | None = ..., + capture_stdout: bool = ..., + capture_stderr: bool = ..., + check: bool = ..., + deliver_cancel: Callable[[Process], Awaitable[object]] | None = ..., + stdout: int | HasFileno | None = ..., + stderr: int | HasFileno | None = ..., + close_fds: bool = ..., + shell: Literal[True], + cwd: StrOrBytesPath | None = ..., + env: Mapping[str, str] | None = ..., + preexec_fn: Callable[[], object] | None = ..., + restore_signals: bool = ..., + start_new_session: bool = ..., + pass_fds: Sequence[int] = ..., + ) -> subprocess.CompletedProcess[bytes]: + ... + + @overload + async def run_process( + command: Sequence[StrOrBytesPath], + *, + task_status: object = ..., # TODO: TaskStatus[Process] + stdin: bytes | bytearray | memoryview | int | HasFileno | None = ..., + capture_stdout: bool = ..., + capture_stderr: bool = ..., + check: bool = ..., + deliver_cancel: Callable[[Process], Awaitable[None]] | None = ..., + stdout: int | HasFileno | None = ..., + stderr: int | HasFileno | None = ..., + close_fds: bool = ..., + shell: bool = ..., + cwd: StrOrBytesPath | None = ..., + env: Mapping[str, str] | None = ..., + preexec_fn: Callable[[], object] | None = ..., + restore_signals: bool = ..., + start_new_session: bool = ..., + pass_fds: Sequence[int] = ..., + ) -> subprocess.CompletedProcess[bytes]: + ... +else: + # At runtime, use the actual implementations. + open_process = _open_process + open_process.__name__ = "open_process" + + run_process = _run_process + run_process.__name__ = "run_process" From ffc3eea598823f7bbffbcf80b3d0319fa473f394 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:17:10 +1000 Subject: [PATCH 03/18] Specify default parameters for these overloads --- trio/_subprocess.py | 164 +++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 77 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index b63012799d..b1043963ad 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -135,6 +135,16 @@ class Process(AsyncResource, metaclass=NoPublicConstructor): # arbitrarily many threads if wait() keeps getting cancelled. _wait_for_exit_data: object = None + _proc: subprocess.Popen[bytes] + stdin: SendStream | None + stdout: ReceiveStream | None + stderr: ReceiveStream | None + stdio: StapledStream | None + _wait_lock: Lock + _pidfd: TextIOWrapper | None + args: StrOrBytesPath | Sequence[StrOrBytesPath] + pid: int + def __init__( self, popen: subprocess.Popen[bytes], @@ -147,13 +157,13 @@ def __init__( self.stdout = stdout self.stderr = stderr - self.stdio: StapledStream | None = None + self.stdio = None if self.stdin is not None and self.stdout is not None: self.stdio = StapledStream(self.stdin, self.stdout) self._wait_lock = Lock() - self._pidfd: TextIOWrapper | None = None + self._pidfd = None if can_try_pidfd_open: try: fd: int = pidfd_open(self._proc.pid, 0) @@ -793,34 +803,34 @@ async def killer() -> None: async def open_process( command: Union[StrOrBytesPath, Sequence[StrOrBytesPath]], *, - stdin: int | HasFileno | None = ..., - stdout: int | HasFileno | None = ..., - stderr: int | HasFileno | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: Mapping[str, str] | None = ..., - startupinfo: subprocess.STARTUPINFO = ..., - creationflags: int = ..., + stdin: int | HasFileno | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: subprocess.STARTUPINFO | None = None, + creationflags: int = 0, ) -> trio.Process: ... async def run_process( command: StrOrBytesPath | Sequence[StrOrBytesPath], *, - task_status: object = ..., # TODO: TaskStatus[Process] - stdin: bytes | bytearray | memoryview | int | HasFileno | None = ..., - capture_stdout: bool = ..., - capture_stderr: bool = ..., - check: bool = ..., - deliver_cancel: Callable[[Process], Awaitable[object]] | None = ..., - stdout: int | HasFileno | None = ..., - stderr: int | HasFileno | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: Mapping[str, str] | None = ..., - startupinfo: subprocess.STARTUPINFO = ..., - creationflags: int = ..., + task_status: object = trio.TASK_STATUS_IGNORED, # TODO: TaskStatus[Process] + stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, + capture_stdout: bool = False, + capture_stderr: bool = False, + check: bool = True, + deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: subprocess.STARTUPINFO | None = None, + creationflags: int = 0, ) -> subprocess.CompletedProcess[bytes]: ... @@ -829,55 +839,55 @@ async def run_process( async def open_process( command: StrOrBytesPath, *, - stdin: int | HasFileno | None = ..., - stdout: int | HasFileno | None = ..., - stderr: int | HasFileno | None = ..., - close_fds: bool = ..., + stdin: int | HasFileno | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + close_fds: bool = True, shell: Literal[True], - cwd: StrOrBytesPath | None = ..., - env: Mapping[str, str] | None = ..., - preexec_fn: Callable[[], object] | None = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., - pass_fds: Sequence[int] = ..., + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + preexec_fn: Callable[[], object] | None = None, + restore_signals: bool = True, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), ) -> trio.Process: ... @overload async def open_process( command: Sequence[StrOrBytesPath], *, - stdin: int | HasFileno | None = ..., - stdout: int | HasFileno | None = ..., - stderr: int | HasFileno | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: Mapping[str, str] | None = ..., - preexec_fn: Callable[[], object] | None = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., - pass_fds: Sequence[int] = ..., + stdin: int | HasFileno | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + preexec_fn: Callable[[], object] | None = None, + restore_signals: bool = True, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), ) -> trio.Process: ... @overload # type: ignore[no-overload-impl] async def run_process( command: StrOrBytesPath, *, - task_status: object = ..., # TODO: TaskStatus[Process] - stdin: bytes | bytearray | memoryview | int | HasFileno | None = ..., - capture_stdout: bool = ..., - capture_stderr: bool = ..., - check: bool = ..., - deliver_cancel: Callable[[Process], Awaitable[object]] | None = ..., - stdout: int | HasFileno | None = ..., - stderr: int | HasFileno | None = ..., - close_fds: bool = ..., + task_status: object = trio.TASK_STATUS_IGNORED, # TODO: TaskStatus[Process] + stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, + capture_stdout: bool = False, + capture_stderr: bool = False, + check: bool = True, + deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + close_fds: bool = True, shell: Literal[True], - cwd: StrOrBytesPath | None = ..., - env: Mapping[str, str] | None = ..., - preexec_fn: Callable[[], object] | None = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., - pass_fds: Sequence[int] = ..., + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + preexec_fn: Callable[[], object] | None = None, + restore_signals: bool = True, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), ) -> subprocess.CompletedProcess[bytes]: ... @@ -885,22 +895,22 @@ async def run_process( async def run_process( command: Sequence[StrOrBytesPath], *, - task_status: object = ..., # TODO: TaskStatus[Process] - stdin: bytes | bytearray | memoryview | int | HasFileno | None = ..., - capture_stdout: bool = ..., - capture_stderr: bool = ..., - check: bool = ..., - deliver_cancel: Callable[[Process], Awaitable[None]] | None = ..., - stdout: int | HasFileno | None = ..., - stderr: int | HasFileno | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: Mapping[str, str] | None = ..., - preexec_fn: Callable[[], object] | None = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., - pass_fds: Sequence[int] = ..., + task_status: object = trio.TASK_STATUS_IGNORED, # TODO: TaskStatus[Process] + stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, + capture_stdout: bool = False, + capture_stderr: bool = False, + check: bool = True, + deliver_cancel: Callable[[Process], Awaitable[None]] | None = None, + stdout: int | HasFileno | None = None, + stderr: int | HasFileno | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + preexec_fn: Callable[[], object] | None = None, + restore_signals: bool = True, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), ) -> subprocess.CompletedProcess[bytes]: ... else: From 22e548968e7f4eec90acaecdbb74a9c5ad27481d Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:30:44 +1000 Subject: [PATCH 04/18] Fill in remaining type hints --- pyproject.toml | 1 + trio/_subprocess.py | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f8d2f571e3..904bf91856 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ module = [ "trio._ki", "trio._socket", "trio._sync", + "trio._subprocess", "trio._tools.gen_exports", "trio._util", ] diff --git a/trio/_subprocess.py b/trio/_subprocess.py index b1043963ad..eed28ffafa 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -9,12 +9,12 @@ from contextlib import ExitStack from functools import partial from io import TextIOWrapper -from typing import Literal, TYPE_CHECKING, Final, Protocol, Union, overload +from typing import TYPE_CHECKING, Final, Literal, Protocol, Union, overload import trio from ._abc import AsyncResource, ReceiveStream, SendStream -from ._core import ClosedResourceError +from ._core import ClosedResourceError, TaskStatus from ._deprecate import deprecated from ._highlevel_generic import StapledStream from ._subprocess_platform import ( @@ -25,7 +25,6 @@ from ._sync import Lock from ._util import NoPublicConstructor - if TYPE_CHECKING: from typing_extensions import TypeAlias @@ -480,15 +479,15 @@ async def _posix_deliver_cancel(p: Process) -> None: # Use a private name, so we can declare platform-specific stubs below. async def _run_process( - command, + command: StrOrBytesPath | Sequence[StrOrBytesPath], *, stdin: bytes | bytearray | memoryview | int | HasFileno | None = b"", capture_stdout: bool = False, capture_stderr: bool = False, check: bool = True, deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, - task_status=trio.TASK_STATUS_IGNORED, # trio.TaskStatus[Process] - **options, + task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # TODO + **options: object, ) -> subprocess.CompletedProcess[bytes]: """Run ``command`` in a subprocess and wait for it to complete. @@ -747,7 +746,8 @@ async def read_output( chunks.append(chunk) async with trio.open_nursery() as nursery: - proc = await open_process(command, **options) + # options needs a complex TypedDict. + proc = await open_process(command, **options) # type: ignore try: if input is not None: nursery.start_soon(feed_input, proc.stdin) @@ -800,6 +800,7 @@ async def killer() -> None: if TYPE_CHECKING: if sys.platform == "win32": + async def open_process( command: Union[StrOrBytesPath, Sequence[StrOrBytesPath]], *, @@ -812,12 +813,13 @@ async def open_process( env: Mapping[str, str] | None = None, startupinfo: subprocess.STARTUPINFO | None = None, creationflags: int = 0, - ) -> trio.Process: ... + ) -> trio.Process: + ... async def run_process( command: StrOrBytesPath | Sequence[StrOrBytesPath], *, - task_status: object = trio.TASK_STATUS_IGNORED, # TODO: TaskStatus[Process] + task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # TODO stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, capture_stdout: bool = False, capture_stderr: bool = False, @@ -835,6 +837,7 @@ async def run_process( ... else: # Unix + @overload # type: ignore[no-overload-impl] async def open_process( command: StrOrBytesPath, @@ -850,7 +853,9 @@ async def open_process( restore_signals: bool = True, start_new_session: bool = False, pass_fds: Sequence[int] = (), - ) -> trio.Process: ... + ) -> trio.Process: + ... + @overload async def open_process( command: Sequence[StrOrBytesPath], @@ -866,13 +871,14 @@ async def open_process( restore_signals: bool = True, start_new_session: bool = False, pass_fds: Sequence[int] = (), - ) -> trio.Process: ... + ) -> trio.Process: + ... @overload # type: ignore[no-overload-impl] async def run_process( command: StrOrBytesPath, *, - task_status: object = trio.TASK_STATUS_IGNORED, # TODO: TaskStatus[Process] + task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # TODO stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, capture_stdout: bool = False, capture_stderr: bool = False, @@ -913,6 +919,7 @@ async def run_process( pass_fds: Sequence[int] = (), ) -> subprocess.CompletedProcess[bytes]: ... + else: # At runtime, use the actual implementations. open_process = _open_process From 0c3bff5c404952149ba8013e8dc7ac6e27b8fd3f Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:31:15 +1000 Subject: [PATCH 05/18] Fix a type error - wait_readable() might need different type hints. --- trio/_subprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index eed28ffafa..a92455829e 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -263,7 +263,7 @@ async def wait(self) -> int: if self.poll() is None: if self._pidfd is not None: try: - await trio.lowlevel.wait_readable(self._pidfd) + await trio.lowlevel.wait_readable(self._pidfd.fileno()) except ClosedResourceError: # something else (probably a call to poll) already closed the # pidfd From 0d79bd0b774be0bb4891b1c2f1cd18af329503a5 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:35:59 +1000 Subject: [PATCH 06/18] Ignore protocols in coverage --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 431a02971b..5272237caf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,6 +23,7 @@ exclude_lines = if _t.TYPE_CHECKING: if t.TYPE_CHECKING: @overload + class .*\bProtocol\b.*\): partial_branches = pragma: no branch From 18c776941d119e9ea869732358ccda367bb6125c Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:38:42 +1000 Subject: [PATCH 07/18] Set __qualname__ also --- trio/_subprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index a92455829e..9a979f997c 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -923,7 +923,7 @@ async def run_process( else: # At runtime, use the actual implementations. open_process = _open_process - open_process.__name__ = "open_process" + open_process.__name__ = open_process.__qualname__ = "open_process" run_process = _run_process - run_process.__name__ = "run_process" + run_process.__name__ = run_process.__qualname__ = "run_process" From 110aae88805c892b60f69146ac2bf7d2c50d34db Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:44:02 +1000 Subject: [PATCH 08/18] Add HasFileno to docs to avoid docs errors --- docs/source/reference-io.rst | 4 ++++ trio/_subprocess.py | 1 + 2 files changed, 5 insertions(+) diff --git a/docs/source/reference-io.rst b/docs/source/reference-io.rst index 9207afb41b..b768c87da1 100644 --- a/docs/source/reference-io.rst +++ b/docs/source/reference-io.rst @@ -731,6 +731,10 @@ task and interact with it while it's running: .. autofunction:: trio.run_process +.. autoclass:: trio._subprocess.HasFileno(Protocol) + + .. automethod:: fileno + .. autoclass:: trio.Process .. autoattribute:: returncode diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 9a979f997c..5aa270ee28 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -78,6 +78,7 @@ def pidfd_open(fd: int, flags: int) -> int: class HasFileno(Protocol): + """Represents any file-like object that has a file descriptor.""" def fileno(self) -> int: ... From 05db4948db8a4a1fbe7f5d73aabfcec2af4c6d74 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 13:55:48 +1000 Subject: [PATCH 09/18] Suppress __init__ signature for trio.Process It's not public, so it shouldn't be documented --- docs/source/reference-io.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference-io.rst b/docs/source/reference-io.rst index b768c87da1..e270033b46 100644 --- a/docs/source/reference-io.rst +++ b/docs/source/reference-io.rst @@ -735,7 +735,7 @@ task and interact with it while it's running: .. automethod:: fileno -.. autoclass:: trio.Process +.. autoclass:: trio.Process() .. autoattribute:: returncode From be1843cdad068eb7b7cb32e392370e8bd692b270 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 14:20:37 +1000 Subject: [PATCH 10/18] Move these declarations into __init__ --- trio/_subprocess.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 5aa270ee28..6636e1beb9 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -79,6 +79,7 @@ def pidfd_open(fd: int, flags: int) -> int: class HasFileno(Protocol): """Represents any file-like object that has a file descriptor.""" + def fileno(self) -> int: ... @@ -135,16 +136,6 @@ class Process(AsyncResource, metaclass=NoPublicConstructor): # arbitrarily many threads if wait() keeps getting cancelled. _wait_for_exit_data: object = None - _proc: subprocess.Popen[bytes] - stdin: SendStream | None - stdout: ReceiveStream | None - stderr: ReceiveStream | None - stdio: StapledStream | None - _wait_lock: Lock - _pidfd: TextIOWrapper | None - args: StrOrBytesPath | Sequence[StrOrBytesPath] - pid: int - def __init__( self, popen: subprocess.Popen[bytes], @@ -157,13 +148,13 @@ def __init__( self.stdout = stdout self.stderr = stderr - self.stdio = None + self.stdio: StapledStream | None = None if self.stdin is not None and self.stdout is not None: self.stdio = StapledStream(self.stdin, self.stdout) - self._wait_lock = Lock() + self._wait_lock: Lock = Lock() - self._pidfd = None + self._pidfd: TextIOWrapper | None = None if can_try_pidfd_open: try: fd: int = pidfd_open(self._proc.pid, 0) @@ -178,8 +169,8 @@ def __init__( # make sure it'll get closed. self._pidfd = open(fd) - self.args = self._proc.args - self.pid = self._proc.pid + self.args: StrOrBytesPath | Sequence[StrOrBytesPath] = self._proc.args + self.pid: int = self._proc.pid def __repr__(self) -> str: returncode = self.returncode From 5902efeec95d56f50b8e770bc5167b0bcd71577a Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 14:23:43 +1000 Subject: [PATCH 11/18] Update verify_types.json --- trio/_tests/verify_types.json | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json index ec345facf3..7dc11d6ec9 100644 --- a/trio/_tests/verify_types.json +++ b/trio/_tests/verify_types.json @@ -7,11 +7,11 @@ "warningCount": 0 }, "typeCompleteness": { - "completenessScore": 0.9250398724082934, + "completenessScore": 0.9282296650717703, "exportedSymbolCounts": { "withAmbiguousType": 0, - "withKnownType": 580, - "withUnknownType": 47 + "withKnownType": 582, + "withUnknownType": 45 }, "ignoreUnknownTypesFromImports": true, "missingClassDocStringCount": 1, @@ -45,9 +45,9 @@ } ], "otherSymbolCounts": { - "withAmbiguousType": 3, - "withKnownType": 602, - "withUnknownType": 61 + "withAmbiguousType": 1, + "withKnownType": 617, + "withUnknownType": 50 }, "packageName": "trio", "symbols": [ @@ -74,18 +74,6 @@ "trio._ssl.SSLStream.transport_stream", "trio._ssl.SSLStream.unwrap", "trio._ssl.SSLStream.wait_send_all_might_not_block", - "trio._subprocess.Process.__init__", - "trio._subprocess.Process.__repr__", - "trio._subprocess.Process.args", - "trio._subprocess.Process.encoding", - "trio._subprocess.Process.errors", - "trio._subprocess.Process.kill", - "trio._subprocess.Process.pid", - "trio._subprocess.Process.poll", - "trio._subprocess.Process.returncode", - "trio._subprocess.Process.send_signal", - "trio._subprocess.Process.terminate", - "trio._subprocess.Process.wait", "trio.current_time", "trio.from_thread.run", "trio.from_thread.run_sync", @@ -95,7 +83,6 @@ "trio.lowlevel.current_statistics", "trio.lowlevel.current_trio_token", "trio.lowlevel.notify_closing", - "trio.lowlevel.open_process", "trio.lowlevel.permanently_detach_coroutine_object", "trio.lowlevel.reattach_detached_coroutine_object", "trio.lowlevel.reschedule", From 7be3a5f09f5a11b9aba3c97ebd229a73ad6937c0 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 14:27:33 +1000 Subject: [PATCH 12/18] os.PathLike is not subscriptable in 3.8 --- trio/_subprocess.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 6636e1beb9..3237affa2e 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -29,7 +29,8 @@ from typing_extensions import TypeAlias -StrOrBytesPath: TypeAlias = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] +# Only subscriptable in 3.9+ +StrOrBytesPath: TypeAlias = Union[str, bytes, 'os.PathLike[str]', 'os.PathLike[bytes]'] # Linux-specific, but has complex lifetime management stuff so we hard-code it From 2b45d64e9f7dd065f6d1007691d7affcf8e229ea Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Mon, 14 Aug 2023 15:08:32 +1000 Subject: [PATCH 13/18] Run linters --- trio/_subprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 3237affa2e..2f20ad0805 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -30,7 +30,7 @@ # Only subscriptable in 3.9+ -StrOrBytesPath: TypeAlias = Union[str, bytes, 'os.PathLike[str]', 'os.PathLike[bytes]'] +StrOrBytesPath: TypeAlias = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] # Linux-specific, but has complex lifetime management stuff so we hard-code it From bb3484cc22d1c82aede9acf5f494995cda5e8d1d Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 15 Aug 2023 09:22:14 +1000 Subject: [PATCH 14/18] Use generic TaskStatus in subprocess --- trio/_subprocess.py | 8 ++++---- trio/_tests/verify_types.json | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 2f20ad0805..3daef31428 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -479,7 +479,7 @@ async def _run_process( capture_stderr: bool = False, check: bool = True, deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, - task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # TODO + task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED, **options: object, ) -> subprocess.CompletedProcess[bytes]: """Run ``command`` in a subprocess and wait for it to complete. @@ -812,7 +812,7 @@ async def open_process( async def run_process( command: StrOrBytesPath | Sequence[StrOrBytesPath], *, - task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # TODO + task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED, stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, capture_stdout: bool = False, capture_stderr: bool = False, @@ -871,7 +871,7 @@ async def open_process( async def run_process( command: StrOrBytesPath, *, - task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # TODO + task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED, stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, capture_stdout: bool = False, capture_stderr: bool = False, @@ -894,7 +894,7 @@ async def run_process( async def run_process( command: Sequence[StrOrBytesPath], *, - task_status: object = trio.TASK_STATUS_IGNORED, # TODO: TaskStatus[Process] + task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED, stdin: bytes | bytearray | memoryview | int | HasFileno | None = None, capture_stdout: bool = False, capture_stderr: bool = False, diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json index 3be2685ea5..e65c377af8 100644 --- a/trio/_tests/verify_types.json +++ b/trio/_tests/verify_types.json @@ -7,11 +7,11 @@ "warningCount": 0 }, "typeCompleteness": { - "completenessScore": 0.9522292993630573, + "completenessScore": 0.9538216560509554, "exportedSymbolCounts": { "withAmbiguousType": 0, - "withKnownType": 598, - "withUnknownType": 30 + "withKnownType": 599, + "withUnknownType": 29 }, "ignoreUnknownTypesFromImports": true, "missingClassDocStringCount": 1, @@ -80,7 +80,6 @@ "trio.open_ssl_over_tcp_listeners", "trio.open_ssl_over_tcp_stream", "trio.open_unix_socket", - "trio.run_process", "trio.serve_listeners", "trio.serve_ssl_over_tcp", "trio.testing._memory_streams.MemoryReceiveStream.__init__", From f502d20db10b5787463414f33e673a43aa2f837c Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 15 Aug 2023 09:30:34 +1000 Subject: [PATCH 15/18] Add error codes to type-ignore, then ignore the error code not being used --- trio/_subprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trio/_subprocess.py b/trio/_subprocess.py index 3daef31428..7cf990fa53 100644 --- a/trio/_subprocess.py +++ b/trio/_subprocess.py @@ -739,8 +739,8 @@ async def read_output( chunks.append(chunk) async with trio.open_nursery() as nursery: - # options needs a complex TypedDict. - proc = await open_process(command, **options) # type: ignore + # options needs a complex TypedDict. The overload error only occurs on Unix. + proc = await open_process(command, **options) # type: ignore[arg-type, call-overload, unused-ignore] try: if input is not None: nursery.start_soon(feed_input, proc.stdin) From f8db88b687a234c0188b4bbafecc736a3bdebd63 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 15 Aug 2023 13:34:31 +1000 Subject: [PATCH 16/18] Add types to trio._subprocess_platform.waitid --- trio/_subprocess_platform/waitid.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/trio/_subprocess_platform/waitid.py b/trio/_subprocess_platform/waitid.py index 2f4f30fc39..22ab05eb83 100644 --- a/trio/_subprocess_platform/waitid.py +++ b/trio/_subprocess_platform/waitid.py @@ -2,15 +2,20 @@ import math import os import sys +from typing import TYPE_CHECKING from .. import _core, _subprocess from .._sync import CapacityLimiter, Event from .._threads import to_thread_run_sync + +assert sys.platform != "win32" or not TYPE_CHECKING + + try: from os import waitid - def sync_wait_reapable(pid): + def sync_wait_reapable(pid: int) -> None: waitid(os.P_PID, pid, os.WEXITED | os.WNOWAIT) except ImportError: @@ -39,9 +44,9 @@ def sync_wait_reapable(pid): int waitid(int idtype, int id, siginfo_t* result, int options); """ ) - waitid = waitid_ffi.dlopen(None).waitid + waitid_cffi = waitid_ffi.dlopen(None).waitid - def sync_wait_reapable(pid): + def sync_wait_reapable(pid: int) -> None: P_PID = 1 WEXITED = 0x00000004 if sys.platform == "darwin": # pragma: no cover @@ -52,7 +57,7 @@ def sync_wait_reapable(pid): else: WNOWAIT = 0x01000000 result = waitid_ffi.new("siginfo_t *") - while waitid(P_PID, pid, result, WEXITED | WNOWAIT) < 0: + while waitid_cffi(P_PID, pid, result, WEXITED | WNOWAIT) < 0: got_errno = waitid_ffi.errno if got_errno == errno.EINTR: continue From cc7f9ad4b042ada4d22c20ea99810a527d13b002 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 15 Aug 2023 13:34:46 +1000 Subject: [PATCH 17/18] Make trio._subprocess_platform strict --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 11f61d9c97..4c89d08097 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,8 +67,12 @@ module = [ "trio._highlevel_open_tcp_stream", "trio._ki", "trio._socket", - "trio._sync", "trio._subprocess", + "trio._subprocess_platform", + "trio._subprocess_platform.kqueue", + "trio._subprocess_platform.waitid", + "trio._subprocess_platform.windows", + "trio._sync", "trio._tools.gen_exports", "trio._util", ] From 834ce9e0b01f5d650f51eda2dab24288e1f6f167 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 15 Aug 2023 14:14:45 +1000 Subject: [PATCH 18/18] Fix minor type errors --- trio/_subprocess_platform/kqueue.py | 4 +++- trio/_subprocess_platform/waitid.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/trio/_subprocess_platform/kqueue.py b/trio/_subprocess_platform/kqueue.py index 9839fd046b..efd0562fc2 100644 --- a/trio/_subprocess_platform/kqueue.py +++ b/trio/_subprocess_platform/kqueue.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import select import sys from typing import TYPE_CHECKING @@ -35,7 +37,7 @@ async def wait_child_exiting(process: "_subprocess.Process") -> None: # in Chromium it seems we should still keep the check. return - def abort(_): + def abort(_: _core.RaiseCancelT) -> _core.Abort: kqueue.control([make_event(select.KQ_EV_DELETE)], 0) return _core.Abort.SUCCEEDED diff --git a/trio/_subprocess_platform/waitid.py b/trio/_subprocess_platform/waitid.py index 22ab05eb83..2a2ca6719d 100644 --- a/trio/_subprocess_platform/waitid.py +++ b/trio/_subprocess_platform/waitid.py @@ -8,8 +8,7 @@ from .._sync import CapacityLimiter, Event from .._threads import to_thread_run_sync - -assert sys.platform != "win32" or not TYPE_CHECKING +assert (sys.platform != "win32" and sys.platform != "darwin") or not TYPE_CHECKING try: