Skip to content

Commit 68014ec

Browse files
authored
Merge pull request #3212 from A5rocks/update-run-process
Add missing arguments to run_process
2 parents d7abe2e + 6eb1ba1 commit 68014ec

File tree

3 files changed

+123
-69
lines changed

3 files changed

+123
-69
lines changed

newsfragments/3183.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update type hints for `trio.run_process` and `trio.lowlevel.open_process`.

src/trio/_subprocess.py

Lines changed: 99 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77
import warnings
88
from contextlib import ExitStack
99
from functools import partial
10-
from typing import TYPE_CHECKING, Final, Literal, Protocol, Union, overload
10+
from typing import (
11+
TYPE_CHECKING,
12+
Final,
13+
Literal,
14+
Protocol,
15+
TypedDict,
16+
Union,
17+
overload,
18+
)
1119

1220
import trio
1321

@@ -23,10 +31,10 @@
2331

2432
if TYPE_CHECKING:
2533
import signal
26-
from collections.abc import Awaitable, Callable, Mapping, Sequence
34+
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
2735
from io import TextIOWrapper
2836

29-
from typing_extensions import TypeAlias
37+
from typing_extensions import TypeAlias, Unpack
3038

3139
from ._abc import ReceiveStream, SendStream
3240

@@ -779,29 +787,46 @@ async def killer() -> None:
779787
# There's a lot of duplication here because type checkers don't
780788
# have a good way to represent overloads that differ only
781789
# slightly. A cheat sheet:
790+
#
782791
# - on Windows, command is Union[str, Sequence[str]];
783792
# on Unix, command is str if shell=True and Sequence[str] otherwise
793+
#
784794
# - on Windows, there are startupinfo and creationflags options;
785-
# on Unix, there are preexec_fn, restore_signals, start_new_session, and pass_fds
795+
# on Unix, there are preexec_fn, restore_signals, start_new_session,
796+
# pass_fds, group (3.9+), extra_groups (3.9+), user (3.9+),
797+
# umask (3.9+), pipesize (3.10+), process_group (3.11+)
798+
#
786799
# - run_process() has the signature of open_process() plus arguments
787-
# capture_stdout, capture_stderr, check, deliver_cancel, and the ability to pass
788-
# bytes as stdin
800+
# capture_stdout, capture_stderr, check, deliver_cancel, the ability
801+
# to pass bytes as stdin, and the ability to run in `nursery.start`
802+
803+
804+
class GeneralProcessArgs(TypedDict, total=False):
805+
"""Arguments shared between all runs."""
806+
807+
stdout: int | HasFileno | None
808+
stderr: int | HasFileno | None
809+
close_fds: bool
810+
cwd: StrOrBytesPath | None
811+
env: Mapping[str, str] | None
812+
executable: StrOrBytesPath | None
813+
789814

790815
if TYPE_CHECKING:
791816
if sys.platform == "win32":
792817

818+
class WindowsProcessArgs(GeneralProcessArgs, total=False):
819+
"""Arguments shared between all Windows runs."""
820+
821+
shell: bool
822+
startupinfo: subprocess.STARTUPINFO | None
823+
creationflags: int
824+
793825
async def open_process(
794826
command: StrOrBytesPath | Sequence[StrOrBytesPath],
795827
*,
796828
stdin: int | HasFileno | None = None,
797-
stdout: int | HasFileno | None = None,
798-
stderr: int | HasFileno | None = None,
799-
close_fds: bool = True,
800-
shell: bool = False,
801-
cwd: StrOrBytesPath | None = None,
802-
env: Mapping[str, str] | None = None,
803-
startupinfo: subprocess.STARTUPINFO | None = None,
804-
creationflags: int = 0,
829+
**kwargs: Unpack[WindowsProcessArgs],
805830
) -> trio.Process:
806831
r"""Execute a child program in a new process.
807832
@@ -864,14 +889,7 @@ async def run_process(
864889
capture_stderr: bool = False,
865890
check: bool = True,
866891
deliver_cancel: Callable[[Process], Awaitable[object]] | None = None,
867-
stdout: int | HasFileno | None = None,
868-
stderr: int | HasFileno | None = None,
869-
close_fds: bool = True,
870-
shell: bool = False,
871-
cwd: StrOrBytesPath | None = None,
872-
env: Mapping[str, str] | None = None,
873-
startupinfo: subprocess.STARTUPINFO | None = None,
874-
creationflags: int = 0,
892+
**kwargs: Unpack[WindowsProcessArgs],
875893
) -> subprocess.CompletedProcess[bytes]:
876894
"""Run ``command`` in a subprocess and wait for it to complete.
877895
@@ -1067,85 +1085,97 @@ async def my_deliver_cancel(process):
10671085
...
10681086

