• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

python-useful-helpers / exec-helpers / 6879920120

15 Nov 2023 04:10PM CUT coverage: 55.86%. Remained the same
6879920120

push

github

penguinolog
Fix README.rst: drop old versions from readme

399 of 706 branches covered (0.0%)

Branch coverage included in aggregate %.

1088 of 1956 relevant lines covered (55.62%)

5.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/exec_helpers/async_api/subprocess.py
1
#    Copyright 2018 - 2023 Aleksei Stepanov aka penguinolog.
2

3
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
#    not use this file except in compliance with the License. You may obtain
5
#    a copy of the License at
6

7
#         http://www.apache.org/licenses/LICENSE-2.0
8

9
#    Unless required by applicable law or agreed to in writing, software
10
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
#    License for the specific language governing permissions and limitations
13
#    under the License.
14

15
"""Python asyncio.create_subprocess_shell wrapper.
×
16

17
.. versionadded:: 3.0.0
18
"""
19

20
from __future__ import annotations
×
21

22
import asyncio
×
23
import contextlib
×
24
import copy
×
25
import datetime
×
26
import errno
×
27
import logging
×
28
import os
×
29
import typing
×
30
import warnings
×
31

32
from exec_helpers import constants
×
33
from exec_helpers import exceptions
×
34
from exec_helpers import proc_enums
×
35
from exec_helpers import subprocess
×
36
from exec_helpers.async_api import api
×
37
from exec_helpers.async_api import exec_result
×
38

39
from .. import _log_templates
×
40
from .. import _subprocess_helpers
×
41

42
if typing.TYPE_CHECKING:
43
    import types
44
    from collections.abc import AsyncIterable
45

46
    from exec_helpers.api import CalledProcessErrorSubClassT
47
    from exec_helpers.api import CommandT
48
    from exec_helpers.api import ErrorInfoT
49
    from exec_helpers.api import ExpectedExitCodesT
50
    from exec_helpers.api import LogMaskReT
51
    from exec_helpers.api import OptionalTimeoutT
52
    from exec_helpers.exec_result import OptionalStdinT
53
    from exec_helpers.subprocess import CwdT
54
    from exec_helpers.subprocess import EnvT
55

56
__all__ = ("Subprocess", "SubprocessExecuteAsyncResult")
×
57

58

59
# noinspection PyTypeHints,PyTypeChecker
60
class SubprocessExecuteAsyncResult(subprocess.SubprocessExecuteAsyncResult):
×
61
    """Override original NamedTuple with proper typing."""
62

63
    __slots__ = ()
×
64

65
    @property
×
66
    def interface(self) -> asyncio.subprocess.Process:  # type: ignore[override]
×
67
        """Override original NamedTuple with proper typing.
68

69
        :return: control interface
70
        :rtype: asyncio.subprocess.Process
71
        """
72
        return super().interface  # type: ignore[return-value]
×
73

74
    # pylint: enable=no-member
75

76
    @property
×
77
    def stdin(self) -> asyncio.StreamWriter | None:  # type: ignore[override]
×
78
        """Override original NamedTuple with proper typing.
79

80
        :return: STDIN interface
81
        :rtype: asyncio.StreamWriter | None
82
        """
83
        warnings.warn(
×
84
            "stdin access deprecated: FIFO is often closed on execution and direct access is not expected.",
85
            DeprecationWarning,
86
            stacklevel=2,
87
        )
88
        return super().stdin  # type: ignore[return-value]
×
89

90
    @property
×
91
    def stderr(self) -> AsyncIterable[bytes] | None:  # type: ignore[override]
×
92
        """Override original NamedTuple with proper typing.
93

94
        :return: STDERR interface
95
        :rtype: AsyncIterable[bytes] | None
96
        """
97
        return super().stderr  # type: ignore[return-value]
×
98

99
    @property
×
100
    def stdout(self) -> AsyncIterable[bytes] | None:  # type: ignore[override]
×
101
        """Override original NamedTuple with proper typing.
102

103
        :return: STDOUT interface
104
        :rtype: AsyncIterable[bytes] | None
105
        """
106
        return super().stdout  # type: ignore[return-value]
×
107

108

109
class _SubprocessExecuteContext(api.ExecuteContext, typing.AsyncContextManager[SubprocessExecuteAsyncResult]):
×
110
    """Subprocess Execute context."""
111

