provide-foundation 0.0.0.dev0__py3-none-any.whl
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.
- provide/__init__.py +15 -0
- provide/foundation/__init__.py +155 -0
- provide/foundation/_version.py +58 -0
- provide/foundation/cli/__init__.py +67 -0
- provide/foundation/cli/commands/__init__.py +3 -0
- provide/foundation/cli/commands/deps.py +71 -0
- provide/foundation/cli/commands/logs/__init__.py +63 -0
- provide/foundation/cli/commands/logs/generate.py +357 -0
- provide/foundation/cli/commands/logs/generate_old.py +569 -0
- provide/foundation/cli/commands/logs/query.py +174 -0
- provide/foundation/cli/commands/logs/send.py +166 -0
- provide/foundation/cli/commands/logs/tail.py +112 -0
- provide/foundation/cli/decorators.py +262 -0
- provide/foundation/cli/main.py +65 -0
- provide/foundation/cli/testing.py +220 -0
- provide/foundation/cli/utils.py +210 -0
- provide/foundation/config/__init__.py +106 -0
- provide/foundation/config/base.py +295 -0
- provide/foundation/config/env.py +369 -0
- provide/foundation/config/loader.py +311 -0
- provide/foundation/config/manager.py +387 -0
- provide/foundation/config/schema.py +284 -0
- provide/foundation/config/sync.py +281 -0
- provide/foundation/config/types.py +78 -0
- provide/foundation/config/validators.py +80 -0
- provide/foundation/console/__init__.py +29 -0
- provide/foundation/console/input.py +364 -0
- provide/foundation/console/output.py +178 -0
- provide/foundation/context/__init__.py +12 -0
- provide/foundation/context/core.py +356 -0
- provide/foundation/core.py +20 -0
- provide/foundation/crypto/__init__.py +182 -0
- provide/foundation/crypto/algorithms.py +111 -0
- provide/foundation/crypto/certificates.py +896 -0
- provide/foundation/crypto/checksums.py +301 -0
- provide/foundation/crypto/constants.py +57 -0
- provide/foundation/crypto/hashing.py +265 -0
- provide/foundation/crypto/keys.py +188 -0
- provide/foundation/crypto/signatures.py +144 -0
- provide/foundation/crypto/utils.py +164 -0
- provide/foundation/errors/__init__.py +96 -0
- provide/foundation/errors/auth.py +73 -0
- provide/foundation/errors/base.py +81 -0
- provide/foundation/errors/config.py +103 -0
- provide/foundation/errors/context.py +299 -0
- provide/foundation/errors/decorators.py +484 -0
- provide/foundation/errors/handlers.py +360 -0
- provide/foundation/errors/integration.py +105 -0
- provide/foundation/errors/platform.py +37 -0
- provide/foundation/errors/process.py +140 -0
- provide/foundation/errors/resources.py +133 -0
- provide/foundation/errors/runtime.py +160 -0
- provide/foundation/errors/safe_decorators.py +133 -0
- provide/foundation/errors/types.py +276 -0
- provide/foundation/file/__init__.py +79 -0
- provide/foundation/file/atomic.py +157 -0
- provide/foundation/file/directory.py +134 -0
- provide/foundation/file/formats.py +236 -0
- provide/foundation/file/lock.py +175 -0
- provide/foundation/file/safe.py +179 -0
- provide/foundation/file/utils.py +170 -0
- provide/foundation/hub/__init__.py +88 -0
- provide/foundation/hub/click_builder.py +310 -0
- provide/foundation/hub/commands.py +42 -0
- provide/foundation/hub/components.py +640 -0
- provide/foundation/hub/decorators.py +244 -0
- provide/foundation/hub/info.py +32 -0
- provide/foundation/hub/manager.py +446 -0
- provide/foundation/hub/registry.py +279 -0
- provide/foundation/hub/type_mapping.py +54 -0
- provide/foundation/hub/types.py +28 -0
- provide/foundation/logger/__init__.py +41 -0
- provide/foundation/logger/base.py +22 -0
- provide/foundation/logger/config/__init__.py +16 -0
- provide/foundation/logger/config/base.py +40 -0
- provide/foundation/logger/config/logging.py +394 -0
- provide/foundation/logger/config/telemetry.py +188 -0
- provide/foundation/logger/core.py +239 -0
- provide/foundation/logger/custom_processors.py +172 -0
- provide/foundation/logger/emoji/__init__.py +44 -0
- provide/foundation/logger/emoji/matrix.py +209 -0
- provide/foundation/logger/emoji/sets.py +458 -0
- provide/foundation/logger/emoji/types.py +56 -0
- provide/foundation/logger/factories.py +56 -0
- provide/foundation/logger/processors/__init__.py +13 -0
- provide/foundation/logger/processors/main.py +254 -0
- provide/foundation/logger/processors/trace.py +113 -0
- provide/foundation/logger/ratelimit/__init__.py +31 -0
- provide/foundation/logger/ratelimit/limiters.py +294 -0
- provide/foundation/logger/ratelimit/processor.py +203 -0
- provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
- provide/foundation/logger/setup/__init__.py +29 -0
- provide/foundation/logger/setup/coordinator.py +138 -0
- provide/foundation/logger/setup/emoji_resolver.py +64 -0
- provide/foundation/logger/setup/processors.py +85 -0
- provide/foundation/logger/setup/testing.py +39 -0
- provide/foundation/logger/trace.py +38 -0
- provide/foundation/metrics/__init__.py +119 -0
- provide/foundation/metrics/otel.py +122 -0
- provide/foundation/metrics/simple.py +165 -0
- provide/foundation/observability/__init__.py +53 -0
- provide/foundation/observability/openobserve/__init__.py +79 -0
- provide/foundation/observability/openobserve/auth.py +72 -0
- provide/foundation/observability/openobserve/client.py +307 -0
- provide/foundation/observability/openobserve/commands.py +357 -0
- provide/foundation/observability/openobserve/exceptions.py +41 -0
- provide/foundation/observability/openobserve/formatters.py +298 -0
- provide/foundation/observability/openobserve/models.py +134 -0
- provide/foundation/observability/openobserve/otlp.py +320 -0
- provide/foundation/observability/openobserve/search.py +222 -0
- provide/foundation/observability/openobserve/streaming.py +235 -0
- provide/foundation/platform/__init__.py +44 -0
- provide/foundation/platform/detection.py +193 -0
- provide/foundation/platform/info.py +157 -0
- provide/foundation/process/__init__.py +39 -0
- provide/foundation/process/async_runner.py +373 -0
- provide/foundation/process/lifecycle.py +406 -0
- provide/foundation/process/runner.py +390 -0
- provide/foundation/setup/__init__.py +101 -0
- provide/foundation/streams/__init__.py +44 -0
- provide/foundation/streams/console.py +57 -0
- provide/foundation/streams/core.py +65 -0
- provide/foundation/streams/file.py +104 -0
- provide/foundation/testing/__init__.py +166 -0
- provide/foundation/testing/cli.py +227 -0
- provide/foundation/testing/crypto.py +163 -0
- provide/foundation/testing/fixtures.py +49 -0
- provide/foundation/testing/hub.py +23 -0
- provide/foundation/testing/logger.py +106 -0
- provide/foundation/testing/streams.py +54 -0
- provide/foundation/tracer/__init__.py +49 -0
- provide/foundation/tracer/context.py +115 -0
- provide/foundation/tracer/otel.py +135 -0
- provide/foundation/tracer/spans.py +174 -0
- provide/foundation/types.py +32 -0
- provide/foundation/utils/__init__.py +97 -0
- provide/foundation/utils/deps.py +195 -0
- provide/foundation/utils/env.py +491 -0
- provide/foundation/utils/formatting.py +483 -0
- provide/foundation/utils/parsing.py +235 -0
- provide/foundation/utils/rate_limiting.py +112 -0
- provide/foundation/utils/streams.py +67 -0
- provide/foundation/utils/timing.py +93 -0
- provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
- provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
- provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
- provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
- provide_foundation-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
"""Async subprocess execution utilities."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import builtins
|
5
|
+
from collections.abc import AsyncIterator, Mapping
|
6
|
+
import os
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
from provide.foundation.logger import get_logger
|
11
|
+
from provide.foundation.process.runner import (
|
12
|
+
CompletedProcess,
|
13
|
+
ProcessError,
|
14
|
+
TimeoutError,
|
15
|
+
)
|
16
|
+
|
17
|
+
plog = get_logger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
def _filter_subprocess_kwargs(kwargs: dict) -> dict:
|
21
|
+
"""Filter kwargs to only include valid subprocess parameters."""
|
22
|
+
valid_subprocess_kwargs = {
|
23
|
+
"stdin",
|
24
|
+
"stdout",
|
25
|
+
"stderr",
|
26
|
+
"shell",
|
27
|
+
"cwd",
|
28
|
+
"env",
|
29
|
+
"universal_newlines",
|
30
|
+
"startupinfo",
|
31
|
+
"creationflags",
|
32
|
+
"restore_signals",
|
33
|
+
"start_new_session",
|
34
|
+
"pass_fds",
|
35
|
+
"encoding",
|
36
|
+
"errors",
|
37
|
+
"text",
|
38
|
+
"executable",
|
39
|
+
"preexec_fn",
|
40
|
+
"close_fds",
|
41
|
+
"group",
|
42
|
+
"extra_groups",
|
43
|
+
"user",
|
44
|
+
"umask",
|
45
|
+
}
|
46
|
+
return {k: v for k, v in kwargs.items() if k in valid_subprocess_kwargs}
|
47
|
+
|
48
|
+
|
49
|
+
async def async_run_command(
|
50
|
+
cmd: list[str] | str,
|
51
|
+
cwd: str | Path | None = None,
|
52
|
+
env: Mapping[str, str] | None = None,
|
53
|
+
capture_output: bool = True,
|
54
|
+
check: bool = True,
|
55
|
+
timeout: float | None = None,
|
56
|
+
input: bytes | None = None,
|
57
|
+
shell: bool = False,
|
58
|
+
**kwargs: Any,
|
59
|
+
) -> CompletedProcess:
|
60
|
+
"""
|
61
|
+
Run a subprocess command asynchronously.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
cmd: Command and arguments as a list
|
65
|
+
cwd: Working directory for the command
|
66
|
+
env: Environment variables (if None, uses current environment)
|
67
|
+
capture_output: Whether to capture stdout/stderr
|
68
|
+
check: Whether to raise exception on non-zero exit
|
69
|
+
timeout: Command timeout in seconds
|
70
|
+
input: Input to send to the process
|
71
|
+
**kwargs: Additional arguments
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
CompletedProcess with results
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
ProcessError: If command fails and check=True
|
78
|
+
TimeoutError: If timeout is exceeded
|
79
|
+
"""
|
80
|
+
cmd_str = " ".join(cmd) if isinstance(cmd, list) else str(cmd)
|
81
|
+
plog.info(
|
82
|
+
"🚀 Running async command", command=cmd_str, cwd=str(cwd) if cwd else None
|
83
|
+
)
|
84
|
+
|
85
|
+
# Prepare environment, disabling foundation telemetry by default
|
86
|
+
run_env = os.environ.copy()
|
87
|
+
if env is not None:
|
88
|
+
run_env.update(env)
|
89
|
+
run_env.setdefault("PROVIDE_TELEMETRY_DISABLED", "true")
|
90
|
+
|
91
|
+
# Convert Path to string
|
92
|
+
if isinstance(cwd, Path):
|
93
|
+
cwd = str(cwd)
|
94
|
+
|
95
|
+
try:
|
96
|
+
# Create subprocess
|
97
|
+
if shell:
|
98
|
+
# For shell commands, use create_subprocess_shell with string command
|
99
|
+
process = await asyncio.create_subprocess_shell(
|
100
|
+
cmd_str,
|
101
|
+
cwd=cwd,
|
102
|
+
env=run_env,
|
103
|
+
stdout=asyncio.subprocess.PIPE if capture_output else None,
|
104
|
+
stderr=asyncio.subprocess.PIPE if capture_output else None,
|
105
|
+
stdin=asyncio.subprocess.PIPE if input else None,
|
106
|
+
**_filter_subprocess_kwargs(kwargs),
|
107
|
+
)
|
108
|
+
else:
|
109
|
+
# For non-shell commands, use create_subprocess_exec with unpacked args
|
110
|
+
process = await asyncio.create_subprocess_exec(
|
111
|
+
*(cmd if isinstance(cmd, list) else [cmd]),
|
112
|
+
cwd=cwd,
|
113
|
+
env=run_env,
|
114
|
+
stdout=asyncio.subprocess.PIPE if capture_output else None,
|
115
|
+
stderr=asyncio.subprocess.PIPE if capture_output else None,
|
116
|
+
stdin=asyncio.subprocess.PIPE if input else None,
|
117
|
+
**_filter_subprocess_kwargs(kwargs),
|
118
|
+
)
|
119
|
+
|
120
|
+
# Communicate with process
|
121
|
+
if timeout:
|
122
|
+
try:
|
123
|
+
stdout, stderr = await asyncio.wait_for(
|
124
|
+
process.communicate(input=input),
|
125
|
+
timeout=timeout,
|
126
|
+
)
|
127
|
+
except builtins.TimeoutError:
|
128
|
+
process.kill()
|
129
|
+
await process.wait()
|
130
|
+
plog.error(
|
131
|
+
"⏱️ Async command timed out", command=cmd_str, timeout=timeout
|
132
|
+
)
|
133
|
+
raise TimeoutError(
|
134
|
+
f"Command timed out after {timeout}s: {cmd_str}",
|
135
|
+
code="PROCESS_ASYNC_TIMEOUT",
|
136
|
+
command=cmd_str,
|
137
|
+
timeout=timeout,
|
138
|
+
)
|
139
|
+
else:
|
140
|
+
stdout, stderr = await process.communicate(input=input)
|
141
|
+
|
142
|
+
# Decode output
|
143
|
+
stdout_str = stdout.decode(errors="replace") if stdout else ""
|
144
|
+
stderr_str = stderr.decode(errors="replace") if stderr else ""
|
145
|
+
|
146
|
+
completed = CompletedProcess(
|
147
|
+
args=cmd,
|
148
|
+
returncode=process.returncode or 0,
|
149
|
+
stdout=stdout_str,
|
150
|
+
stderr=stderr_str,
|
151
|
+
cwd=cwd,
|
152
|
+
env=dict(run_env) if env else None,
|
153
|
+
)
|
154
|
+
|
155
|
+
if check and process.returncode != 0:
|
156
|
+
plog.error(
|
157
|
+
"❌ Async command failed",
|
158
|
+
command=cmd_str,
|
159
|
+
returncode=process.returncode,
|
160
|
+
stderr=stderr_str if capture_output else None,
|
161
|
+
)
|
162
|
+
raise ProcessError(
|
163
|
+
f"Command failed with exit code {process.returncode}: {cmd_str}",
|
164
|
+
code="PROCESS_ASYNC_FAILED",
|
165
|
+
command=cmd_str,
|
166
|
+
returncode=process.returncode,
|
167
|
+
stdout=stdout_str if capture_output else None,
|
168
|
+
stderr=stderr_str if capture_output else None,
|
169
|
+
)
|
170
|
+
|
171
|
+
plog.debug(
|
172
|
+
"✅ Async command completed",
|
173
|
+
command=cmd_str,
|
174
|
+
returncode=process.returncode,
|
175
|
+
)
|
176
|
+
|
177
|
+
return completed
|
178
|
+
|
179
|
+
except Exception as e:
|
180
|
+
if isinstance(e, ProcessError | TimeoutError):
|
181
|
+
raise
|
182
|
+
|
183
|
+
plog.error(
|
184
|
+
"💥 Async command execution failed",
|
185
|
+
command=cmd_str,
|
186
|
+
error=str(e),
|
187
|
+
)
|
188
|
+
raise ProcessError(
|
189
|
+
f"Failed to execute async command: {cmd_str}",
|
190
|
+
code="PROCESS_ASYNC_EXECUTION_FAILED",
|
191
|
+
command=cmd_str,
|
192
|
+
error=str(e),
|
193
|
+
) from e
|
194
|
+
|
195
|
+
|
196
|
+
async def async_stream_command(
|
197
|
+
cmd: list[str],
|
198
|
+
cwd: str | Path | None = None,
|
199
|
+
env: Mapping[str, str] | None = None,
|
200
|
+
timeout: float | None = None,
|
201
|
+
stream_stderr: bool = False,
|
202
|
+
**kwargs: Any,
|
203
|
+
) -> AsyncIterator[str]:
|
204
|
+
"""
|
205
|
+
Stream command output line by line asynchronously.
|
206
|
+
|
207
|
+
Args:
|
208
|
+
cmd: Command and arguments as a list
|
209
|
+
cwd: Working directory for the command
|
210
|
+
env: Environment variables
|
211
|
+
timeout: Command timeout in seconds
|
212
|
+
**kwargs: Additional arguments
|
213
|
+
|
214
|
+
Yields:
|
215
|
+
Lines of output from the command
|
216
|
+
|
217
|
+
Raises:
|
218
|
+
ProcessError: If command fails
|
219
|
+
TimeoutError: If timeout is exceeded
|
220
|
+
"""
|
221
|
+
cmd_str = " ".join(cmd) if isinstance(cmd, list) else str(cmd)
|
222
|
+
plog.info(
|
223
|
+
"🌊 Streaming async command", command=cmd_str, cwd=str(cwd) if cwd else None
|
224
|
+
)
|
225
|
+
|
226
|
+
# Prepare environment, disabling foundation telemetry by default
|
227
|
+
run_env = os.environ.copy()
|
228
|
+
if env is not None:
|
229
|
+
run_env.update(env)
|
230
|
+
run_env.setdefault("PROVIDE_TELEMETRY_DISABLED", "true")
|
231
|
+
|
232
|
+
# Convert Path to string
|
233
|
+
if isinstance(cwd, Path):
|
234
|
+
cwd = str(cwd)
|
235
|
+
|
236
|
+
try:
|
237
|
+
# Create subprocess
|
238
|
+
# Merge stderr to stdout for streaming, as we always want to see errors
|
239
|
+
stderr_handling = (
|
240
|
+
asyncio.subprocess.STDOUT if stream_stderr else asyncio.subprocess.PIPE
|
241
|
+
)
|
242
|
+
process = await asyncio.create_subprocess_exec(
|
243
|
+
*(cmd if isinstance(cmd, list) else cmd.split()),
|
244
|
+
cwd=cwd,
|
245
|
+
env=run_env,
|
246
|
+
stdout=asyncio.subprocess.PIPE,
|
247
|
+
stderr=stderr_handling,
|
248
|
+
**_filter_subprocess_kwargs(kwargs),
|
249
|
+
)
|
250
|
+
|
251
|
+
# Stream output with optional timeout
|
252
|
+
if timeout:
|
253
|
+
# For timeout, we need to handle it differently
|
254
|
+
# Create a task to read lines with timeout
|
255
|
+
async def read_with_timeout():
|
256
|
+
lines = []
|
257
|
+
if process.stdout:
|
258
|
+
try:
|
259
|
+
# Use wait_for on each readline operation
|
260
|
+
remaining_timeout = timeout
|
261
|
+
start_time = asyncio.get_event_loop().time()
|
262
|
+
|
263
|
+
while True:
|
264
|
+
elapsed = asyncio.get_event_loop().time() - start_time
|
265
|
+
remaining_timeout = timeout - elapsed
|
266
|
+
|
267
|
+
if remaining_timeout <= 0:
|
268
|
+
raise builtins.TimeoutError()
|
269
|
+
|
270
|
+
# Wait for a line with remaining timeout
|
271
|
+
line = await asyncio.wait_for(
|
272
|
+
process.stdout.readline(), timeout=remaining_timeout
|
273
|
+
)
|
274
|
+
|
275
|
+
if not line:
|
276
|
+
break # EOF
|
277
|
+
|
278
|
+
lines.append(line.decode(errors="replace").rstrip())
|
279
|
+
except builtins.TimeoutError:
|
280
|
+
process.kill()
|
281
|
+
await process.wait()
|
282
|
+
plog.error(
|
283
|
+
"⏱️ Async stream timed out", command=cmd_str, timeout=timeout
|
284
|
+
)
|
285
|
+
raise TimeoutError(
|
286
|
+
f"Command timed out after {timeout}s: {cmd_str}",
|
287
|
+
code="PROCESS_ASYNC_STREAM_TIMEOUT",
|
288
|
+
command=cmd_str,
|
289
|
+
timeout=timeout,
|
290
|
+
)
|
291
|
+
|
292
|
+
# Wait for process to complete
|
293
|
+
await process.wait()
|
294
|
+
|
295
|
+
if process.returncode != 0:
|
296
|
+
raise ProcessError(
|
297
|
+
f"Command failed with exit code {process.returncode}: {cmd_str}",
|
298
|
+
code="PROCESS_ASYNC_STREAM_FAILED",
|
299
|
+
command=cmd_str,
|
300
|
+
returncode=process.returncode,
|
301
|
+
)
|
302
|
+
|
303
|
+
return lines
|
304
|
+
|
305
|
+
# Yield lines as they were read
|
306
|
+
lines = await read_with_timeout()
|
307
|
+
for line in lines:
|
308
|
+
yield line
|
309
|
+
else:
|
310
|
+
# No timeout - stream normally
|
311
|
+
if process.stdout:
|
312
|
+
async for line in process.stdout:
|
313
|
+
yield line.decode(errors="replace").rstrip()
|
314
|
+
|
315
|
+
# Wait for process to complete
|
316
|
+
await process.wait()
|
317
|
+
|
318
|
+
if process.returncode != 0:
|
319
|
+
raise ProcessError(
|
320
|
+
f"Command failed with exit code {process.returncode}: {cmd_str}",
|
321
|
+
code="PROCESS_ASYNC_STREAM_FAILED",
|
322
|
+
command=cmd_str,
|
323
|
+
returncode=process.returncode,
|
324
|
+
)
|
325
|
+
|
326
|
+
plog.debug("✅ Async stream completed", command=cmd_str)
|
327
|
+
|
328
|
+
except Exception as e:
|
329
|
+
if isinstance(e, ProcessError | TimeoutError):
|
330
|
+
raise
|
331
|
+
|
332
|
+
plog.error("💥 Async stream failed", command=cmd_str, error=str(e))
|
333
|
+
raise ProcessError(
|
334
|
+
f"Failed to stream async command: {cmd_str}",
|
335
|
+
code="PROCESS_ASYNC_STREAM_ERROR",
|
336
|
+
command=cmd_str,
|
337
|
+
error=str(e),
|
338
|
+
) from e
|
339
|
+
|
340
|
+
|
341
|
+
async def async_run_shell(
|
342
|
+
cmd: str,
|
343
|
+
cwd: str | Path | None = None,
|
344
|
+
env: Mapping[str, str] | None = None,
|
345
|
+
capture_output: bool = True,
|
346
|
+
check: bool = True,
|
347
|
+
timeout: float | None = None,
|
348
|
+
**kwargs: Any,
|
349
|
+
) -> CompletedProcess:
|
350
|
+
"""Run a shell command asynchronously.
|
351
|
+
|
352
|
+
Args:
|
353
|
+
cmd: Shell command string
|
354
|
+
cwd: Working directory
|
355
|
+
env: Environment variables
|
356
|
+
capture_output: Whether to capture output
|
357
|
+
check: Whether to raise on non-zero exit
|
358
|
+
timeout: Command timeout
|
359
|
+
**kwargs: Additional subprocess arguments
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
CompletedProcess with results
|
363
|
+
"""
|
364
|
+
return await async_run_command(
|
365
|
+
cmd,
|
366
|
+
cwd=cwd,
|
367
|
+
env=env,
|
368
|
+
capture_output=capture_output,
|
369
|
+
check=check,
|
370
|
+
timeout=timeout,
|
371
|
+
shell=True,
|
372
|
+
**kwargs,
|
373
|
+
)
|