2323
2424
2525class Popen :
26+ """Provides access to a process that is running inside a given shell.
27+ Inspired by the :class:`subprocess.Popen` interface within the Python
28+ standard library. Unlike :class:`subprocess.Popen`, instances of this
29+ class should be generated by :meth:`ShellProxy.popen` rather than via
30+ the constructor.
31+
32+ Attributes
33+ ----------
34+ stream: Iterator[str]
35+ An output stream for this process.
36+ args: str
37+ The argument string that was used to generate this process.
38+ pid: int, optional
39+ The PID of this process, if known.
40+ finished: bool
41+ A dynamic flag (i.e., a property) that indicates whether this process
42+ has terminated.
43+ retcode: int, optional
44+ The return code produced by this process, if known.
45+ """
2646 def __init__ (self ,
2747 args : str ,
2848 uid : str ,
@@ -71,7 +91,6 @@ def pid(self) -> Optional[int]:
7191
7292 @property
7393 def finished (self ) -> bool :
74- """True if the process has exited; False if not."""
7594 return self .returncode is not None
7695
7796 @property
@@ -81,6 +100,13 @@ def returncode(self) -> Optional[int]:
81100 return self .__returncode
82101
83102 def send_signal (self , sig : int ) -> None :
103+ """Sends a given signal to the process.
104+
105+ Parameters
106+ ----------
107+ sig: int
108+ The signal number.
109+ """
84110 pid = self .pid
85111 if pid :
86112 self .__shell .send_signal (pid , sig )
@@ -102,7 +128,7 @@ def wait(self, time_limit: Optional[float] = None) -> int:
102128
103129 Parameters
104130 ----------
105- time_limit: Optional[ float] = None
131+ time_limit: float, optional
106132 An optional time limit.
107133
108134 Raises
@@ -136,7 +162,18 @@ def exec_id_to_host_pid(self, exec_id: str) -> int:
136162 return self .__api_docker .exec_inspect (exec_id )['Pid' ]
137163
138164 def local_to_host_pid (self , pid_local : int ) -> Optional [int ]:
139- """Finds the host PID for a process inside this shell."""
165+ """Finds the host PID for a process inside this shell.
166+
167+ Parameters
168+ ----------
169+ pid_local: int
170+ The PID of the process inside the container.
171+
172+ Returns
173+ -------
174+ int
175+ The PID of the same process on the host machine.
176+ """
140177 ctr_pids = [self .__container_pid ]
141178 info = self .__api_docker .inspect_container (self .__container_docker .id )
142179 ctr_pids += [self .exec_id_to_host_pid (i ) for i in info ['ExecIDs' ]]
@@ -163,6 +200,15 @@ def local_to_host_pid(self, pid_local: int) -> Optional[int]:
163200 return None
164201
165202 def send_signal (self , pid : int , sig : int ) -> None :
203+ """Sends a given signal to a specified process.
204+
205+ Parameters
206+ ----------
207+ pid: int
208+ The PID of the process.
209+ sig: int
210+ The signal number.
211+ """
166212 self .execute (f'kill -{ sig } { pid } ' , user = 'root' )
167213
168214 def __generate_popen_uid (self , command : str ) -> str :
@@ -184,12 +230,12 @@ def environ(self, var: str) -> str:
184230 raise exceptions .EnvNotFoundError (var )
185231 return val
186232
187- def instrument (self ,
188- command : str ,
189- time_limit : Optional [int ] = None ,
190- kill_after : int = 1 ,
191- identifier : Optional [str ] = None
192- ) -> str :
233+ def _instrument (self ,
234+ command : str ,
235+ time_limit : Optional [int ] = None ,
236+ kill_after : int = 1 ,
237+ identifier : Optional [str ] = None
238+ ) -> str :
193239 logger .debug ("instrumenting command: %s" , command )
194240 q = shlex .quote
195241 command = f'source /.environment && { command } '
@@ -212,12 +258,48 @@ def popen(self,
212258 time_limit : Optional [int ] = None ,
213259 kill_after : int = 1
214260 ) -> Popen :
261+ """Creates a process without blocking, and returns an interface to it.
262+ Inspired by :meth:`subprocess.Popen` in the Python standard library.
263+ This method can be used, for example, to stream the output of a
264+ non-blocking process, or to send a signal (e.g., SIGTERM) to a process
265+ at run-time.
266+
267+ Parameters
268+ ----------
269+ command: str
270+ The command that should be executed.
271+ stdout: bool
272+ If :code:`True`, includes stdout as part of output.
273+ stderr: bool
274+ If :code:`True`, includes stderr as part of output.
275+ user: str, optional
276+ The name or UID of the user, inside the container, that should
277+ execute the command. If left unspecified, the default user for
278+ the container will be used.
279+ context: str, optional
280+ The absolute path to the working directory that should be used
281+ when executing the command. If unspecified, the default working
282+ directory for the container will be used.
283+ time_limit: int, optional
284+ The maximum number of seconds that the command is allowed to run
285+ before being terminated via SIGTERM. If unspecified, no time limit
286+ will be imposed on command execution.
287+ kill_after: int
288+ The maximum number of seconds to wait before sending SIGKILL to
289+ the process after attempting termination via SIGTERM. Only applies
290+ when :code:`time_limit` is specified.
291+
292+ Returns
293+ -------
294+ Popen
295+ An interface for interacting with and inspecting the process.
296+ """
215297 uid_popen = self .__generate_popen_uid (command )
216298 id_container = self .__container_docker .id
217299 api_docker = self .__api_docker
218300 command_orig = command
219- command = self .instrument (command , time_limit , kill_after ,
220- identifier = uid_popen )
301+ command = self ._instrument (command , time_limit , kill_after ,
302+ identifier = uid_popen )
221303 exec_resp = api_docker .exec_create (id_container , command ,
222304 tty = True ,
223305 stdout = stdout ,
@@ -241,9 +323,21 @@ def execute(self,
241323 time_limit : Optional [int ] = None ,
242324 kill_after : int = 1
243325 ) -> Tuple [int , str , float ]:
326+ """Executes a given command and blocks until its completion.
327+
328+ Note
329+ ----
330+ Accepts the same arguments as :meth:`popen`.
331+
332+ Returns
333+ -------
334+ Tuple[int, str, float]
335+ The return code, output, and wall-clock running time of the
336+ execution.
337+ """
244338 logger .debug ("executing command: %s" , command )
245339 dockerc = self .__container_docker
246- command = self .instrument (command , time_limit , kill_after )
340+ command = self ._instrument (command , time_limit , kill_after )
247341
248342 timer = Stopwatch ()
249343 timer .start ()
0 commit comments