112
    __slots__ = ("__cwd", "__env", "__process")
×
113

114
    def __init__(
×
115
        self,
116
        *,
117
        command: str,
118
        stdin: bytes | None = None,
119
        open_stdout: bool = True,
120
        open_stderr: bool = True,
121
        cwd: CwdT = None,
122
        env: EnvT = None,
123
        logger: logging.Logger,
124
        **kwargs: typing.Any,
125
    ) -> None:
126
        """Subprocess Execute context.
127

128
        :param command: Command for execution (fully formatted)
129
        :type command: str
130
        :param stdin: pass STDIN text to the process (fully formatted)
131
        :type stdin: bytes
132
        :param open_stdout: open STDOUT stream for read
133
        :type open_stdout: bool
134
        :param open_stderr: open STDERR stream for read
135
        :type open_stderr: bool
136
        :param cwd: Sets the current directory before the child is executed.
137
        :type cwd: str | bytes | pathlib.Path | None
138
        :param env: Defines the environment variables for the new process.
139
        :type env: Mapping[str | bytes, str | bytes] | None
140
        :param logger: instance logger
141
        :type logger: logging.Logger
142
        :param kwargs: additional parameters for call.
143
        :type kwargs: typing.Any
144
        """
145
        super().__init__(
×
146
            command=command,
147
            stdin=stdin,
148
            open_stdout=open_stdout,
149
            open_stderr=open_stderr,
150
            logger=logger,
151
            **kwargs,
152
        )
153
        self.__cwd = cwd
×
154
        self.__env = env
×
155
        self.__process: asyncio.subprocess.Process | None = None
×
156

157
    def __repr__(self) -> str:
158
        """Debug string.
159

160
        :return: reproduce for debug
161
        :rtype: str
162
        """
163
        return (
164
            f"<Subprocess().open_execute_context("
165
            f"command={self.command!r}, "
166
            f"stdin={self.stdin!r}, "
167
            f"open_stdout={self.open_stdout!r}, "
168
            f"open_stderr={self.open_stderr!r}, "
169
            f"cwd={self.__cwd!r}, "
170
            f"env={self.__env!r}, "
171
            f"logger={self.logger!r}) "
172
            f"at {id(self)}>"
173
        )
174

175
    async def __aenter__(self) -> SubprocessExecuteAsyncResult:
×
176
        """Context manager enter.
177

178
        :return: raw execution information
179
        :rtype: SshExecuteAsyncResult
180
        :raises OSError: stdin write failed/stdin close failed
181

182
        Command is executed only in context manager to be sure, that everything will be cleaned up properly.
183
        """
184
        started = datetime.datetime.now(tz=datetime.timezone.utc)
×
185

186
        self.__process = await asyncio.create_subprocess_shell(
×
187
            cmd=self.command,
188
            stdout=asyncio.subprocess.PIPE if self.open_stdout else asyncio.subprocess.DEVNULL,
189
            stderr=asyncio.subprocess.PIPE if self.open_stderr else asyncio.subprocess.DEVNULL,
190
            stdin=asyncio.subprocess.PIPE,
191
            cwd=self.__cwd,
192
            env=self.__env,
193
            universal_newlines=False,
194
            **_subprocess_helpers.subprocess_kw,
195
        )
196
        process = self.__process
×
197

198
        if self.stdin is not None:
×
199
            if process.stdin is None:
×
200
                self.logger.warning("STDIN pipe is not set, but STDIN data is available to send.")
×
201
            else:
202
                try:
×
203
                    process.stdin.write(self.stdin)
×
204
                    await process.stdin.drain()
×
205
                except BrokenPipeError:
×
206
                    self.logger.warning("STDIN Send failed: broken PIPE")
×
207
                except ConnectionResetError:
×
208
                    self.logger.warning("STDIN Send failed: closed PIPE")
×
209
                try:
×
210
                    process.stdin.close()
×
211
                except BrokenPipeError:
×
212
                    self.logger.warning("STDIN Send failed: broken PIPE")
×
213
                except OSError as exc:
×
214
                    if exc.errno != errno.EINVAL:
×
215
                        _subprocess_helpers.kill_proc_tree(process.pid)
×
216
                        process.kill()
×
217
                        raise
×
218

219
        # noinspection PyArgumentList
220
        return SubprocessExecuteAsyncResult(
×
221
            interface=process,
222
            stdin=None,
223
            stderr=process.stderr,
224
            stdout=process.stdout,
225
            started=started,
226
        )
