@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kgraph-launcher +15 -3
- package/lib/kgraph/scripts/build-bundle.sh +17 -4
- package/lib/site-packages/outcome/__init__.py +20 -0
- package/lib/site-packages/outcome/_impl.py +239 -0
- package/lib/site-packages/outcome/_util.py +33 -0
- package/lib/site-packages/outcome/_version.py +7 -0
- package/lib/site-packages/outcome/py.typed +0 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sniffio/__init__.py +17 -0
- package/lib/site-packages/sniffio/_impl.py +95 -0
- package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
- package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
- package/lib/site-packages/sniffio/_version.py +3 -0
- package/lib/site-packages/sniffio/py.typed +0 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sortedcontainers/__init__.py +74 -0
- package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
- package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
- package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/trio/__init__.py +133 -0
- package/lib/site-packages/trio/__main__.py +3 -0
- package/lib/site-packages/trio/_abc.py +714 -0
- package/lib/site-packages/trio/_channel.py +610 -0
- package/lib/site-packages/trio/_core/__init__.py +94 -0
- package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
- package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
- package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
- package/lib/site-packages/trio/_core/_exceptions.py +169 -0
- package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
- package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
- package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
- package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
- package/lib/site-packages/trio/_core/_generated_run.py +269 -0
- package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
- package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
- package/lib/site-packages/trio/_core/_io_common.py +31 -0
- package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
- package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
- package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
- package/lib/site-packages/trio/_core/_ki.py +271 -0
- package/lib/site-packages/trio/_core/_local.py +104 -0
- package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
- package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
- package/lib/site-packages/trio/_core/_run.py +3148 -0
- package/lib/site-packages/trio/_core/_run_context.py +15 -0
- package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
- package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
- package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
- package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
- package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
- package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
- package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
- package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
- package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
- package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
- package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
- package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
- package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
- package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
- package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
- package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
- package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
- package/lib/site-packages/trio/_core/_traps.py +318 -0
- package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
- package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
- package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
- package/lib/site-packages/trio/_deprecate.py +171 -0
- package/lib/site-packages/trio/_dtls.py +1380 -0
- package/lib/site-packages/trio/_file_io.py +513 -0
- package/lib/site-packages/trio/_highlevel_generic.py +125 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
- package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
- package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
- package/lib/site-packages/trio/_highlevel_socket.py +423 -0
- package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
- package/lib/site-packages/trio/_path.py +289 -0
- package/lib/site-packages/trio/_repl.py +159 -0
- package/lib/site-packages/trio/_signals.py +185 -0
- package/lib/site-packages/trio/_socket.py +1326 -0
- package/lib/site-packages/trio/_ssl.py +964 -0
- package/lib/site-packages/trio/_subprocess.py +1178 -0
- package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
- package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
- package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
- package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
- package/lib/site-packages/trio/_sync.py +908 -0
- package/lib/site-packages/trio/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
- package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
- package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
- package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
- package/lib/site-packages/trio/_tests/test_abc.py +72 -0
- package/lib/site-packages/trio/_tests/test_channel.py +750 -0
- package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
- package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
- package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
- package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
- package/lib/site-packages/trio/_tests/test_exports.py +626 -0
- package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
- package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
- package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
- package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
- package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
- package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
- package/lib/site-packages/trio/_tests/test_path.py +279 -0
- package/lib/site-packages/trio/_tests/test_repl.py +428 -0
- package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
- package/lib/site-packages/trio/_tests/test_signals.py +186 -0
- package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
- package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
- package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
- package/lib/site-packages/trio/_tests/test_sync.py +735 -0
- package/lib/site-packages/trio/_tests/test_testing.py +682 -0
- package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
- package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
- package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
- package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
- package/lib/site-packages/trio/_tests/test_trio.py +8 -0
- package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
- package/lib/site-packages/trio/_tests/test_util.py +349 -0
- package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
- package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
- package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
- package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
- package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
- package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
- package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
- package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
- package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
- package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
- package/lib/site-packages/trio/_threads.py +610 -0
- package/lib/site-packages/trio/_timeouts.py +197 -0
- package/lib/site-packages/trio/_tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
- package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
- package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
- package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
- package/lib/site-packages/trio/_unix_pipes.py +197 -0
- package/lib/site-packages/trio/_util.py +385 -0
- package/lib/site-packages/trio/_version.py +3 -0
- package/lib/site-packages/trio/_wait_for_object.py +67 -0
- package/lib/site-packages/trio/_windows_pipes.py +144 -0
- package/lib/site-packages/trio/abc.py +23 -0
- package/lib/site-packages/trio/from_thread.py +13 -0
- package/lib/site-packages/trio/lowlevel.py +95 -0
- package/lib/site-packages/trio/py.typed +0 -0
- package/lib/site-packages/trio/socket.py +602 -0
- package/lib/site-packages/trio/testing/__init__.py +58 -0
- package/lib/site-packages/trio/testing/_check_streams.py +570 -0
- package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
- package/lib/site-packages/trio/testing/_fake_net.py +584 -0
- package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
- package/lib/site-packages/trio/testing/_network.py +36 -0
- package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
- package/lib/site-packages/trio/testing/_sequencer.py +87 -0
- package/lib/site-packages/trio/testing/_trio_test.py +50 -0
- package/lib/site-packages/trio/to_thread.py +4 -0
- package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
- package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
- package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
- package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
- package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
- package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import warnings
|
|
8
|
+
from contextlib import ExitStack
|
|
9
|
+
from functools import partial
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Final,
|
|
13
|
+
Literal,
|
|
14
|
+
Protocol,
|
|
15
|
+
TypeAlias,
|
|
16
|
+
TypedDict,
|
|
17
|
+
overload,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
import trio
|
|
21
|
+
|
|
22
|
+
from ._core import ClosedResourceError, TaskStatus
|
|
23
|
+
from ._highlevel_generic import StapledStream
|
|
24
|
+
from ._subprocess_platform import (
|
|
25
|
+
create_pipe_from_child_output,
|
|
26
|
+
create_pipe_to_child_stdin,
|
|
27
|
+
wait_child_exiting,
|
|
28
|
+
)
|
|
29
|
+
from ._sync import Lock
|
|
30
|
+
from ._util import NoPublicConstructor, final
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
import signal
|
|
34
|
+
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
|
|
35
|
+
from io import TextIOWrapper
|
|
36
|
+
|
|
37
|
+
from typing_extensions import Unpack
|
|
38
|
+
|
|
39
|
+
from ._abc import ReceiveStream, SendStream
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Sphinx cannot parse the stringified version
|
|
43
|
+
StrOrBytesPath: TypeAlias = str | bytes | os.PathLike[str] | os.PathLike[bytes]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Linux-specific, but has complex lifetime management stuff so we hard-code it
|
|
47
|
+
# here instead of hiding it behind the _subprocess_platform abstraction
|
|
48
|
+
can_try_pidfd_open: bool
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
|
|
51
|
+
def pidfd_open(fd: int, flags: int) -> int: ...
|
|
52
|
+
|
|
53
|
+
from ._subprocess_platform import ClosableReceiveStream, ClosableSendStream
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
can_try_pidfd_open = True
|
|
57
|
+
try:
|
|
58
|
+
from os import pidfd_open
|
|
59
|
+
except ImportError:
|
|
60
|
+
if sys.platform == "linux":
|
|
61
|
+
# this workaround is needed on:
|
|
62
|
+
# - CPython <= 3.8
|
|
63
|
+
# - non-CPython (maybe?)
|
|
64
|
+
# - Anaconda's interpreter (as it is built to assume an older
|
|
65
|
+
# than current linux kernel)
|
|
66
|
+
#
|
|
67
|
+
# The last point implies that other custom builds might not work;
|
|
68
|
+
# therefore, no assertion should be here.
|
|
69
|
+
import ctypes
|
|
70
|
+
|
|
71
|
+
_cdll_for_pidfd_open = ctypes.CDLL(None, use_errno=True)
|
|
72
|
+
_cdll_for_pidfd_open.syscall.restype = ctypes.c_long
|
|
73
|
+
# pid and flags are actually int-sized, but the syscall() function
|
|
74
|
+
# always takes longs. (Except on x32 where long is 32-bits and syscall
|
|
75
|
+
# takes 64-bit arguments. But in the unlikely case that anyone is
|
|
76
|
+
# using x32, this will still work, b/c we only need to pass in 32 bits
|
|
77
|
+
# of data, and the C ABI doesn't distinguish between passing 32-bit vs
|
|
78
|
+
# 64-bit integers; our 32-bit values will get loaded into 64-bit
|
|
79
|
+
# registers where syscall() will find them.)
|
|
80
|
+
_cdll_for_pidfd_open.syscall.argtypes = [
|
|
81
|
+
ctypes.c_long, # syscall number
|
|
82
|
+
ctypes.c_long, # pid
|
|
83
|
+
ctypes.c_long, # flags
|
|
84
|
+
]
|
|
85
|
+
__NR_pidfd_open = 434
|
|
86
|
+
|
|
87
|
+
def pidfd_open(fd: int, flags: int) -> int:
|
|
88
|
+
result = _cdll_for_pidfd_open.syscall(__NR_pidfd_open, fd, flags)
|
|
89
|
+
if result < 0: # pragma: no cover
|
|
90
|
+
err = ctypes.get_errno()
|
|
91
|
+
raise OSError(err, os.strerror(err))
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
can_try_pidfd_open = False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class HasFileno(Protocol):
|
|
99
|
+
"""Represents any file-like object that has a file descriptor."""
|
|
100
|
+
|
|
101
|
+
def fileno(self) -> int: ...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@final
|
|
105
|
+
class Process(metaclass=NoPublicConstructor):
|
|
106
|
+
r"""A child process. Like :class:`subprocess.Popen`, but async.
|
|
107
|
+
|
|
108
|
+
This class has no public constructor. The most common way to get a
|
|
109
|
+
`Process` object is to combine `Nursery.start` with `run_process`::
|
|
110
|
+
|
|
111
|
+
process_object = await nursery.start(run_process, ...)
|
|
112
|
+
|
|
113
|
+
This way, `run_process` supervises the process and makes sure that it is
|
|
114
|
+
cleaned up properly, while optionally checking the return value, feeding
|
|
115
|
+
it input, and so on.
|
|
116
|
+
|
|
117
|
+
If you need more control – for example, because you want to spawn a child
|
|
118
|
+
process that outlives your program – then another option is to use
|
|
119
|
+
`trio.lowlevel.open_process`::
|
|
120
|
+
|
|
121
|
+
process_object = await trio.lowlevel.open_process(...)
|
|
122
|
+
|
|
123
|
+
Attributes:
|
|
124
|
+
args (str or list): The ``command`` passed at construction time,
|
|
125
|
+
specifying the process to execute and its arguments.
|
|
126
|
+
pid (int): The process ID of the child process managed by this object.
|
|
127
|
+
stdin (trio.abc.SendStream or None): A stream connected to the child's
|
|
128
|
+
standard input stream: when you write bytes here, they become available
|
|
129
|
+
for the child to read. Only available if the :class:`Process`
|
|
130
|
+
was constructed using ``stdin=PIPE``; otherwise this will be None.
|
|
131
|
+
stdout (trio.abc.ReceiveStream or None): A stream connected to
|
|
132
|
+
the child's standard output stream: when the child writes to
|
|
133
|
+
standard output, the written bytes become available for you
|
|
134
|
+
to read here. Only available if the :class:`Process` was
|
|
135
|
+
constructed using ``stdout=PIPE``; otherwise this will be None.
|
|
136
|
+
stderr (trio.abc.ReceiveStream or None): A stream connected to
|
|
137
|
+
the child's standard error stream: when the child writes to
|
|
138
|
+
standard error, the written bytes become available for you
|
|
139
|
+
to read here. Only available if the :class:`Process` was
|
|
140
|
+
constructed using ``stderr=PIPE``; otherwise this will be None.
|
|
141
|
+
stdio (trio.StapledStream or None): A stream that sends data to
|
|
142
|
+
the child's standard input and receives from the child's standard
|
|
143
|
+
output. Only available if both :attr:`stdin` and :attr:`stdout` are
|
|
144
|
+
available; otherwise this will be None.
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
# We're always in binary mode.
|
|
149
|
+
universal_newlines: Final = False
|
|
150
|
+
encoding: Final = None
|
|
151
|
+
errors: Final = None
|
|
152
|
+
|
|
153
|
+
# Available for the per-platform wait_child_exiting() implementations
|
|
154
|
+
# to stash some state; waitid platforms use this to avoid spawning
|
|
155
|
+
# arbitrarily many threads if wait() keeps getting cancelled.
|
|
156
|
+
_wait_for_exit_data: object = None
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
popen: subprocess.Popen[bytes],
|
|
161
|
+
stdin: SendStream | None,
|
|
162
|
+
stdout: ReceiveStream | None,
|
|
163
|
+
stderr: ReceiveStream | None,
|
|
164
|
+
) -> None:
|
|
165
|
+
self._proc = popen
|
|
166
|
+
self.stdin = stdin
|
|
167
|
+
self.stdout = stdout
|
|
168
|
+
self.stderr = stderr
|
|
169
|
+
|
|
170
|
+
self.stdio: StapledStream[SendStream, ReceiveStream] | None = None
|
|
171
|
+
if self.stdin is not None and self.stdout is not None:
|
|
172
|
+
self.stdio = StapledStream(self.stdin, self.stdout)
|
|
173
|
+
|
|
174
|
+
self._wait_lock: Lock = Lock()
|
|
175
|
+
|
|
176
|
+
self._pidfd: TextIOWrapper | None = None
|
|
177
|
+
if can_try_pidfd_open:
|
|
178
|
+
try:
|
|
179
|
+
fd: int = pidfd_open(self._proc.pid, 0)
|
|
180
|
+
except OSError: # pragma: no cover
|
|
181
|
+
# Well, we tried, but it didn't work (probably because we're
|
|
182
|
+
# running on an older kernel, or in an older sandbox, that
|
|
183
|
+
# hasn't been updated to support pidfd_open). We'll fall back
|
|
184
|
+
# on waitid instead.
|
|
185
|
+
pass
|
|
186
|
+
else:
|
|
187
|
+
# It worked! Wrap the raw fd up in a Python file object to
|
|
188
|
+
# make sure it'll get closed.
|
|
189
|
+
# SIM115: open-file-with-context-handler
|
|
190
|
+
self._pidfd = open(fd) # noqa: SIM115
|
|
191
|
+
|
|
192
|
+
self.args: StrOrBytesPath | Sequence[StrOrBytesPath] = self._proc.args
|
|
193
|
+
self.pid: int = self._proc.pid
|
|
194
|
+
|
|
195
|
+
def __repr__(self) -> str:
|
|
196
|
+
returncode = self.returncode
|
|
197
|
+
if returncode is None:
|
|
198
|
+
status = f"running with PID {self.pid}"
|
|
199
|
+
else:
|
|
200
|
+
if returncode < 0:
|
|
201
|
+
status = f"exited with signal {-returncode}"
|
|
202
|
+
else:
|
|
203
|
+
status = f"exited with status {returncode}"
|
|
204
|
+
return f"<trio.Process {self.args!r}: {status}>"
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def returncode(self) -> int | None:
|
|
208
|
+
"""The exit status of the process (an integer), or ``None`` if it's
|
|
209
|
+
still running.
|
|
210
|
+
|
|
211
|
+
By convention, a return code of zero indicates success. On
|
|
212
|
+
UNIX, negative values indicate termination due to a signal,
|
|
213
|
+
e.g., -11 if terminated by signal 11 (``SIGSEGV``). On
|
|
214
|
+
Windows, a process that exits due to a call to
|
|
215
|
+
:meth:`Process.terminate` will have an exit status of 1.
|
|
216
|
+
|
|
217
|
+
Unlike the standard library `subprocess.Popen.returncode`, you don't
|
|
218
|
+
have to call `poll` or `wait` to update this attribute; it's
|
|
219
|
+
automatically updated as needed, and will always give you the latest
|
|
220
|
+
information.
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
result = self._proc.poll()
|
|
224
|
+
if result is not None:
|
|
225
|
+
self._close_pidfd()
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
def _close_pidfd(self) -> None:
|
|
229
|
+
if self._pidfd is not None:
|
|
230
|
+
trio.lowlevel.notify_closing(self._pidfd.fileno())
|
|
231
|
+
self._pidfd.close()
|
|
232
|
+
self._pidfd = None
|
|
233
|
+
|
|
234
|
+
async def wait(self) -> int:
|
|
235
|
+
"""Block until the process exits.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The exit status of the process; see :attr:`returncode`.
|
|
239
|
+
"""
|
|
240
|
+
async with self._wait_lock:
|
|
241
|
+
if self.poll() is None:
|
|
242
|
+
if self._pidfd is not None:
|
|
243
|
+
with contextlib.suppress(
|
|
244
|
+
ClosedResourceError,
|
|
245
|
+
): # something else (probably a call to poll) already closed the pidfd
|
|
246
|
+
await trio.lowlevel.wait_readable(self._pidfd.fileno())
|
|
247
|
+
else:
|
|
248
|
+
await wait_child_exiting(self)
|
|
249
|
+
# We have to use .wait() here, not .poll(), because on macOS
|
|
250
|
+
# (and maybe other systems, who knows), there's a race
|
|
251
|
+
# condition inside the kernel that creates a tiny window where
|
|
252
|
+
# kqueue reports that the process has exited, but
|
|
253
|
+
# waitpid(WNOHANG) can't yet reap it. So this .wait() may
|
|
254
|
+
# actually block for a tiny fraction of a second.
|
|
255
|
+
self._proc.wait()
|
|
256
|
+
self._close_pidfd()
|
|
257
|
+
assert self._proc.returncode is not None
|
|
258
|
+
return self._proc.returncode
|
|
259
|
+
|
|
260
|
+
def poll(self) -> int | None:
|
|
261
|
+
"""Returns the exit status of the process (an integer), or ``None`` if
|
|
262
|
+
it's still running.
|
|
263
|
+
|
|
264
|
+
Note that on Trio (unlike the standard library `subprocess.Popen`),
|
|
265
|
+
``process.poll()`` and ``process.returncode`` always give the same
|
|
266
|
+
result. See `returncode` for more details. This method is only
|
|
267
|
+
included to make it easier to port code from `subprocess`.
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
return self.returncode
|
|
271
|
+
|
|
272
|
+
def send_signal(self, sig: signal.Signals | int) -> None:
|
|
273
|
+
"""Send signal ``sig`` to the process.
|
|
274
|
+
|
|
275
|
+
On UNIX, ``sig`` may be any signal defined in the
|
|
276
|
+
:mod:`signal` module, such as ``signal.SIGINT`` or
|
|
277
|
+
``signal.SIGTERM``. On Windows, it may be anything accepted by
|
|
278
|
+
the standard library :meth:`subprocess.Popen.send_signal`.
|
|
279
|
+
"""
|
|
280
|
+
self._proc.send_signal(sig)
|
|
281
|
+
|
|
282
|
+
def terminate(self) -> None:
|
|
283
|
+
"""Terminate the process, politely if possible.
|
|
284
|
+
|
|
285
|
+
On UNIX, this is equivalent to
|
|
286
|
+
``send_signal(signal.SIGTERM)``; by convention this requests
|
|
287
|
+
graceful termination, but a misbehaving or buggy process might
|
|
288
|
+
ignore it. On Windows, :meth:`terminate` forcibly terminates the
|
|
289
|
+
process in the same manner as :meth:`kill`.
|
|
290
|
+
"""
|
|
291
|
+
self._proc.terminate()
|
|
292
|
+
|
|
293
|
+
def kill(self) -> None:
|
|
294
|
+
"""Immediately terminate the process.
|
|
295
|
+
|
|
296
|
+
On UNIX, this is equivalent to
|
|
297
|
+
``send_signal(signal.SIGKILL)``. On Windows, it calls
|
|
298
|
+
``TerminateProcess``. In both cases, the process cannot
|
|
299
|
+
prevent itself from being killed, but the termination will be
|
|
300
|
+
delivered asynchronously; use :meth:`wait` if you want to
|
|
301
|
+
ensure the process is actually dead before proceeding.
|
|
302
|
+
"""
|
|
303
|
+
self._proc.kill()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
async def _open_process(
|
|
307
|
+
command: StrOrBytesPath | Sequence[StrOrBytesPath],
|
|
308
|
+
*,
|
|
309
|
+
stdin: int | HasFileno | None = None,
|
|
310
|
+
stdout: int | HasFileno | None = None,
|
|
311
|
+
stderr: int | HasFileno | None = None,
|
|
312
|
+
**options: object,
|
|
313
|
+
) -> Process:
|
|
314
|
+
r"""Execute a child program in a new process.
|
|
315
|
+
|
|
316
|
+
After construction, you can interact with the child process by writing data to its
|
|
317
|
+
`~trio.Process.stdin` stream (a `~trio.abc.SendStream`), reading data from its
|
|
318
|
+
`~trio.Process.stdout` and/or `~trio.Process.stderr` streams (both
|
|
319
|
+
`~trio.abc.ReceiveStream`\s), sending it signals using `~trio.Process.terminate`,
|
|
320
|
+
`~trio.Process.kill`, or `~trio.Process.send_signal`, and waiting for it to exit
|
|
321
|
+
using `~trio.Process.wait`. See `trio.Process` for details.
|
|
322
|
+
|
|
323
|
+
Each standard stream is only available if you specify that a pipe should be created
|
|
324
|
+
for it. For example, if you pass ``stdin=subprocess.PIPE``, you can write to the
|
|
325
|
+
`~trio.Process.stdin` stream, else `~trio.Process.stdin` will be ``None``.
|
|
326
|
+
|
|
327
|
+
Unlike `trio.run_process`, this function doesn't do any kind of automatic
|
|
328
|
+
management of the child process. It's up to you to implement whatever semantics you
|
|
329
|
+
want.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
command: The command to run. Typically this is a sequence of strings or
|
|
333
|
+
bytes such as ``['ls', '-l', 'directory with spaces']``, where the
|
|
334
|
+
first element names the executable to invoke and the other elements
|
|
335
|
+
specify its arguments. With ``shell=True`` in the ``**options``, or on
|
|
336
|
+
Windows, ``command`` can be a string or bytes, which will be parsed
|
|
337
|
+
following platform-dependent :ref:`quoting rules
|
|
338
|
+
<subprocess-quoting>`. In all cases ``command`` can be a path or a
|
|
339
|
+
sequence of paths.
|
|
340
|
+
stdin: Specifies what the child process's standard input
|
|
341
|
+
stream should connect to: output written by the parent
|
|
342
|
+
(``subprocess.PIPE``), nothing (``subprocess.DEVNULL``),
|
|
343
|
+
or an open file (pass a file descriptor or something whose
|
|
344
|
+
``fileno`` method returns one). If ``stdin`` is unspecified,
|
|
345
|
+
the child process will have the same standard input stream
|
|
346
|
+
as its parent.
|
|
347
|
+
stdout: Like ``stdin``, but for the child process's standard output
|
|
348
|
+
stream.
|
|
349
|
+
stderr: Like ``stdin``, but for the child process's standard error
|
|
350
|
+
stream. An additional value ``subprocess.STDOUT`` is supported,
|
|
351
|
+
which causes the child's standard output and standard error
|
|
352
|
+
messages to be intermixed on a single standard output stream,
|
|
353
|
+
attached to whatever the ``stdout`` option says to attach it to.
|
|
354
|
+
**options: Other :ref:`general subprocess options <subprocess-options>`
|
|
355
|
+
are also accepted.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
A new `trio.Process` object.
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
OSError: if the process spawning fails, for example because the
|
|
362
|
+
specified command could not be found.
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
for key in ("universal_newlines", "text", "encoding", "errors", "bufsize"):
|
|
366
|
+
if options.get(key):
|
|
367
|
+
raise TypeError(
|
|
368
|
+
"trio.Process only supports communicating over "
|
|
369
|
+
f"unbuffered byte streams; the '{key}' option is not supported",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if os.name == "posix":
|
|
373
|
+
# TODO: how do paths and sequences thereof play with `shell=True`?
|
|
374
|
+
if isinstance(command, (str, bytes)) and not options.get("shell"):
|
|
375
|
+
raise TypeError(
|
|
376
|
+
"command must be a sequence (not a string or bytes) if "
|
|
377
|
+
"shell=False on UNIX systems",
|
|
378
|
+
)
|
|
379
|
+
if not isinstance(command, (str, bytes)) and options.get("shell"):
|
|
380
|
+
raise TypeError(
|
|
381
|
+
"command must be a string or bytes (not a sequence) if "
|
|
382
|
+
"shell=True on UNIX systems",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
trio_stdin: ClosableSendStream | None = None
|
|
386
|
+
trio_stdout: ClosableReceiveStream | None = None
|
|
387
|
+
trio_stderr: ClosableReceiveStream | None = None
|
|
388
|
+
# Close the parent's handle for each child side of a pipe; we want the child to
|
|
389
|
+
# have the only copy, so that when it exits we can read EOF on our side. The
|
|
390
|
+
# trio ends of pipes will be transferred to the Process object, which will be
|
|
391
|
+
# responsible for their lifetime. If process spawning fails, though, we still
|
|
392
|
+
# want to close them before letting the failure bubble out
|
|
393
|
+
with ExitStack() as always_cleanup, ExitStack() as cleanup_on_fail:
|
|
394
|
+
if stdin == subprocess.PIPE:
|
|
395
|
+
trio_stdin, stdin = create_pipe_to_child_stdin()
|
|
396
|
+
always_cleanup.callback(os.close, stdin)
|
|
397
|
+
cleanup_on_fail.callback(trio_stdin.close)
|
|
398
|
+
if stdout == subprocess.PIPE:
|
|
399
|
+
trio_stdout, stdout = create_pipe_from_child_output()
|
|
400
|
+
always_cleanup.callback(os.close, stdout)
|
|
401
|
+
cleanup_on_fail.callback(trio_stdout.close)
|
|
402
|
+
if stderr == subprocess.STDOUT:
|
|
403
|
+
# If we created a pipe for stdout, pass the same pipe for
|
|
404
|
+
# stderr. If stdout was some non-pipe thing (DEVNULL or a
|
|
405
|
+
# given FD), pass the same thing. If stdout was passed as
|
|
406
|
+
# None, keep stderr as STDOUT to allow subprocess to dup
|
|
407
|
+
# our stdout. Regardless of which of these is applicable,
|
|
408
|
+
# don't create a new Trio stream for stderr -- if stdout
|
|
409
|
+
# is piped, stderr will be intermixed on the stdout stream.
|
|
410
|
+
if stdout is not None:
|
|
411
|
+
stderr = stdout
|
|
412
|
+
elif stderr == subprocess.PIPE:
|
|
413
|
+
trio_stderr, stderr = create_pipe_from_child_output()
|
|
414
|
+
always_cleanup.callback(os.close, stderr)
|
|
415
|
+
cleanup_on_fail.callback(trio_stderr.close)
|
|
416
|
+
|
|
417
|
+
popen = await trio.to_thread.run_sync(
|
|
418
|
+
partial(
|
|
419
|
+
subprocess.Popen,
|
|
420
|
+
command,
|
|
421
|
+
stdin=stdin,
|
|
422
|
+
stdout=stdout,
|
|
423
|
+
stderr=stderr,
|
|
424
|
+
**options,
|
|
425
|
+
),
|
|
426
|
+
)
|
|
427
|
+
# We did not fail, so dismiss the stack for the trio ends
|
|
428
|
+
cleanup_on_fail.pop_all()
|
|
429
|
+
|
|
430
|
+
return Process._create(popen, trio_stdin, trio_stdout, trio_stderr)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
# async function missing await
|
|
434
|
+
async def _windows_deliver_cancel(p: Process) -> None: # noqa: RUF029
|
|
435
|
+
try:
|
|
436
|
+
p.terminate()
|
|
437
|
+
except OSError as exc:
|
|
438
|
+
warnings.warn(
|
|
439
|
+
RuntimeWarning(f"TerminateProcess on {p!r} failed with: {exc!r}"),
|
|
440
|
+
stacklevel=1,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
async def _posix_deliver_cancel(p: Process) -> None:
|
|
445
|
+
try:
|
|
446
|
+
p.terminate()
|
|
447
|
+
await trio.sleep(5)
|
|
448
|
+
warnings.warn(
|
|
449
|
+
RuntimeWarning(
|
|
450
|
+
f"process {p!r} ignored SIGTERM for 5 seconds. "
|
|
451
|
+
"(Maybe you should pass a custom deliver_cancel?) "
|
|
452
|
+
"Trying SIGKILL.",
|
|
453
|
+
),
|
|
454
|
+
stacklevel=1,
|
|
455
|
+
)
|
|
456
|
+
p.kill()
|
|
457
|
+
except OSError as exc:
|
|
458
|
+
warnings.warn(
|
|
459
|
+
RuntimeWarning(f"tried to kill process {p!r}, but failed with: {exc!r}"),
|
|
460
|
+
stacklevel=1,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
# Use a private name, so we can declare platform-specific stubs below.
|
|
465
|
+
# This is also the signature read by Sphinx
|
|
466
|
+
async def _run_process(
|
|
467
|
+
command: StrOrBytesPath | Sequence[StrOrBytesPath],
|
|
468
|
+
*,
|
|
469
|
+
stdin: bytes | bytearray | memoryview | int | HasFileno | None = b"",
|
|
470
|
+
capture_stdout: bool = False,
|
|
471
|
+
capture_stderr: bool = False,
|
|
472
|
+
check: bool = True,
|
|
473
|
+
deliver_cancel: Callable[[Process], Awaitable[object]] | None = None,
|
|
474
|
+
task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED,
|
|
475
|
+
**options: object,
|
|
476
|
+
) -> subprocess.CompletedProcess[bytes]:
|
|
477
|
+
"""Run ``command`` in a subprocess and wait for it to complete.
|
|
478
|
+
|
|
479
|
+
This function can be called in two different ways.
|
|
480
|
+
|
|
481
|
+
One option is a direct call, like::
|
|
482
|
+
|
|
483
|
+
completed_process_info = await trio.run_process(...)
|
|
484
|
+
|
|
485
|
+
In this case, it returns a :class:`subprocess.CompletedProcess` instance
|
|
486
|
+
describing the results. Use this if you want to treat a process like a
|
|
487
|
+
function call.
|
|
488
|
+
|
|
489
|
+
The other option is to run it as a task using `Nursery.start` – the enhanced version
|
|
490
|
+
of `~Nursery.start_soon` that lets a task pass back a value during startup::
|
|
491
|
+
|
|
492
|
+
process = await nursery.start(trio.run_process, ...)
|
|
493
|
+
|
|
494
|
+
In this case, `~Nursery.start` returns a `Process` object that you can use
|
|
495
|
+
to interact with the process while it's running. Use this if you want to
|
|
496
|
+
treat a process like a background task.
|
|
497
|
+
|
|
498
|
+
Either way, `run_process` makes sure that the process has exited before
|
|
499
|
+
returning, handles cancellation, optionally checks for errors, and
|
|
500
|
+
provides some convenient shorthands for dealing with the child's
|
|
501
|
+
input/output.
|
|
502
|
+
|
|
503
|
+
**Input:** `run_process` supports all the same ``stdin=`` arguments as
|
|
504
|
+
`subprocess.Popen`. In addition, if you simply want to pass in some fixed
|
|
505
|
+
data, you can pass a plain `bytes` object, and `run_process` will take
|
|
506
|
+
care of setting up a pipe, feeding in the data you gave, and then sending
|
|
507
|
+
end-of-file. The default is ``b""``, which means that the child will receive
|
|
508
|
+
an empty stdin. If you want the child to instead read from the parent's
|
|
509
|
+
stdin, use ``stdin=None``.
|
|
510
|
+
|
|
511
|
+
**Output:** By default, any output produced by the subprocess is
|
|
512
|
+
passed through to the standard output and error streams of the
|
|
513
|
+
parent Trio process.
|
|
514
|
+
|
|
515
|
+
When calling `run_process` directly, you can capture the subprocess's output by
|
|
516
|
+
passing ``capture_stdout=True`` to capture the subprocess's standard output, and/or
|
|
517
|
+
``capture_stderr=True`` to capture its standard error. Captured data is collected up
|
|
518
|
+
by Trio into an in-memory buffer, and then provided as the
|
|
519
|
+
:attr:`~subprocess.CompletedProcess.stdout` and/or
|
|
520
|
+
:attr:`~subprocess.CompletedProcess.stderr` attributes of the returned
|
|
521
|
+
:class:`~subprocess.CompletedProcess` object. The value for any stream that was not
|
|
522
|
+
captured will be ``None``.
|
|
523
|
+
|
|
524
|
+
If you want to capture both stdout and stderr while keeping them
|
|
525
|
+
separate, pass ``capture_stdout=True, capture_stderr=True``.
|
|
526
|
+
|
|
527
|
+
If you want to capture both stdout and stderr but mixed together
|
|
528
|
+
in the order they were printed, use: ``capture_stdout=True, stderr=subprocess.STDOUT``.
|
|
529
|
+
This directs the child's stderr into its stdout, so the combined
|
|
530
|
+
output will be available in the `~subprocess.CompletedProcess.stdout`
|
|
531
|
+
attribute.
|
|
532
|
+
|
|
533
|
+
If you're using ``await nursery.start(trio.run_process, ...)`` and want to capture
|
|
534
|
+
the subprocess's output for further processing, then use ``stdout=subprocess.PIPE``
|
|
535
|
+
and then make sure to read the data out of the `Process.stdout` stream. If you want
|
|
536
|
+
to capture stderr separately, use ``stderr=subprocess.PIPE``. If you want to capture
|
|
537
|
+
both, but mixed together in the correct order, use ``stdout=subprocess.PIPE,
|
|
538
|
+
stderr=subprocess.STDOUT``.
|
|
539
|
+
|
|
540
|
+
**Error checking:** If the subprocess exits with a nonzero status
|
|
541
|
+
code, indicating failure, :func:`run_process` raises a
|
|
542
|
+
:exc:`subprocess.CalledProcessError` exception rather than
|
|
543
|
+
returning normally. The captured outputs are still available as
|
|
544
|
+
the :attr:`~subprocess.CalledProcessError.stdout` and
|
|
545
|
+
:attr:`~subprocess.CalledProcessError.stderr` attributes of that
|
|
546
|
+
exception. To disable this behavior, so that :func:`run_process`
|
|
547
|
+
returns normally even if the subprocess exits abnormally, pass ``check=False``.
|
|
548
|
+
|
|
549
|
+
Note that this can make the ``capture_stdout`` and ``capture_stderr``
|
|
550
|
+
arguments useful even when starting `run_process` as a task: if you only
|
|
551
|
+
care about the output if the process fails, then you can enable capturing
|
|
552
|
+
and then read the output off of the `~subprocess.CalledProcessError`.
|
|
553
|
+
|
|
554
|
+
**Cancellation:** If cancelled, `run_process` sends a termination
|
|
555
|
+
request to the subprocess, then waits for it to fully exit. The
|
|
556
|
+
``deliver_cancel`` argument lets you control how the process is terminated.
|
|
557
|
+
|
|
558
|
+
.. note:: `run_process` is intentionally similar to the standard library
|
|
559
|
+
`subprocess.run`, but some of the defaults are different. Specifically, we
|
|
560
|
+
default to:
|
|
561
|
+
|
|
562
|
+
- ``check=True``, because `"errors should never pass silently / unless
|
|
563
|
+
explicitly silenced" <https://www.python.org/dev/peps/pep-0020/>`__.
|
|
564
|
+
|
|
565
|
+
- ``stdin=b""``, because it produces less-confusing results if a subprocess
|
|
566
|
+
unexpectedly tries to read from stdin.
|
|
567
|
+
|
|
568
|
+
To get the `subprocess.run` semantics, use ``check=False, stdin=None``.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
command (list or str): The command to run. Typically this is a
|
|
572
|
+
sequence of strings such as ``['ls', '-l', 'directory with spaces']``,
|
|
573
|
+
where the first element names the executable to invoke and the other
|
|
574
|
+
elements specify its arguments. With ``shell=True`` in the
|
|
575
|
+
``**options``, or on Windows, ``command`` may alternatively
|
|
576
|
+
be a string, which will be parsed following platform-dependent
|
|
577
|
+
:ref:`quoting rules <subprocess-quoting>`.
|
|
578
|
+
|
|
579
|
+
stdin (:obj:`bytes`, subprocess.PIPE, file descriptor, or None): The
|
|
580
|
+
bytes to provide to the subprocess on its standard input stream, or
|
|
581
|
+
``None`` if the subprocess's standard input should come from the
|
|
582
|
+
same place as the parent Trio process's standard input. As is the
|
|
583
|
+
case with the :mod:`subprocess` module, you can also pass a file
|
|
584
|
+
descriptor or an object with a ``fileno()`` method, in which case
|
|
585
|
+
the subprocess's standard input will come from that file.
|
|
586
|
+
|
|
587
|
+
When starting `run_process` as a background task, you can also use
|
|
588
|
+
``stdin=subprocess.PIPE``, in which case `Process.stdin` will be a
|
|
589
|
+
`~trio.abc.SendStream` that you can use to send data to the child.
|
|
590
|
+
|
|
591
|
+
capture_stdout (bool): If true, capture the bytes that the subprocess
|
|
592
|
+
writes to its standard output stream and return them in the
|
|
593
|
+
`~subprocess.CompletedProcess.stdout` attribute of the returned
|
|
594
|
+
`subprocess.CompletedProcess` or `subprocess.CalledProcessError`.
|
|
595
|
+
|
|
596
|
+
capture_stderr (bool): If true, capture the bytes that the subprocess
|
|
597
|
+
writes to its standard error stream and return them in the
|
|
598
|
+
`~subprocess.CompletedProcess.stderr` attribute of the returned
|
|
599
|
+
`~subprocess.CompletedProcess` or `subprocess.CalledProcessError`.
|
|
600
|
+
|
|
601
|
+
check (bool): If false, don't validate that the subprocess exits
|
|
602
|
+
successfully. You should be sure to check the
|
|
603
|
+
``returncode`` attribute of the returned object if you pass
|
|
604
|
+
``check=False``, so that errors don't pass silently.
|
|
605
|
+
|
|
606
|
+
deliver_cancel (async function or None): If `run_process` is cancelled,
|
|
607
|
+
then it needs to kill the child process. There are multiple ways to
|
|
608
|
+
do this, so we let you customize it.
|
|
609
|
+
|
|
610
|
+
If you pass None (the default), then the behavior depends on the
|
|
611
|
+
platform:
|
|
612
|
+
|
|
613
|
+
- On Windows, Trio calls ``TerminateProcess``, which should kill the
|
|
614
|
+
process immediately.
|
|
615
|
+
|
|
616
|
+
- On Unix-likes, the default behavior is to send a ``SIGTERM``, wait
|
|
617
|
+
5 seconds, and send a ``SIGKILL``.
|
|
618
|
+
|
|
619
|
+
Alternatively, you can customize this behavior by passing in an
|
|
620
|
+
arbitrary async function, which will be called with the `Process`
|
|
621
|
+
object as an argument. For example, the default Unix behavior could
|
|
622
|
+
be implemented like this::
|
|
623
|
+
|
|
624
|
+
async def my_deliver_cancel(process):
|
|
625
|
+
process.send_signal(signal.SIGTERM)
|
|
626
|
+
await trio.sleep(5)
|
|
627
|
+
process.send_signal(signal.SIGKILL)
|
|
628
|
+
|
|
629
|
+
When the process actually exits, the ``deliver_cancel`` function
|
|
630
|
+
will automatically be cancelled – so if the process exits after
|
|
631
|
+
``SIGTERM``, then we'll never reach the ``SIGKILL``.
|
|
632
|
+
|
|
633
|
+
In any case, `run_process` will always wait for the child process to
|
|
634
|
+
exit before raising `Cancelled`.
|
|
635
|
+
|
|
636
|
+
**options: :func:`run_process` also accepts any :ref:`general subprocess
|
|
637
|
+
options <subprocess-options>` and passes them on to the
|
|
638
|
+
:class:`~trio.Process` constructor. This includes the
|
|
639
|
+
``stdout`` and ``stderr`` options, which provide additional
|
|
640
|
+
redirection possibilities such as ``stderr=subprocess.STDOUT``,
|
|
641
|
+
``stdout=subprocess.DEVNULL``, or file descriptors.
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
|
|
645
|
+
When called normally – a `subprocess.CompletedProcess` instance
|
|
646
|
+
describing the return code and outputs.
|
|
647
|
+
|
|
648
|
+
When called via `Nursery.start` – a `trio.Process` instance.
|
|
649
|
+
|
|
650
|
+
Raises:
|
|
651
|
+
UnicodeError: if ``stdin`` is specified as a Unicode string, rather
|
|
652
|
+
than bytes
|
|
653
|
+
ValueError: if multiple redirections are specified for the same
|
|
654
|
+
stream, e.g., both ``capture_stdout=True`` and
|
|
655
|
+
``stdout=subprocess.DEVNULL``
|
|
656
|
+
subprocess.CalledProcessError: if ``check=False`` is not passed
|
|
657
|
+
and the process exits with a nonzero exit status
|
|
658
|
+
OSError: if an error is encountered starting or communicating with
|
|
659
|
+
the process
|
|
660
|
+
ExceptionGroup: if exceptions occur in ``deliver_cancel``,
|
|
661
|
+
or when exceptions occur when communicating with the subprocess.
|
|
662
|
+
If strict_exception_groups is set to false in the global context,
|
|
663
|
+
which is deprecated, then single exceptions will be collapsed.
|
|
664
|
+
|
|
665
|
+
.. note:: The child process runs in the same process group as the parent
|
|
666
|
+
Trio process, so a Ctrl+C will be delivered simultaneously to both
|
|
667
|
+
parent and child. If you don't want this behavior, consult your
|
|
668
|
+
platform's documentation for starting child processes in a different
|
|
669
|
+
process group.
|
|
670
|
+
|
|
671
|
+
"""
|
|
672
|
+
|
|
673
|
+
if isinstance(stdin, str):
|
|
674
|
+
raise UnicodeError("process stdin must be bytes, not str")
|
|
675
|
+
if task_status is trio.TASK_STATUS_IGNORED:
|
|
676
|
+
if stdin is subprocess.PIPE:
|
|
677
|
+
raise ValueError(
|
|
678
|
+
"stdout=subprocess.PIPE is only valid with nursery.start, "
|
|
679
|
+
"since that's the only way to access the pipe; use nursery.start "
|
|
680
|
+
"or pass the data you want to write directly",
|
|
681
|
+
)
|
|
682
|
+
if options.get("stdout") is subprocess.PIPE:
|
|
683
|
+
raise ValueError(
|
|
684
|
+
"stdout=subprocess.PIPE is only valid with nursery.start, "
|
|
685
|
+
"since that's the only way to access the pipe",
|
|
686
|
+
)
|
|
687
|
+
if options.get("stderr") is subprocess.PIPE:
|
|
688
|
+
raise ValueError(
|
|
689
|
+
"stderr=subprocess.PIPE is only valid with nursery.start, "
|
|
690
|
+
"since that's the only way to access the pipe",
|
|
691
|
+
)
|
|
692
|
+
if isinstance(stdin, (bytes, bytearray, memoryview)):
|
|
693
|
+
input_ = stdin
|
|
694
|
+
options["stdin"] = subprocess.PIPE
|
|
695
|
+
else:
|
|
696
|
+
# stdin should be something acceptable to Process
|
|
697
|
+
# (None, DEVNULL, a file descriptor, etc) and Process
|
|
698
|
+
# will raise if it's not
|
|
699
|
+
input_ = None
|
|
700
|
+
options["stdin"] = stdin
|
|
701
|
+
|
|
702
|
+
if capture_stdout:
|
|
703
|
+
if "stdout" in options:
|
|
704
|
+
raise ValueError("can't specify both stdout and capture_stdout")
|
|
705
|
+
options["stdout"] = subprocess.PIPE
|
|
706
|
+
if capture_stderr:
|
|
707
|
+
if "stderr" in options:
|
|
708
|
+
raise ValueError("can't specify both stderr and capture_stderr")
|
|
709
|
+
options["stderr"] = subprocess.PIPE
|
|
710
|
+
|
|
711
|
+
if deliver_cancel is None:
|
|
712
|
+
if os.name == "nt":
|
|
713
|
+
deliver_cancel = _windows_deliver_cancel
|
|
714
|
+
else:
|
|
715
|
+
assert os.name == "posix"
|
|
716
|
+
deliver_cancel = _posix_deliver_cancel
|
|
717
|
+
|
|
718
|
+
stdout_chunks: list[bytes | bytearray] = []
|
|
719
|
+
stderr_chunks: list[bytes | bytearray] = []
|
|
720
|
+
|
|
721
|
+
async def feed_input(stream: SendStream) -> None:
|
|
722
|
+
async with stream:
|
|
723
|
+
try:
|
|
724
|
+
assert input_ is not None
|
|
725
|
+
await stream.send_all(input_)
|
|
726
|
+
except trio.BrokenResourceError:
|
|
727
|
+
pass
|
|
728
|
+
|
|
729
|
+
async def read_output(
|
|
730
|
+
stream: ReceiveStream,
|
|
731
|
+
chunks: list[bytes | bytearray],
|
|
732
|
+
) -> None:
|
|
733
|
+
async with stream:
|
|
734
|
+
async for chunk in stream:
|
|
735
|
+
chunks.append(chunk) # noqa: PERF401
|
|
736
|
+
|
|
737
|
+
# Opening the process does not need to be inside the nursery, so we put it outside
|
|
738
|
+
# so any exceptions get directly seen by users.
|
|
739
|
+
proc = await _open_process(command, **options) # type: ignore[arg-type]
|
|
740
|
+
async with trio.open_nursery() as nursery:
|
|
741
|
+
try:
|
|
742
|
+
if input_ is not None:
|
|
743
|
+
assert proc.stdin is not None
|
|
744
|
+
nursery.start_soon(feed_input, proc.stdin)
|
|
745
|
+
proc.stdin = None
|
|
746
|
+
proc.stdio = None
|
|
747
|
+
if capture_stdout:
|
|
748
|
+
assert proc.stdout is not None
|
|
749
|
+
nursery.start_soon(read_output, proc.stdout, stdout_chunks)
|
|
750
|
+
proc.stdout = None
|
|
751
|
+
proc.stdio = None
|
|
752
|
+
if capture_stderr:
|
|
753
|
+
assert proc.stderr is not None
|
|
754
|
+
nursery.start_soon(read_output, proc.stderr, stderr_chunks)
|
|
755
|
+
proc.stderr = None
|
|
756
|
+
task_status.started(proc)
|
|
757
|
+
await proc.wait()
|
|
758
|
+
except BaseException:
|
|
759
|
+
with trio.CancelScope(shield=True):
|
|
760
|
+
killer_cscope = trio.CancelScope(shield=True)
|
|
761
|
+
|
|
762
|
+
async def killer() -> None:
|
|
763
|
+
with killer_cscope:
|
|
764
|
+
await deliver_cancel(proc)
|
|
765
|
+
|
|
766
|
+
nursery.start_soon(killer)
|
|
767
|
+
await proc.wait()
|
|
768
|
+
killer_cscope.cancel(reason="trio internal implementation detail")
|
|
769
|
+
raise
|
|
770
|
+
|
|
771
|
+
stdout = b"".join(stdout_chunks) if capture_stdout else None
|
|
772
|
+
stderr = b"".join(stderr_chunks) if capture_stderr else None
|
|
773
|
+
|
|
774
|
+
if proc.returncode and check:
|
|
775
|
+
raise subprocess.CalledProcessError(
|
|
776
|
+
proc.returncode,
|
|
777
|
+
proc.args,
|
|
778
|
+
output=stdout,
|
|
779
|
+
stderr=stderr,
|
|
780
|
+
)
|
|
781
|
+
else:
|
|
782
|
+
assert proc.returncode is not None
|
|
783
|
+
return subprocess.CompletedProcess(proc.args, proc.returncode, stdout, stderr)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
# There's a lot of duplication here because type checkers don't
|
|
787
|
+
# have a good way to represent overloads that differ only
|
|
788
|
+
# slightly. A cheat sheet:
|
|
789
|
+
#
|
|
790
|
+
# - on Windows, command is Union[str, Sequence[str]];
|
|
791
|
+
# on Unix, command is str if shell=True and Sequence[str] otherwise
|
|
792
|
+
#
|
|
793
|
+
# - on Windows, there are startupinfo and creationflags options;
|
|
794
|
+
# on Unix, there are preexec_fn, restore_signals, start_new_session,
|
|
795
|
+
# pass_fds, group (3.9+), extra_groups (3.9+), user (3.9+),
|
|
796
|
+
# umask (3.9+), pipesize (3.10+), process_group (3.11+)
|
|
797
|
+
#
|
|
798
|
+
# - run_process() has the signature of open_process() plus arguments
|
|
799
|
+
# capture_stdout, capture_stderr, check, deliver_cancel, the ability
|
|
800
|
+
# to pass bytes as stdin, and the ability to run in `nursery.start`
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
class GeneralProcessArgs(TypedDict, total=False):
|
|
804
|
+
"""Arguments shared between all runs."""
|
|
805
|
+
|
|
806
|
+
stdout: int | HasFileno | None
|
|
807
|
+
stderr: int | HasFileno | None
|
|
808
|
+
close_fds: bool
|
|
809
|
+
cwd: StrOrBytesPath | None
|
|
810
|
+
env: Mapping[str, str] | None
|
|
811
|
+
executable: StrOrBytesPath | None
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
if TYPE_CHECKING:
|
|
815
|
+
if sys.platform == "win32":
|
|
816
|
+
|
|
817
|
+
class WindowsProcessArgs(GeneralProcessArgs, total=False):
|
|
818
|
+
"""Arguments shared between all Windows runs."""
|
|
819
|
+
|
|
820
|
+
shell: bool
|
|
821
|
+
startupinfo: subprocess.STARTUPINFO | None
|
|
822
|
+
creationflags: int
|
|
823
|
+
|
|
824
|
+
async def open_process(
|
|
825
|
+
command: StrOrBytesPath | Sequence[StrOrBytesPath],
|
|
826
|
+
*,
|
|
827
|
+
stdin: int | HasFileno | None = None,
|
|
828
|
+
**kwargs: Unpack[WindowsProcessArgs],
|
|
829
|
+
) -> trio.Process:
|
|
830
|
+
r"""Execute a child program in a new process.
|
|
831
|
+
|
|
832
|
+
After construction, you can interact with the child process by writing data to its
|
|
833
|
+
`~trio.Process.stdin` stream (a `~trio.abc.SendStream`), reading data from its
|
|
834
|
+
`~trio.Process.stdout` and/or `~trio.Process.stderr` streams (both
|
|
835
|
+
`~trio.abc.ReceiveStream`\s), sending it signals using `~trio.Process.terminate`,
|
|
836
|
+
`~trio.Process.kill`, or `~trio.Process.send_signal`, and waiting for it to exit
|
|
837
|
+
using `~trio.Process.wait`. See `trio.Process` for details.
|
|
838
|
+
|
|
839
|
+
Each standard stream is only available if you specify that a pipe should be created
|
|
840
|
+
for it. For example, if you pass ``stdin=subprocess.PIPE``, you can write to the
|
|
841
|
+
`~trio.Process.stdin` stream, else `~trio.Process.stdin` will be ``None``.
|
|
842
|
+
|
|
843
|
+
Unlike `trio.run_process`, this function doesn't do any kind of automatic
|
|
844
|
+
management of the child process. It's up to you to implement whatever semantics you
|
|
845
|
+
want.
|
|
846
|
+
|
|
847
|
+
Args:
|
|
848
|
+
command (list or str): The command to run. Typically this is a
|
|
849
|
+
sequence of strings such as ``['ls', '-l', 'directory with spaces']``,
|
|
850
|
+
where the first element names the executable to invoke and the other
|
|
851
|
+
elements specify its arguments. With ``shell=True`` in the
|
|
852
|
+
``**options``, or on Windows, ``command`` may alternatively
|
|
853
|
+
be a string, which will be parsed following platform-dependent
|
|
854
|
+
:ref:`quoting rules <subprocess-quoting>`.
|
|
855
|
+
stdin: Specifies what the child process's standard input
|
|
856
|
+
stream should connect to: output written by the parent
|
|
857
|
+
(``subprocess.PIPE``), nothing (``subprocess.DEVNULL``),
|
|
858
|
+
or an open file (pass a file descriptor or something whose
|
|
859
|
+
``fileno`` method returns one). If ``stdin`` is unspecified,
|
|
860
|
+
the child process will have the same standard input stream
|
|
861
|
+
as its parent.
|
|
862
|
+
stdout: Like ``stdin``, but for the child process's standard output
|
|
863
|
+
stream.
|
|
864
|
+
stderr: Like ``stdin``, but for the child process's standard error
|
|
865
|
+
stream. An additional value ``subprocess.STDOUT`` is supported,
|
|
866
|
+
which causes the child's standard output and standard error
|
|
867
|
+
messages to be intermixed on a single standard output stream,
|
|
868
|
+
attached to whatever the ``stdout`` option says to attach it to.
|
|
869
|
+
**options: Other :ref:`general subprocess options <subprocess-options>`
|
|
870
|
+
are also accepted.
|
|
871
|
+
|
|
872
|
+
Returns:
|
|
873
|
+
A new `trio.Process` object.
|
|
874
|
+
|
|
875
|
+
Raises:
|
|
876
|
+
OSError: if the process spawning fails, for example because the
|
|
877
|
+
specified command could not be found.
|
|
878
|
+
|
|
879
|
+
"""
|
|
880
|
+
...
|
|
881
|
+
|
|
882
|
+
async def run_process(
|
|
883
|
+
command: StrOrBytesPath | Sequence[StrOrBytesPath],
|
|
884
|
+
*,
|
|
885
|
+
task_status: TaskStatus[Process] = trio.TASK_STATUS_IGNORED,
|
|
886
|
+
stdin: bytes | bytearray | memoryview | int | HasFileno | None = None,
|
|
887
|
+
capture_stdout: bool = False,
|
|
888
|
+
capture_stderr: bool = False,
|
|
889
|
+
check: bool = True,
|
|
890
|
+
deliver_cancel: Callable[[Process], Awaitable[object]] | None = None,
|
|
891
|
+
**kwargs: Unpack[WindowsProcessArgs],
|
|
892
|
+
) -> subprocess.CompletedProcess[bytes]:
|
|
893
|
+
"""Run ``command`` in a subprocess and wait for it to complete.
|
|
894
|
+
|
|
895
|
+
This function can be called in two different ways.
|
|
896
|
+
|
|
897
|
+
One option is a direct call, like::
|
|
898
|
+
|
|
899
|
+
completed_process_info = await trio.run_process(...)
|
|
900
|
+
|
|
901
|
+
In this case, it returns a :class:`subprocess.CompletedProcess` instance
|
|
902
|
+
describing the results. Use this if you want to treat a process like a
|
|
903
|
+
function call.
|
|
904
|
+
|
|
905
|
+
The other option is to run it as a task using `Nursery.start` – the enhanced version
|
|
906
|
+
of `~Nursery.start_soon` that lets a task pass back a value during startup::
|
|
907
|
+
|
|
908
|
+
process = await nursery.start(trio.run_process, ...)
|
|
909
|
+
|
|
910
|
+
In this case, `~Nursery.start` returns a `Process` object that you can use
|
|
911
|
+
to interact with the process while it's running. Use this if you want to
|
|
912
|
+
treat a process like a background task.
|
|
913
|
+
|
|
914
|
+
Either way, `run_process` makes sure that the process has exited before
|
|
915
|
+
returning, handles cancellation, optionally checks for errors, and
|
|
916
|
+
provides some convenient shorthands for dealing with the child's
|
|
917
|
+
input/output.
|
|
918
|
+
|
|
919
|
+
**Input:** `run_process` supports all the same ``stdin=`` arguments as
|
|
920
|
+
`subprocess.Popen`. In addition, if you simply want to pass in some fixed
|
|
921
|
+
data, you can pass a plain `bytes` object, and `run_process` will take
|
|
922
|
+
care of setting up a pipe, feeding in the data you gave, and then sending
|
|
923
|
+
end-of-file. The default is ``b""``, which means that the child will receive
|
|
924
|
+
an empty stdin. If you want the child to instead read from the parent's
|
|
925
|
+
stdin, use ``stdin=None``.
|
|
926
|
+
|
|
927
|
+
**Output:** By default, any output produced by the subprocess is
|
|
928
|
+
passed through to the standard output and error streams of the
|
|
929
|
+
parent Trio process.
|
|
930
|
+
|
|
931
|
+
When calling `run_process` directly, you can capture the subprocess's output by
|
|
932
|
+
passing ``capture_stdout=True`` to capture the subprocess's standard output, and/or
|
|
933
|
+
``capture_stderr=True`` to capture its standard error. Captured data is collected up
|
|
934
|
+
by Trio into an in-memory buffer, and then provided as the
|
|
935
|
+
:attr:`~subprocess.CompletedProcess.stdout` and/or
|
|
936
|
+
:attr:`~subprocess.CompletedProcess.stderr` attributes of the returned
|
|
937
|
+
:class:`~subprocess.CompletedProcess` object. The value for any stream that was not
|
|
938
|
+
captured will be ``None``.
|
|
939
|
+
|
|
940
|
+
If you want to capture both stdout and stderr while keeping them
|
|
941
|
+
separate, pass ``capture_stdout=True, capture_stderr=True``.
|
|
942
|
+
|
|
943
|
+
If you want to capture both stdout and stderr but mixed together
|
|
944
|
+
in the order they were printed, use: ``capture_stdout=True, stderr=subprocess.STDOUT``.
|
|
945
|
+
This directs the child's stderr into its stdout, so the combined
|
|
946
|
+
output will be available in the `~subprocess.CompletedProcess.stdout`
|
|
947
|
+
attribute.
|
|
948
|
+
|
|
949
|
+
If you're using ``await nursery.start(trio.run_process, ...)`` and want to capture
|
|
950
|
+
the subprocess's output for further processing, then use ``stdout=subprocess.PIPE``
|
|
951
|
+
and then make sure to read the data out of the `Process.stdout` stream. If you want
|
|
952
|
+
to capture stderr separately, use ``stderr=subprocess.PIPE``. If you want to capture
|
|
953
|
+
both, but mixed together in the correct order, use ``stdout=subprocess.PIPE,
|
|
954
|
+
stderr=subprocess.STDOUT``.
|
|
955
|
+
|
|
956
|
+
**Error checking:** If the subprocess exits with a nonzero status
|
|
957
|
+
code, indicating failure, :func:`run_process` raises a
|
|
958
|
+
:exc:`subprocess.CalledProcessError` exception rather than
|
|
959
|
+
returning normally. The captured outputs are still available as
|
|
960
|
+
the :attr:`~subprocess.CalledProcessError.stdout` and
|
|
961
|
+
:attr:`~subprocess.CalledProcessError.stderr` attributes of that
|
|
962
|
+
exception. To disable this behavior, so that :func:`run_process`
|
|
963
|
+
returns normally even if the subprocess exits abnormally, pass ``check=False``.
|
|
964
|
+
|
|
965
|
+
Note that this can make the ``capture_stdout`` and ``capture_stderr``
|
|
966
|
+
arguments useful even when starting `run_process` as a task: if you only
|
|
967
|
+
care about the output if the process fails, then you can enable capturing
|
|
968
|
+
and then read the output off of the `~subprocess.CalledProcessError`.
|
|
969
|
+
|
|
970
|
+
**Cancellation:** If cancelled, `run_process` sends a termination
|
|
971
|
+
request to the subprocess, then waits for it to fully exit. The
|
|
972
|
+
``deliver_cancel`` argument lets you control how the process is terminated.
|
|
973
|
+
|
|
974
|
+
.. note:: `run_process` is intentionally similar to the standard library
|
|
975
|
+
`subprocess.run`, but some of the defaults are different. Specifically, we
|
|
976
|
+
default to:
|
|
977
|
+
|
|
978
|
+
- ``check=True``, because `"errors should never pass silently / unless
|
|
979
|
+
explicitly silenced" <https://www.python.org/dev/peps/pep-0020/>`__.
|
|
980
|
+
|
|
981
|
+
- ``stdin=b""``, because it produces less-confusing results if a subprocess
|
|
982
|
+
unexpectedly tries to read from stdin.
|
|
983
|
+
|
|
984
|
+
To get the `subprocess.run` semantics, use ``check=False, stdin=None``.
|
|
985
|
+
|
|
986
|
+
Args:
|
|
987
|
+
command (list or str): The command to run. Typically this is a
|
|
988
|
+
sequence of strings such as ``['ls', '-l', 'directory with spaces']``,
|
|
989
|
+
where the first element names the executable to invoke and the other
|
|
990
|
+
elements specify its arguments. With ``shell=True`` in the
|
|
991
|
+
``**options``, or on Windows, ``command`` may alternatively
|
|
992
|
+
be a string, which will be parsed following platform-dependent
|
|
993
|
+
:ref:`quoting rules <subprocess-quoting>`.
|
|
994
|
+
|
|
995
|
+
stdin (:obj:`bytes`, subprocess.PIPE, file descriptor, or None): The
|
|
996
|
+
bytes to provide to the subprocess on its standard input stream, or
|
|
997
|
+
``None`` if the subprocess's standard input should come from the
|
|
998
|
+
same place as the parent Trio process's standard input. As is the
|
|
999
|
+
case with the :mod:`subprocess` module, you can also pass a file
|
|
1000
|
+
descriptor or an object with a ``fileno()`` method, in which case
|
|
1001
|
+
the subprocess's standard input will come from that file.
|
|
1002
|
+
|
|
1003
|
+
When starting `run_process` as a background task, you can also use
|
|
1004
|
+
``stdin=subprocess.PIPE``, in which case `Process.stdin` will be a
|
|
1005
|
+
`~trio.abc.SendStream` that you can use to send data to the child.
|
|
1006
|
+
|
|
1007
|
+
capture_stdout (bool): If true, capture the bytes that the subprocess
|
|
1008
|
+
writes to its standard output stream and return them in the
|
|
1009
|
+
`~subprocess.CompletedProcess.stdout` attribute of the returned
|
|
1010
|
+
`subprocess.CompletedProcess` or `subprocess.CalledProcessError`.
|
|
1011
|
+
|
|
1012
|
+
capture_stderr (bool): If true, capture the bytes that the subprocess
|
|
1013
|
+
writes to its standard error stream and return them in the
|
|
1014
|
+
`~subprocess.CompletedProcess.stderr` attribute of the returned
|
|
1015
|
+
`~subprocess.CompletedProcess` or `subprocess.CalledProcessError`.
|
|
1016
|
+
|
|
1017
|
+
check (bool): If false, don't validate that the subprocess exits
|
|
1018
|
+
successfully. You should be sure to check the
|
|
1019
|
+
``returncode`` attribute of the returned object if you pass
|
|
1020
|
+
``check=False``, so that errors don't pass silently.
|
|
1021
|
+
|
|
1022
|
+
deliver_cancel (async function or None): If `run_process` is cancelled,
|
|
1023
|
+
then it needs to kill the child process. There are multiple ways to
|
|
1024
|
+
do this, so we let you customize it.
|
|
1025
|
+
|
|
1026
|
+
If you pass None (the default), then the behavior depends on the
|
|
1027
|
+
platform:
|
|
1028
|
+
|
|
1029
|
+
- On Windows, Trio calls ``TerminateProcess``, which should kill the
|
|
1030
|
+
process immediately.
|
|
1031
|
+
|
|
1032
|
+
- On Unix-likes, the default behavior is to send a ``SIGTERM``, wait
|
|
1033
|
+
5 seconds, and send a ``SIGKILL``.
|
|
1034
|
+
|
|
1035
|
+
Alternatively, you can customize this behavior by passing in an
|
|
1036
|
+
arbitrary async function, which will be called with the `Process`
|
|
1037
|
+
object as an argument. For example, the default Unix behavior could
|
|
1038
|
+
be implemented like this::
|
|
1039
|
+
|
|
1040
|
+
async def my_deliver_cancel(process):
|
|
1041
|
+
process.send_signal(signal.SIGTERM)
|
|
1042
|
+
await trio.sleep(5)
|
|
1043
|
+
process.send_signal(signal.SIGKILL)
|
|
1044
|
+
|
|
1045
|
+
When the process actually exits, the ``deliver_cancel`` function
|
|
1046
|
+
will automatically be cancelled – so if the process exits after
|
|
1047
|
+
``SIGTERM``, then we'll never reach the ``SIGKILL``.
|
|
1048
|
+
|
|
1049
|
+
In any case, `run_process` will always wait for the child process to
|
|
1050
|
+
exit before raising `Cancelled`.
|
|
1051
|
+
|
|
1052
|
+
**options: :func:`run_process` also accepts any :ref:`general subprocess
|
|
1053
|
+
options <subprocess-options>` and passes them on to the
|
|
1054
|
+
:class:`~trio.Process` constructor. This includes the
|
|
1055
|
+
``stdout`` and ``stderr`` options, which provide additional
|
|
1056
|
+
redirection possibilities such as ``stderr=subprocess.STDOUT``,
|
|
1057
|
+
``stdout=subprocess.DEVNULL``, or file descriptors.
|
|
1058
|
+
|
|
1059
|
+
Returns:
|
|
1060
|
+
|
|
1061
|
+
When called normally – a `subprocess.CompletedProcess` instance
|
|
1062
|
+
describing the return code and outputs.
|
|
1063
|
+
|
|
1064
|
+
When called via `Nursery.start` – a `trio.Process` instance.
|
|
1065
|
+
|
|
1066
|
+
Raises:
|
|
1067
|
+
UnicodeError: if ``stdin`` is specified as a Unicode string, rather
|
|
1068
|
+
than bytes
|
|
1069
|
+
ValueError: if multiple redirections are specified for the same
|
|
1070
|
+
stream, e.g., both ``capture_stdout=True`` and
|
|
1071
|
+
``stdout=subprocess.DEVNULL``
|
|
1072
|
+
subprocess.CalledProcessError: if ``check=False`` is not passed
|
|
1073
|
+
and the process exits with a nonzero exit status
|
|
1074
|
+
OSError: if an error is encountered starting or communicating with
|
|
1075
|
+
the process
|
|
1076
|
+
|
|
1077
|
+
.. note:: The child process runs in the same process group as the parent
|
|
1078
|
+
Trio process, so a Ctrl+C will be delivered simultaneously to both
|
|
1079
|
+
parent and child. If you don't want this behavior, consult your
|
|
1080
|
+
platform's documentation for starting child processes in a different
|
|
1081
|
+
process group.
|
|
1082
|
+
|
|
1083
|
+
"""
|
|
1084
|
+
...
|
|
1085
|
+
|
|
1086
|
+
else: # Unix
|
|
1087
|
+
# pyright doesn't give any error about overloads missing docstrings as they're
|
|
1088
|
+
# overloads. But might still be a problem for other static analyzers / docstring
|
|
1089
|
+
# readers (?)
|
|
1090
|
+
|
|
1091
|
+
class UnixProcessArgs3_10(GeneralProcessArgs, total=False):
|
|
1092
|
+
"""Arguments shared between all Unix runs."""
|
|
1093
|
+
|
|
1094
|
+
preexec_fn: Callable[[], object] | None
|
|
1095
|
+
restore_signals: bool
|
|
1096
|
+
start_new_session: bool
|
|
1097
|
+
pass_fds: Sequence[int]
|
|
1098
|
+
|
|
1099
|
+
# 3.9+
|
|
1100
|
+
group: str | int | None
|
|
1101
|
+
extra_groups: Iterable[str | int] | None
|
|
1102
|
+
user: str | int | None
|
|
1103
|
+
umask: int
|
|
1104
|
+
|
|
1105
|
+
# 3.10+
|
|
1106
|
+
pipesize: int
|
|
1107
|
+
|
|
1108
|
+
class UnixProcessArgs3_11(UnixProcessArgs3_10, total=False):
|
|
1109
|
+
"""Arguments shared between all Unix runs on 3.11+."""
|
|
1110
|
+
|
|
1111
|
+
process_group: int | None
|
|
1112
|
+
|
|
1113
|
+
class UnixRunProcessMixin(TypedDict, total=False):
|
|
1114
|
+
"""Arguments unique to run_process on Unix."""
|
|
1115
|
+
|
|
1116
|
+
task_status: TaskStatus[Process]
|
|
1117
|
+
capture_stdout: bool
|
|
1118
|
+
capture_stderr: bool
|
|
1119
|
+
check: bool
|
|
1120
|
+
deliver_cancel: Callable[[Process], Awaitable[None]] | None
|
|
1121
|
+
|
|
1122
|
+
# TODO: once https://github.com/python/mypy/issues/18692 is
|
|
1123
|
+
# fixed, move the `UnixRunProcessArgs` definition down.
|
|
1124
|
+
if sys.version_info >= (3, 11):
|
|
1125
|
+
UnixProcessArgs = UnixProcessArgs3_11
|
|
1126
|
+
|
|
1127
|
+
class UnixRunProcessArgs(UnixProcessArgs3_11, UnixRunProcessMixin):
|
|
1128
|
+
"""Arguments for run_process on Unix with 3.11+"""
|
|
1129
|
+
|
|
1130
|
+
else:
|
|
1131
|
+
UnixProcessArgs = UnixProcessArgs3_10
|
|
1132
|
+
|
|
1133
|
+
class UnixRunProcessArgs(UnixProcessArgs3_10, UnixRunProcessMixin):
|
|
1134
|
+
"""Arguments for run_process on Unix with 3.10+"""
|
|
1135
|
+
|
|
1136
|
+
@overload # type: ignore[no-overload-impl]
|
|
1137
|
+
async def open_process(
|
|
1138
|
+
command: StrOrBytesPath,
|
|
1139
|
+
*,
|
|
1140
|
+
stdin: int | HasFileno | None = None,
|
|
1141
|
+
shell: Literal[True],
|
|
1142
|
+
**kwargs: Unpack[UnixProcessArgs],
|
|
1143
|
+
) -> trio.Process: ...
|
|
1144
|
+
|
|
1145
|
+
@overload
|
|
1146
|
+
async def open_process(
|
|
1147
|
+
command: Sequence[StrOrBytesPath],
|
|
1148
|
+
*,
|
|
1149
|
+
stdin: int | HasFileno | None = None,
|
|
1150
|
+
shell: bool = False,
|
|
1151
|
+
**kwargs: Unpack[UnixProcessArgs],
|
|
1152
|
+
) -> trio.Process: ...
|
|
1153
|
+
|
|
1154
|
+
@overload # type: ignore[no-overload-impl]
|
|
1155
|
+
async def run_process(
|
|
1156
|
+
command: StrOrBytesPath,
|
|
1157
|
+
*,
|
|
1158
|
+
stdin: bytes | bytearray | memoryview | int | HasFileno | None = b"",
|
|
1159
|
+
shell: Literal[True],
|
|
1160
|
+
**kwargs: Unpack[UnixRunProcessArgs],
|
|
1161
|
+
) -> subprocess.CompletedProcess[bytes]: ...
|
|
1162
|
+
|
|
1163
|
+
@overload
|
|
1164
|
+
async def run_process(
|
|
1165
|
+
command: Sequence[StrOrBytesPath],
|
|
1166
|
+
*,
|
|
1167
|
+
stdin: bytes | bytearray | memoryview | int | HasFileno | None = b"",
|
|
1168
|
+
shell: bool = False,
|
|
1169
|
+
**kwargs: Unpack[UnixRunProcessArgs],
|
|
1170
|
+
) -> subprocess.CompletedProcess[bytes]: ...
|
|
1171
|
+
|
|
1172
|
+
else:
|
|
1173
|
+
# At runtime, use the actual implementations.
|
|
1174
|
+
open_process = _open_process
|
|
1175
|
+
open_process.__name__ = open_process.__qualname__ = "open_process"
|
|
1176
|
+
|
|
1177
|
+
run_process = _run_process
|
|
1178
|
+
run_process.__name__ = run_process.__qualname__ = "run_process"
|