10691087
else: # Unix
1070-
# pyright doesn't give any error about these missing docstrings as they're
1088+
# pyright doesn't give any error about overloads missing docstrings as they're
10711089
# overloads. But might still be a problem for other static analyzers / docstring
10721090
# readers (?)
1091+
1092+
class UnixProcessArgs3_9(GeneralProcessArgs, total=False):
1093+
"""Arguments shared between all Unix runs."""
1094+
1095+
preexec_fn: Callable[[], object] | None
1096+
restore_signals: bool
1097+
start_new_session: bool
1098+
pass_fds: Sequence[int]
1099+
1100+
# 3.9+
1101+
group: str | int | None
1102+
extra_groups: Iterable[str | int] | None
1103+
user: str | int | None
1104+
umask: int
1105+
1106+
class UnixProcessArgs3_10(UnixProcessArgs3_9, total=False):
1107+
"""Arguments shared between all Unix runs on 3.10+."""
1108+
1109+
pipesize: int
1110+
1111+
class UnixProcessArgs3_11(UnixProcessArgs3_10, total=False):
1112+
"""Arguments shared between all Unix runs on 3.11+."""
1113+
1114+
process_group: int | None
1115+
1116+
class UnixRunProcessMixin(TypedDict, total=False):
1117+
"""Arguments unique to run_process on Unix."""
1118+
1119+
task_status: TaskStatus[Process]
1120+
capture_stdout: bool
1121+
capture_stderr: bool
1122+
check: bool
1123+
deliver_cancel: Callable[[Process], Awaitable[None]] | None
1124+
1125+
# TODO: once https://github.com/python/mypy/issues/18692 is
1126+
# fixed, move the `UnixRunProcessArgs` definition down.
1127+
if sys.version_info >= (3, 11):
1128+
UnixProcessArgs = UnixProcessArgs3_11
1129+
1130+
class UnixRunProcessArgs(UnixProcessArgs3_11, UnixRunProcessMixin):
1131+
"""Arguments for run_process on Unix with 3.11+"""
1132+
1133+
elif sys.version_info >= (3, 10):
1134+
UnixProcessArgs = UnixProcessArgs3_10
1135+
1136+
class UnixRunProcessArgs(UnixProcessArgs3_10, UnixRunProcessMixin):
1137+
"""Arguments for run_process on Unix with 3.10+"""
1138+
1139+
else:
1140+
UnixProcessArgs = UnixProcessArgs3_9
1141+
1142+
class UnixRunProcessArgs(UnixProcessArgs3_9, UnixRunProcessMixin):
1143+
"""Arguments for run_process on Unix with 3.9+"""
1144+
10731145
@overload # type: ignore[no-overload-impl]
10741146
async def open_process(
10751147
command: StrOrBytesPath,
10761148
*,
10771149
stdin: int | HasFileno | None = None,
1078-
stdout: int | HasFileno | None = None,
1079-
stderr: int | HasFileno | None = None,
1080-
close_fds: bool = True,
10811150
shell: Literal[True],
1082-
cwd: StrOrBytesPath | None = None,
1083-
env: Mapping[str, str] | None = None,
1084-
preexec_fn: Callable[[], object] | None = None,
1085-
restore_signals: bool = True,
1086-
start_new_session: bool = False,
1087-
pass_fds: Sequence[int] = (),
1151+
**kwargs: Unpack[UnixProcessArgs],
10881152
) -> trio.Process: ...
10891153