227

228
    async def __aexit__(
×
229
        self,
230
        exc_type: type[BaseException] | None,
231
        exc_val: BaseException | None,
232
        exc_tb: types.TracebackType | None,
233
    ) -> None:
234
        process = self.__process
×
235
        if process is not None:  # pylint: disable=consider-using-assignment-expr
×
236
            _subprocess_helpers.kill_proc_tree(process.pid)
×
237
            with contextlib.suppress(ProcessLookupError):
×
238
                process.kill()
×
239
            self.__process = None
×
240

241

242
class Subprocess(api.ExecHelper):
×
243
    """Subprocess helper with timeouts and lock-free FIFO.
244

245
    :param log_mask_re: regex lookup rule to mask command for logger.
246
                        all MATCHED groups will be replaced by '<*masked*>'
247
    :type log_mask_re: str | re.Pattern[str] | None
248
    :param logger: logger instance to use
249
    :type logger: logging.Logger
250

251
    .. versionchanged:: 3.1.0 Not singleton anymore. Only lock is shared between all instances.
252
    .. versionchanged:: 3.2.0 Logger can be enforced.
253
    .. versionchanged:: 4.1.0 support chroot
254
    .. versionchanged:: 4.3.0 Lock is not shared anymore: allow parallel call of different instances.
255
    """
256

257
    __slots__ = ()
×
258

259
    def __init__(
×
260
        self,
261
        log_mask_re: LogMaskReT = None,
262
        *,
263
        logger: logging.Logger = logging.getLogger(__name__),  # noqa: B008
264
    ) -> None:
265
        """Subprocess helper with timeouts and lock-free FIFO."""
266
        super().__init__(logger=logger, log_mask_re=log_mask_re)
×
267

268
    async def _exec_command(  # type: ignore[override]
×
269
        self,
270
        command: str,
271
        async_result: SubprocessExecuteAsyncResult,
272
        timeout: OptionalTimeoutT,
273
        *,
274
        verbose: bool = False,
275
        log_mask_re: LogMaskReT = None,
276
        stdin: OptionalStdinT = None,
277
        log_stdout: bool = True,
278
        log_stderr: bool = True,
279
        **kwargs: typing.Any,
280
    ) -> exec_result.ExecResult:
281
        """Get exit status from channel with timeout.
282

283
        :param command: Command for execution
284
        :type command: str
285
        :param async_result: execute_async result
286
        :type async_result: SubprocessExecuteAsyncResult
287
        :param timeout: Timeout for command execution
288
        :type timeout: int | float | None
289
        :param verbose: produce verbose log record on command call
290
        :type verbose: bool
291
        :param log_mask_re: regex lookup rule to mask command for logger.
292
                            all MATCHED groups will be replaced by '<*masked*>'
293
        :type log_mask_re: str | re.Pattern[str] | None
294
        :param stdin: pass STDIN text to the process
295
        :type stdin: bytes | str | bytearray | None
296
        :param log_stdout: log STDOUT during read
297
        :type log_stdout: bool
298
        :param log_stderr: log STDERR during read
299
        :type log_stderr: bool
300
        :param kwargs: additional parameters for call.
301
        :type kwargs: typing.Any
302
        :return: Execution result
303
        :rtype: ExecResult
304
        :raises OSError: exception during process kill (and not regarding to already closed process)
305
        :raises ExecHelperNoKillError: Process not dies on SIGTERM & SIGKILL
306
        :raises ExecHelperTimeoutError: Timeout exceeded
307
        """
308

309
        async def poll_stdout() -> None:
×
310
            """Sync stdout poll."""
311
            await result.read_stdout(src=async_result.stdout, log=self.logger if log_stdout else None, verbose=verbose)
×
312

313
        async def poll_stderr() -> None:
×
314
            """Sync stderr poll."""
315
            await result.read_stderr(src=async_result.stderr, log=self.logger if log_stderr else None, verbose=verbose)
×
316

317
        # Store command with hidden data
318
        cmd_for_log: str = self._mask_command(cmd=command, log_mask_re=log_mask_re)
×
319

320
        result = exec_result.ExecResult(cmd=cmd_for_log, stdin=stdin, started=async_result.started)
×
321

322
        stdout_task: asyncio.Task[None] = asyncio.create_task(poll_stdout())
