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,62 @@ 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 ,
4953 ** kwargs : Any ,
50- ) -> CompletedProcess :
54+ ) -> subprocess . CompletedProcess [ str ] :
5155 platform_settings : dict [str , Any ] = {}
5256 if platform .system () == "Windows" :
5357 platform_settings ["env" ] = os .environ
5458
55- # this part keeps behavior backwards compatible with subprocess.run
56- tee = kwargs .get ("tee" , True )
57- stdout = kwargs .get ("stdout" , sys .stdout )
59+ # pop arguments so that we can ensure there are no unexpected arguments
60+ stdout = kwargs .pop ("stdout" , sys .stdout )
61+ stderr = kwargs .pop ("stderr" , sys .stderr )
62+ for arg in ["cwd" , "env" ]:
63+ if arg in kwargs :
64+ platform_settings [arg ] = kwargs .pop (arg )
65+ if kwargs :
66+ msg = f"Popen.__init__() got an unexpected keyword argument '{ next (iter (kwargs .keys ()))} '"
67+ raise TypeError (msg )
68+ del kwargs
5869
5970 with Path (os .devnull ).open ("w" , encoding = "UTF-8" ) as devnull :
6071 if stdout == subprocess .DEVNULL or not tee :
6172 stdout = devnull
62- stderr = kwargs .get ("stderr" , sys .stderr )
6373 if stderr == subprocess .DEVNULL or not tee :
6474 stderr = devnull
6575
6676 # We need to tell subprocess which shell to use when running shell-like
6777 # commands.
6878 # * SHELL is not always defined
6979 # * /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 ]
80+ if executable is None and isinstance (args , str ) and " " in args :
81+ executable = os .environ .get ("SHELL" , "/bin/sh" )
7782
7883 # Some users are reporting that default (undocumented) limit 64k is too
7984 # low
8085 if isinstance (args , str ):
8186 process = await asyncio .create_subprocess_shell (
8287 args ,
8388 limit = STREAM_LIMIT ,
84- stdin = kwargs . get ( " stdin" , False ) ,
89+ stdin = stdin ,
8590 stdout = asyncio .subprocess .PIPE ,
8691 stderr = asyncio .subprocess .PIPE ,
92+ executable = executable ,
8793 ** platform_settings ,
8894 )
8995 else :
9096 process = await asyncio .create_subprocess_exec (
9197 * args ,
9298 limit = STREAM_LIMIT ,
93- stdin = kwargs . get ( " stdin" , False ) ,
99+ stdin = stdin ,
94100 stdout = asyncio .subprocess .PIPE ,
95101 stderr = asyncio .subprocess .PIPE ,
102+ executable = executable ,
96103 ** platform_settings ,
97104 )
98105 out : list [str ] = []
@@ -101,7 +108,7 @@ async def _stream_subprocess( # noqa: C901
101108 def tee_func (line : bytes , sink : list [str ], pipe : Any | None ) -> None :
102109 line_str = line .decode ("utf-8" ).rstrip ()
103110 sink .append (line_str )
104- if not kwargs . get ( " quiet" ) :
111+ if not quiet :
105112 if pipe and hasattr (pipe , "write" ):
106113 print (line_str , file = pipe )
107114 else :
@@ -126,15 +133,14 @@ def tee_func(line: bytes, sink: list[str], pipe: Any | None) -> None:
126133
127134 # We need to be sure we keep the stdout/stderr output identical with
128135 # the ones produced by subprocess.run(), at least when in text mode.
129- check = kwargs .get ("check" , False )
130136 stdout = None if check else ""
131137 stderr = None if check else ""
132138 if out :
133139 stdout = os .linesep .join (out ) + os .linesep
134140 if err :
135141 stderr = os .linesep .join (err ) + os .linesep
136142
137- return CompletedProcess (
143+ return subprocess . CompletedProcess (
138144 args = args ,
139145 returncode = await process .wait (),
140146 stdout = stdout ,
@@ -151,13 +157,15 @@ def run(
151157 bufsize : int = - 1 ,
152158 input : bytes | str | None = None , # noqa: A002
153159 * ,
154- capture_output : bool = False ,
160+ capture_output : bool = True ,
155161 timeout : int | None = None ,
156162 check : bool = False ,
157163 ** kwargs : Any ,
158- ) -> CompletedProcess :
164+ ) -> subprocess . CompletedProcess [ str ] :
159165 """Drop-in replacement for subprocess.run that behaves like tee.
160166
167+ Not all arguments to subprocess.run are supported.
168+
161169 Extra arguments added by our version:
162170 echo: False - Prints command before executing it.
163171 quiet: False - Avoid printing output
@@ -177,16 +185,22 @@ def run(
177185 cmd = args if isinstance (args , str ) else join (args )
178186 # 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
179187 if bufsize != - 1 :
180- msg = "Ignored bufsize argument as it is not supported yet by __package__"
188+ msg = f"Ignored bufsize argument as it is not supported yet by { __package__ } "
189+ _logger .warning (msg )
190+ if input is not None :
191+ msg = f"Ignored input argument as it is not supported yet by { __package__ } "
192+ _logger .warning (msg )
193+ if timeout is not None :
194+ msg = f"Ignored timeout argument as it is not supported yet by { __package__ } "
195+ _logger .warning (msg )
196+ if not capture_output :
197+ msg = f"Ignored capture_output argument as it is not supported yet by { __package__ } "
181198 _logger .warning (msg )
182199 kwargs ["check" ] = check
183- kwargs ["input" ] = input
184- kwargs ["timeout" ] = timeout
185- kwargs ["capture_output" ] = capture_output
186200
187201 check = kwargs .get ("check" , False )
188202
189- if kwargs .get ("echo" ):
203+ if kwargs .pop ("echo" , False ):
190204 print (f"COMMAND: { cmd } " ) # noqa: T201
191205
192206 result = asyncio .run (_stream_subprocess (cmd , ** kwargs ))
0 commit comments