10901154
@overload
10911155
async def open_process(
10921156
command: Sequence[StrOrBytesPath],
10931157
*,
10941158
stdin: int | HasFileno | None = None,
1095-
stdout: int | HasFileno | None = None,
1096-
stderr: int | HasFileno | None = None,
1097-
close_fds: bool = True,
10981159
shell: bool = False,
1099-
cwd: StrOrBytesPath | None = None,
1100-
env: Mapping[str, str] | None = None,
1101-
preexec_fn: Callable[[], object] | None = None,
1102-
restore_signals: bool = True,
1103-
start_new_session: bool = False,
1104-
pass_fds: Sequence[int] = (),
1160+
**kwargs: Unpack[UnixProcessArgs],
11051161
) -> trio.Process: ...
11061162

11071163
@overload # type: ignore[no-overload-impl]
11081164
async def run_process(
11091165
command: StrOrBytesPath,
11101166
*,
1111-
task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED,
11121167
stdin: bytes | bytearray | memoryview | int | HasFileno | None = None,
1113-
capture_stdout: bool = False,
1114-
capture_stderr: bool = False,
1115-
check: bool = True,
1116-
deliver_cancel: Callable[[Process], Awaitable[object]] | None = None,
1117-
stdout: int | HasFileno | None = None,
1118-
stderr: int | HasFileno | None = None,
1119-
close_fds: bool = True,
11201168
shell: Literal[True],
1121-
cwd: StrOrBytesPath | None = None,
1122-
env: Mapping[str, str] | None = None,
1123-
preexec_fn: Callable[[], object] | None = None,
1124-
restore_signals: bool = True,
1125-
start_new_session: bool = False,
1126-
pass_fds: Sequence[int] = (),
1169+
**kwargs: Unpack[UnixRunProcessArgs],
11271170
) -> subprocess.CompletedProcess[bytes]: ...
11281171

11291172
@overload
11301173
async def run_process(
11311174
command: Sequence[StrOrBytesPath],
11321175
*,
1133-
task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED,
11341176
stdin: bytes | bytearray | memoryview | int | HasFileno | None = None,
1135-
capture_stdout: bool = False,
1136-
capture_stderr: bool = False,
1137-
check: bool = True,
1138-
deliver_cancel: Callable[[Process], Awaitable[None]] | None = None,
1139-
stdout: int | HasFileno | None = None,
1140-
stderr: int | HasFileno | None = None,
1141-
close_fds: bool = True,
11421177
shell: bool = False,
1143-
cwd: StrOrBytesPath | None = None,
1144-
env: Mapping[str, str] | None = None,
1145-
preexec_fn: Callable[[], object] | None = None,
1146-
restore_signals: bool = True,
1147-
start_new_session: bool = False,
1148-
pass_fds: Sequence[int] = (),
1178+
**kwargs: Unpack[UnixRunProcessArgs],
11491179
) -> subprocess.CompletedProcess[bytes]: ...
11501180

11511181
else:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import sys
2+
3+
import trio
4+
5+
6+
async def test() -> None:
7+
# this could test more by using platform checks, but currently this
8+
# is just regression tests + sanity checks.
9+
await trio.run_process("python", executable="ls")
10+
await trio.lowlevel.open_process("python", executable="ls")
11+
12+
# note: there's no error code on the type ignore as it varies
13+
# between platforms.
14+
await trio.run_process("python", capture_stdout=True)
15+
await trio.lowlevel.open_process("python", capture_stdout=True) # type: ignore
16+
17+
if sys.platform != "win32" and sys.version_info >= (3, 9):
18+
await trio.run_process("python", extra_groups=[5])
19+
await trio.lowlevel.open_process("python", extra_groups=[5])
20+
21+
# 3.11+:
22+
await trio.run_process("python", process_group=5) # type: ignore
23+
await trio.lowlevel.open_process("python", process_group=5) # type: ignore

0 commit comments

Comments
 (0)