2525_logger = logging .getLogger (__name__ )
2626
2727if TYPE_CHECKING :
28- from subprocess_tee ._types import SequenceNotStr
29-
30- CompletedProcess = subprocess .CompletedProcess [Any ]
3128 from collections .abc import Callable
32- else :
33- CompletedProcess = subprocess .CompletedProcess
29+
30+ from subprocess_tee ._types import SequenceNotStr
31+ CompletedProcess = subprocess .CompletedProcess
3432
3533STREAM_LIMIT = 2 ** 23 # 8MB instead of default 64kb, override it if you need
3634
@@ -46,53 +44,66 @@ async def _read_stream(stream: StreamReader, callback: Callable[..., Any]) -> No
4644
4745async def _stream_subprocess ( # noqa: C901
4846 args : str | tuple [str , ...],
47+ * ,
48+ stdin = None ,
49+ tee = True ,
50+ quiet = False ,
51+ check = False ,
52+ executable = None ,
53+ capture_output = True ,
4954 ** kwargs : Any ,
50- ) -> CompletedProcess :
55+ ) -> subprocess . CompletedProcess [ str ] :
5156 platform_settings : dict [str , Any ] = {}
5257 if platform .system () == "Windows" :
5358 platform_settings ["env" ] = os .environ
5459
55- # this part keeps behavior backwards compatible with subprocess.run
56- tee = kwargs .get ("tee" , True )
57- stdout = kwargs .get ("stdout" , sys .stdout )
60+ # pop arguments so that we can ensure there are no unexpected arguments
61+ stdout = kwargs .pop ("stdout" , sys .stdout )
62+ stderr = kwargs .pop ("stderr" , sys .stderr )
63+ for arg in ["cwd" , "env" ]:
64+ if arg in kwargs :
65+ platform_settings [arg ] = kwargs .pop (arg )
66+ if kwargs :
67+ msg = f"Popen.__init__() got an unexpected keyword argument '{ list (kwargs .keys ())[0 ]} '"
68+ raise TypeError (msg )
69+ if not capture_output :
70+ msg = "Only capture_output=True is supported"
71+ raise NotImplementedError (msg )
72+ del kwargs
5873
5974 with Path (os .devnull ).open ("w" , encoding = "UTF-8" ) as devnull :
6075 if stdout == subprocess .DEVNULL or not tee :
6176 stdout = devnull
62- stderr = kwargs .get ("stderr" , sys .stderr )
6377 if stderr == subprocess .DEVNULL or not tee :
6478 stderr = devnull
6579
6680 # We need to tell subprocess which shell to use when running shell-like
6781 # commands.
6882 # * SHELL is not always defined
6983 # * /bin/bash does not exit on alpine, /bin/sh seems bit more portable
70- if "executable" not in kwargs and isinstance (args , str ) and " " in args :
71- platform_settings ["executable" ] = os .environ .get ("SHELL" , "/bin/sh" )
72-
73- # pass kwargs we know to be supported
74- for arg in ["cwd" , "env" ]:
75- if arg in kwargs :
76- platform_settings [arg ] = kwargs [arg ]
84+ if executable is None and isinstance (args , str ) and " " in args :
85+ executable = os .environ .get ("SHELL" , "/bin/sh" )
7786
7887 # Some users are reporting that default (undocumented) limit 64k is too
7988 # low
8089 if isinstance (args , str ):
8190 process = await asyncio .create_subprocess_shell (
8291 args ,
8392 limit = STREAM_LIMIT ,
84- stdin = kwargs . get ( " stdin" , False ) ,
93+ stdin = stdin ,
8594 stdout = asyncio .subprocess .PIPE ,
8695 stderr = asyncio .subprocess .PIPE ,
96+ executable = executable ,
8797 ** platform_settings ,
8898 )
8999 else :
90100 process = await asyncio .create_subprocess_exec (
91101 * args ,
92102 limit = STREAM_LIMIT ,
93- stdin = kwargs . get ( " stdin" , False ) ,
103+ stdin = stdin ,
94104 stdout = asyncio .subprocess .PIPE ,
95105 stderr = asyncio .subprocess .PIPE ,
106+ executable = executable ,
96107 ** platform_settings ,
97108 )
98109 out : list [str ] = []
@@ -101,7 +112,7 @@ async def _stream_subprocess( # noqa: C901
101112 def tee_func (line : bytes , sink : list [str ], pipe : Any | None ) -> None :
102113 line_str = line .decode ("utf-8" ).rstrip ()
103114 sink .append (line_str )
104- if not kwargs . get ( " quiet" ) :
115+ if not quiet :
105116 if pipe and hasattr (pipe , "write" ):
106117 print (line_str , file = pipe )
107118 else :
@@ -126,15 +137,14 @@ def tee_func(line: bytes, sink: list[str], pipe: Any | None) -> None:
126137
127138 # We need to be sure we keep the stdout/stderr output identical with
128139 # the ones produced by subprocess.run(), at least when in text mode.
129- check = kwargs .get ("check" , False )
130140 stdout = None if check else ""
131141 stderr = None if check else ""
132142 if out :
133143 stdout = os .linesep .join (out ) + os .linesep
134144 if err :
135145 stderr = os .linesep .join (err ) + os .linesep
136146
137- return CompletedProcess (
147+ return subprocess . CompletedProcess (
138148 args = args ,
139149 returncode = await process .wait (),
140150 stdout = stdout ,
@@ -149,15 +159,15 @@ def tee_func(line: bytes, sink: list[str], pipe: Any | None) -> None:
149159def run (
150160 args : str | SequenceNotStr [str ] | None = None ,
151161 bufsize : int = - 1 ,
152- input : bytes | str | None = None , # noqa: A002
153162 * ,
154- capture_output : bool = False ,
155- timeout : int | None = None ,
163+ capture_output : bool = True ,
156164 check : bool = False ,
157165 ** kwargs : Any ,
158- ) -> CompletedProcess :
166+ ) -> subprocess . CompletedProcess [ str ] :
159167 """Drop-in replacement for subprocess.run that behaves like tee.
160168
169+ Not all arguments to subprocess.run are supported.
170+
161171 Extra arguments added by our version:
162172 echo: False - Prints command before executing it.
163173 quiet: False - Avoid printing output
@@ -177,16 +187,14 @@ def run(
177187 cmd = args if isinstance (args , str ) else join (args )
178188 # bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=-1, encoding=None, errors=None, text=None, pipesize=-1, process_group=None
179189 if bufsize != - 1 :
180- msg = "Ignored bufsize argument as it is not supported yet by __package__"
190+ msg = f "Ignored bufsize argument as it is not supported yet by { __package__ } "
181191 _logger .warning (msg )
182192 kwargs ["check" ] = check
183- kwargs ["input" ] = input
184- kwargs ["timeout" ] = timeout
185193 kwargs ["capture_output" ] = capture_output
186194
187195 check = kwargs .get ("check" , False )
188196
189- if kwargs .get ("echo" ):
197+ if kwargs .pop ("echo" , False ):
190198 print (f"COMMAND: { cmd } " ) # noqa: T201
191199
192200 result = asyncio .run (_stream_subprocess (cmd , ** kwargs ))
0 commit comments