×
323
        stderr_task: asyncio.Task[None] = asyncio.create_task(poll_stderr())
×
324

325
        try:
×
326
            # Wait real timeout here
327
            exit_code: int = await asyncio.wait_for(async_result.interface.wait(), timeout=timeout)
×
328
            result.exit_code = exit_code
×
329
        except asyncio.TimeoutError as exc:
×
330
            # kill -9 for all subprocesses
331
            _subprocess_helpers.kill_proc_tree(async_result.interface.pid)
×
332
            exit_signal: int | None = await asyncio.wait_for(async_result.interface.wait(), timeout=0.001)
×
333
            if exit_signal is None:  # pylint: disable=consider-using-assignment-expr
×
334
                raise exceptions.ExecHelperNoKillError(
×
335
                    result=result,
336
                    timeout=timeout,  # type: ignore[arg-type]
337
                ) from exc
338
            result.exit_code = exit_signal
×
339
        else:
340
            return result
×
341
        finally:
342
            stdout_task.cancel()
×
343
            stderr_task.cancel()
×
344
            result.set_timestamp()
×
345

346
        wait_err_msg: str = _log_templates.CMD_WAIT_ERROR.format(result=result, timeout=timeout)
×
347
        self.logger.debug(wait_err_msg)
×
348
        raise exceptions.ExecHelperTimeoutError(result=result, timeout=timeout)  # type: ignore[arg-type]
×
349

350
    def open_execute_context(
×
351
        self,
352
        command: str,
353
        *,
354
        stdin: OptionalStdinT = None,
355
        open_stdout: bool = True,
356
        open_stderr: bool = True,
357
        chroot_path: str | None = None,
358
        cwd: CwdT = None,
359
        env: EnvT = None,
360
        env_patch: EnvT = None,
361
        **kwargs: typing.Any,
362
    ) -> _SubprocessExecuteContext:
363
        """Get execution context manager.
364

365
        :param command: Command for execution
366
        :type command: str | Iterable[str]
367
        :param stdin: pass STDIN text to the process
368
        :type stdin: bytes | str | bytearray | None
369
        :param open_stdout: open STDOUT stream for read
370
        :type open_stdout: bool
371
        :param open_stderr: open STDERR stream for read
372
        :type open_stderr: bool
373
        :param chroot_path: chroot path override
374
        :type chroot_path: str | None
375
        :param cwd: Sets the current directory before the child is executed.
376
        :type cwd: str | bytes | pathlib.Path | None
377
        :param env: Defines the environment variables for the new process.
378
        :type env: Mapping[str | bytes, str | bytes] | None
379
        :param env_patch: Defines the environment variables to ADD for the new process.
380
        :type env_patch: Mapping[str | bytes, str | bytes] | None
381
        :param kwargs: additional parameters for call.
382
        :type kwargs: typing.Any
383
        :return: Execute context
384
        :rtype: _SubprocessExecuteContext
385
        .. versionadded:: 8.0.0
386
        """
387
        if env_patch is not None:
×
388
            # make mutable copy
389
            env = dict(copy.deepcopy(os.environ) if env is None else copy.deepcopy(env))  # type: ignore[arg-type]
×
390
            env.update(env_patch)  # type: ignore[arg-type]
×
391
        return _SubprocessExecuteContext(
×
392
            command=f"{self._prepare_command(cmd=command, chroot_path=chroot_path)}\n",
393
            stdin=None if stdin is None else self._string_bytes_bytearray_as_bytes(stdin),
394
            open_stdout=open_stdout,
395
            open_stderr=open_stderr,
396
            cwd=cwd,
397
            env=env,
398
            logger=self.logger,
399
            **kwargs,
400
        )
401

402
    async def execute(
×
403
        self,
404
        command: CommandT,
405
        verbose: bool = False,
406
        timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
407
        *,
408
        log_mask_re: LogMaskReT = None,
409
        stdin: OptionalStdinT = None,
410
        open_stdout: bool = True,
411
        log_stdout: bool = True,
412
        open_stderr: bool = True,
413
        log_stderr: bool = True,
414
        chroot_path: str | None = None,
415
        cwd: CwdT = None,
416
        env: EnvT = None,
417
        env_patch: EnvT = None,
418
        **kwargs: typing.Any,
419
    ) -> exec_result.ExecResult:
