Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/anyio/_core/_subprocesses.py: 29%

38 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:38 +0000

1from __future__ import annotations 

2 

3from collections.abc import AsyncIterable, Mapping, Sequence 

4from io import BytesIO 

5from os import PathLike 

6from subprocess import DEVNULL, PIPE, CalledProcessError, CompletedProcess 

7from typing import IO, Any, cast 

8 

9from ..abc import Process 

10from ._eventloop import get_async_backend 

11from ._tasks import create_task_group 

12 

13 

14async def run_process( 

15 command: str | bytes | Sequence[str | bytes], 

16 *, 

17 input: bytes | None = None, 

18 stdout: int | IO[Any] | None = PIPE, 

19 stderr: int | IO[Any] | None = PIPE, 

20 check: bool = True, 

21 cwd: str | bytes | PathLike[str] | None = None, 

22 env: Mapping[str, str] | None = None, 

23 start_new_session: bool = False, 

24) -> CompletedProcess[bytes]: 

25 """ 

26 Run an external command in a subprocess and wait until it completes. 

27 

28 .. seealso:: :func:`subprocess.run` 

29 

30 :param command: either a string to pass to the shell, or an iterable of strings 

31 containing the executable name or path and its arguments 

32 :param input: bytes passed to the standard input of the subprocess 

33 :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, 

34 a file-like object, or `None` 

35 :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, 

36 :data:`subprocess.STDOUT`, a file-like object, or `None` 

37 :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the 

38 process terminates with a return code other than 0 

39 :param cwd: If not ``None``, change the working directory to this before running the 

40 command 

41 :param env: if not ``None``, this mapping replaces the inherited environment 

42 variables from the parent process 

43 :param start_new_session: if ``true`` the setsid() system call will be made in the 

44 child process prior to the execution of the subprocess. (POSIX only) 

45 :return: an object representing the completed process 

46 :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process 

47 exits with a nonzero return code 

48 

49 """ 

50 

51 async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None: 

52 buffer = BytesIO() 

53 async for chunk in stream: 

54 buffer.write(chunk) 

55 

56 stream_contents[index] = buffer.getvalue() 

57 

58 async with await open_process( 

59 command, 

60 stdin=PIPE if input else DEVNULL, 

61 stdout=stdout, 

62 stderr=stderr, 

63 cwd=cwd, 

64 env=env, 

65 start_new_session=start_new_session, 

66 ) as process: 

67 stream_contents: list[bytes | None] = [None, None] 

68 try: 

69 async with create_task_group() as tg: 

70 if process.stdout: 

71 tg.start_soon(drain_stream, process.stdout, 0) 

72 if process.stderr: 

73 tg.start_soon(drain_stream, process.stderr, 1) 

74 if process.stdin and input: 

75 await process.stdin.send(input) 

76 await process.stdin.aclose() 

77 

78 await process.wait() 

79 except BaseException: 

80 process.kill() 

81 raise 

82 

83 output, errors = stream_contents 

84 if check and process.returncode != 0: 

85 raise CalledProcessError(cast(int, process.returncode), command, output, errors) 

86 

87 return CompletedProcess(command, cast(int, process.returncode), output, errors) 

88 

89 

90async def open_process( 

91 command: str | bytes | Sequence[str | bytes], 

92 *, 

93 stdin: int | IO[Any] | None = PIPE, 

94 stdout: int | IO[Any] | None = PIPE, 

95 stderr: int | IO[Any] | None = PIPE, 

96 cwd: str | bytes | PathLike[str] | None = None, 

97 env: Mapping[str, str] | None = None, 

98 start_new_session: bool = False, 

99) -> Process: 

100 """ 

101 Start an external command in a subprocess. 

102 

103 .. seealso:: :class:`subprocess.Popen` 

104 

105 :param command: either a string to pass to the shell, or an iterable of strings 

106 containing the executable name or path and its arguments 

107 :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a 

108 file-like object, or ``None`` 

109 :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, 

110 a file-like object, or ``None`` 

111 :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, 

112 :data:`subprocess.STDOUT`, a file-like object, or ``None`` 

113 :param cwd: If not ``None``, the working directory is changed before executing 

114 :param env: If env is not ``None``, it must be a mapping that defines the 

115 environment variables for the new process 

116 :param start_new_session: if ``true`` the setsid() system call will be made in the 

117 child process prior to the execution of the subprocess. (POSIX only) 

118 :return: an asynchronous process object 

119 

120 """ 

121 if isinstance(command, (str, bytes)): 

122 return await get_async_backend().open_process( 

123 command, 

124 shell=True, 

125 stdin=stdin, 

126 stdout=stdout, 

127 stderr=stderr, 

128 cwd=cwd, 

129 env=env, 

130 start_new_session=start_new_session, 

131 ) 

132 else: 

133 return await get_async_backend().open_process( 

134 command, 

135 shell=False, 

136 stdin=stdin, 

137 stdout=stdout, 

138 stderr=stderr, 

139 cwd=cwd, 

140 env=env, 

141 start_new_session=start_new_session, 

142 )