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

36 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:19 +0000

1from __future__ import annotations 

2 

3from io import BytesIO 

4from os import PathLike 

5from subprocess import DEVNULL, PIPE, CalledProcessError, CompletedProcess 

6from typing import ( 

7 IO, 

8 Any, 

9 AsyncIterable, 

10 Mapping, 

11 Sequence, 

12 cast, 

13) 

14 

15from ..abc import Process 

16from ._eventloop import get_asynclib 

17from ._tasks import create_task_group 

18 

19 

20async def run_process( 

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

22 *, 

23 input: bytes | None = None, 

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

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

26 check: bool = True, 

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

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

29 start_new_session: bool = False, 

30) -> CompletedProcess[bytes]: 

31 """ 

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

33 

34 .. seealso:: :func:`subprocess.run` 

35 

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

37 executable name or path and its arguments 

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

39 :param stdout: either :data:`subprocess.PIPE` or :data:`subprocess.DEVNULL` 

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

41 :data:`subprocess.STDOUT` 

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

43 terminates with a return code other than 0 

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

45 :param env: if not ``None``, this mapping replaces the inherited environment variables from the 

46 parent process 

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

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

49 :return: an object representing the completed process 

50 :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process exits with a 

51 nonzero return code 

52 

53 """ 

54 

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

56 buffer = BytesIO() 

57 async for chunk in stream: 

58 buffer.write(chunk) 

59 

60 stream_contents[index] = buffer.getvalue() 

61 

62 async with await open_process( 

63 command, 

64 stdin=PIPE if input else DEVNULL, 

65 stdout=stdout, 

66 stderr=stderr, 

67 cwd=cwd, 

68 env=env, 

69 start_new_session=start_new_session, 

70 ) as process: 

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

72 try: 

73 async with create_task_group() as tg: 

74 if process.stdout: 

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

76 if process.stderr: 

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

78 if process.stdin and input: 

79 await process.stdin.send(input) 

80 await process.stdin.aclose() 

81 

82 await process.wait() 

83 except BaseException: 

84 process.kill() 

85 raise 

86 

87 output, errors = stream_contents 

88 if check and process.returncode != 0: 

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

90 

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

92 

93 

94async def open_process( 

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

96 *, 

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

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

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

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

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

102 start_new_session: bool = False, 

103) -> Process: 

104 """ 

105 Start an external command in a subprocess. 

106 

107 .. seealso:: :class:`subprocess.Popen` 

108 

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

110 executable name or path and its arguments 

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

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

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

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

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

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

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

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

119 variables for the new process 

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

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

122 :return: an asynchronous process object 

123 

124 """ 

125 shell = isinstance(command, str) 

126 return await get_asynclib().open_process( 

127 command, 

128 shell=shell, 

129 stdin=stdin, 

130 stdout=stdout, 

131 stderr=stderr, 

132 cwd=cwd, 

133 env=env, 

134 start_new_session=start_new_session, 

135 )