420
        """Execute command and wait for return code.
421

422
        :param command: Command for execution
423
        :type command: str | Iterable[str]
424
        :param verbose: Produce log.info records for command call and output
425
        :type verbose: bool
426
        :param timeout: Timeout for command execution.
427
        :type timeout: int | float | None
428
        :param log_mask_re: regex lookup rule to mask command for logger.
429
                            all MATCHED groups will be replaced by '<*masked*>'
430
        :type log_mask_re: str | re.Pattern[str] | None
431
        :param stdin: pass STDIN text to the process
432
        :type stdin: bytes | str | bytearray | None
433
        :param open_stdout: open STDOUT stream for read
434
        :type open_stdout: bool
435
        :param log_stdout: log STDOUT during read
436
        :type log_stdout: bool
437
        :param open_stderr: open STDERR stream for read
438
        :type open_stderr: bool
439
        :param log_stderr: log STDERR during read
440
        :type log_stderr: bool
441
        :param chroot_path: chroot path override
442
        :type chroot_path: str | None
443
        :param cwd: Sets the current directory before the child is executed.
444
        :type cwd: str | bytes | pathlib.Path | None
445
        :param env: Defines the environment variables for the new process.
446
        :type env: Mapping[str | bytes, str | bytes] | None
447
        :param env_patch: Defines the environment variables to ADD for the new process.
448
        :type env_patch: Mapping[str | bytes, str | bytes] | None
449
        :param kwargs: additional parameters for call.
450
        :type kwargs: typing.Any
451
        :return: Execution result
452
        :rtype: ExecResult
453
        :raises ExecHelperTimeoutError: Timeout exceeded
454

455
        .. versionchanged:: 1.2.0 default timeout 1 hour
456
        .. versionchanged:: 2.1.0 Allow parallel calls
457
        .. versionchanged:: 7.0.0 Allow command as list of arguments. Command will be joined with components escaping.
458
        .. versionchanged:: 8.0.0 chroot path exposed.
459
        """
460
        return await super().execute(
×
461
            command=command,
462
            verbose=verbose,
463
            timeout=timeout,
464
            log_mask_re=log_mask_re,
465
            stdin=stdin,
466
            open_stdout=open_stdout,
467
            log_stdout=log_stdout,
468
            open_stderr=open_stderr,
469
            log_stderr=log_stderr,
470
            chroot_path=chroot_path,
471
            cwd=cwd,
472
            env=env,
473
            env_patch=env_patch,
474
            **kwargs,
475
        )
476

477
    async def __call__(
×
478
        self,
479
        command: CommandT,
480
        verbose: bool = False,
481
        timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
482
        *,
483
        log_mask_re: LogMaskReT = None,
484
        stdin: OptionalStdinT = None,
485
        open_stdout: bool = True,
486
        log_stdout: bool = True,
487
        open_stderr: bool = True,
488
        log_stderr: bool = True,
489
        chroot_path: str | None = None,
490
        cwd: CwdT = None,
491
        env: EnvT = None,
492
        env_patch: EnvT = None,
493
        **kwargs: typing.Any,
494
    ) -> exec_result.ExecResult:
495
        """Execute command and wait for return code.
496

497
        :param command: Command for execution
498
        :type command: str | Iterable[str]
499
        :param verbose: Produce log.info records for command call and output
500
        :type verbose: bool
501
        :param timeout: Timeout for command execution.
502
        :type timeout: int | float | None
503
        :param log_mask_re: regex lookup rule to mask command for logger.
504
                            all MATCHED groups will be replaced by '<*masked*>'
505
        :type log_mask_re: str | re.Pattern[str] | None
506
        :param stdin: pass STDIN text to the process
507
        :type stdin: bytes | str | bytearray | None
508
        :param open_stdout: open STDOUT stream for read
509
        :type open_stdout: bool
510
        :param log_stdout: log STDOUT during read
511
        :type log_stdout: bool
512
        :param open_stderr: open STDERR stream for read
513
        :type open_stderr: bool
514
        :param log_stderr: log STDERR during read
515
        :type log_stderr: bool
516
        :param chroot_path: chroot path override
517
        :type chroot_path: str | None
518
        :param cwd: Sets the current directory before the child is executed.
519
        :type cwd: str | bytes | pathlib.Path | None
520
        :param env: Defines the environment variables for the new process.
521
        :type env: Mapping[str | bytes, str | bytes] | None
522
        :param env_patch: Defines the environment variables to ADD for the new process.
523
        :type env_patch: Mapping[str | bytes, str | bytes] | None
524
        :param kwargs: additional parameters for call.
525
        :type kwargs: typing.Any
526
        :return: Execution result
527
        :rtype: ExecResult
528
        :raises ExecHelperTimeoutError: Timeout exceeded
529

530
        .. versionchanged:: 1.2.0 default timeout 1 hour
531
        .. versionchanged:: 2.1.0 Allow parallel calls
532
        """
533
        return await super().__call__(
×
534
            command=command,
535
            verbose=verbose,
536
            timeout=timeout,
537
            log_mask_re=log_mask_re,
538
            stdin=stdin,
539
            open_stdout=open_stdout,
540
            log_stdout=log_stdout,
541
            open_stderr=open_stderr,
542
            log_stderr=log_stderr,
543
            chroot_path=chroot_path,
544
            cwd=cwd,
545
            env=env,
546
            env_patch=env_patch,
547
            **kwargs,
548
        )
549

550
    async def check_call(
×
551
        self,
552
        command: CommandT,
553
        verbose: bool = False,
554
        timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
555
        error_info: ErrorInfoT = None,
556
        expected: ExpectedExitCodesT = (proc_enums.EXPECTED,),
557
        raise_on_err: bool = True,
558
        *,
559
        log_mask_re: LogMaskReT = None,
560
        stdin: OptionalStdinT = None,
561
        open_stdout: bool = True,
562
        log_stdout: bool = True,
563
        open_stderr: bool = True,
564
        log_stderr: bool = True,
565
        cwd: CwdT = None,
566
        env: EnvT = None,
567
        env_patch: EnvT = None,
568
        exception_class: CalledProcessErrorSubClassT = exceptions.CalledProcessError,
569
        **kwargs: typing.Any,
570
    ) -> exec_result.ExecResult:
571
        """Execute command and check for return code.
572

573
        :param command: Command for execution
574
        :type command: str | Iterable[str]
575
        :param verbose: Produce log.info records for command call and output
576
        :type verbose: bool
577
        :param timeout: Timeout for command execution.
578
        :type timeout: int | float | None
579
        :param error_info: Text for error details, if fail happens
580
        :type error_info: str | None
581
        :param expected: expected return codes (0 by default)
582
        :type expected: Iterable[int | proc_enums.ExitCodes]
583
        :param raise_on_err: Raise exception on unexpected return code
584
        :type raise_on_err: bool
585
        :param log_mask_re: regex lookup rule to mask command for logger.
586
                            all MATCHED groups will be replaced by '<*masked*>'
587
        :type log_mask_re: str | re.Pattern[str] | None
588
        :param stdin: pass STDIN text to the process
589
        :type stdin: bytes | str | bytearray | None
590
        :param open_stdout: open STDOUT stream for read
591
        :type open_stdout: bool
592
        :param log_stdout: log STDOUT during read
593
        :type log_stdout: bool
594
        :param open_stderr: open STDERR stream for read
595
        :type open_stderr: bool
596
        :param log_stderr: log STDERR during read
597
        :type log_stderr: bool
598
        :param cwd: Sets the current directory before the child is executed.
599
        :type cwd: str | bytes | pathlib.Path | None
600
        :param env: Defines the environment variables for the new process.
601
        :type env: Mapping[str | bytes, str | bytes] | None
602
        :param env_patch: Defines the environment variables to ADD for the new process.
603
        :type env_patch: Mapping[str | bytes, str | bytes] | None
604
        :param exception_class: Exception class for errors. Subclass of CalledProcessError is mandatory.
605
        :type exception_class: type[exceptions.CalledProcessError]
606
        :param kwargs: additional parameters for call.
607
        :type kwargs: typing.Any
608
        :return: Execution result
609
        :rtype: ExecResult
610
        :raises ExecHelperTimeoutError: Timeout exceeded
611
        :raises CalledProcessError: Unexpected exit code
612

613
        .. versionchanged:: 1.2.0 default timeout 1 hour
614
        .. versionchanged:: 3.2.0 Exception class can be substituted
615
        .. versionchanged:: 3.4.0 Expected is not optional, defaults os dependent
616
        """
617
        return await super().check_call(
×
618
            command=command,
619
            verbose=verbose,
620
            timeout=timeout,
621
            error_info=error_info,
622
            expected=expected,
623
            raise_on_err=raise_on_err,
624
            log_mask_re=log_mask_re,
625
            stdin=stdin,
626
            open_stdout=open_stdout,
627
            log_stdout=log_stdout,
628
            open_stderr=open_stderr,
629
            log_stderr=log_stderr,
630
            cwd=cwd,
631
            env=env,
632
            env_patch=env_patch,
633
            exception_class=exception_class,
634
            **kwargs,
635
        )
636

637
    async def check_stderr(
×
638
        self,
639
        command: CommandT,
640
        verbose: bool = False,
641
        timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
642
        error_info: ErrorInfoT = None,
643
        raise_on_err: bool = True,
644
        *,
645
        expected: ExpectedExitCodesT = (proc_enums.EXPECTED,),
646
        log_mask_re: LogMaskReT = None,
647
        stdin: OptionalStdinT = None,
648
        open_stdout: bool = True,
649
        log_stdout: bool = True,
650
        open_stderr: bool = True,
651
        log_stderr: bool = True,
652
        cwd: CwdT = None,
653
        env: EnvT = None,
654
        env_patch: EnvT = None,
655
        exception_class: CalledProcessErrorSubClassT = exceptions.CalledProcessError,
656
        **kwargs: typing.Any,
657
    ) -> exec_result.ExecResult:
658
        """Execute command expecting return code 0 and empty STDERR.
659

660
        :param command: Command for execution
661
        :type command: str | Iterable[str]
662
        :param verbose: Produce log.info records for command call and output
663
        :type verbose: bool
664
        :param timeout: Timeout for command execution.
665
        :type timeout: int | float | None
666
        :param error_info: Text for error details, if fail happens
667
        :type error_info: str | None
668
        :param raise_on_err: Raise exception on unexpected return code
669
        :type raise_on_err: bool
670
        :param expected: expected return codes (0 by default)
671
        :type expected: Iterable[int | proc_enums.ExitCodes]
672
        :param log_mask_re: regex lookup rule to mask command for logger.
673
                            all MATCHED groups will be replaced by '<*masked*>'
674
        :type log_mask_re: str | re.Pattern[str] | None
675
        :param stdin: pass STDIN text to the process
676
        :type stdin: bytes | str | bytearray | None
677
        :param open_stdout: open STDOUT stream for read
678
        :type open_stdout: bool
679
        :param log_stdout: log STDOUT during read
680
        :type log_stdout: bool
681
        :param open_stderr: open STDERR stream for read
682
        :type open_stderr: bool
683
        :param log_stderr: log STDERR during read
684
        :type log_stderr: bool
685
        :param cwd: Sets the current directory before the child is executed.
686
        :type cwd: str | bytes | pathlib.Path | None
687
        :param env: Defines the environment variables for the new process.
688
        :type env: Mapping[str | bytes, str | bytes] | None
689
        :param env_patch: Defines the environment variables to ADD for the new process.
690
        :type env_patch: Mapping[str | bytes, str | bytes] | None
691
        :param exception_class: Exception class for errors. Subclass of CalledProcessError is mandatory.
692
        :type exception_class: type[exceptions.CalledProcessError]
693
        :param kwargs: additional parameters for call.
694
        :type kwargs: typing.Any
695
        :return: Execution result
696
        :rtype: ExecResult
697
        :raises ExecHelperTimeoutError: Timeout exceeded
698
        :raises CalledProcessError: Unexpected exit code or stderr presents
699

700
        .. versionchanged:: 1.2.0 default timeout 1 hour
701
        .. versionchanged:: 3.2.0 Exception class can be substituted
702
        .. versionchanged:: 3.4.0 Expected is not optional, defaults os dependent
703
        """
704
        return await super().check_stderr(
×
705
            command=command,
706
            verbose=verbose,
707
            timeout=timeout,
708
            error_info=error_info,
709
            raise_on_err=raise_on_err,
710
            expected=expected,
711
            log_mask_re=log_mask_re,
712
            stdin=stdin,
713
            open_stdout=open_stdout,
714
            log_stdout=log_stdout,
715
            open_stderr=open_stderr,
716
            log_stderr=log_stderr,
717
            cwd=cwd,
718
            env=env,
719
            env_patch=env_patch,
720
            exception_class=exception_class,
721
            **kwargs,
722
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc