from __future__ import print_function
from warnings import warn
import select
+from subprocess import Popen, PIPE
+from type_helpers import isstr
# #############################################################################
# CONSTANTS
sources_and_descriptions = sources_and_descriptions[0]
for arg in sources_and_descriptions:
- if isinstance(arg, str): # is a description
+ if isstr(arg): # is a description
if len(descriptions) == 0:
raise ValueError(
'first arg must be source, not a description!')
# CONTEXT MANAGEMENT
# #############################################################################
+#: defualt args for Popen constructor in case a process is given as cmd
+DEFAULT_SUBPROCESS_ARGS = dict(bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE)
class FollowContextManager(object):
""" context for Follower objects, ensures everything is closed properly
procs = None
proc_descs = None
+ proc_objs = None
def __init__(self, files=None, file_descs=None,
sockets=None, socket_descs=None,
- procs=None, proc_descs=None):
- """ create a context manager for Follower, only does arg checking """
+ procs=None, proc_descs=None,
+ subprocess_args=None):
+ """ create a context manager for Follower
+
+ check args and that they match.
+ tries to guess good descs for files, sockets and procs that are not
+ given
+
+ :param files: list/tuple of, or single file handle or file name
+ :param file_descs: None or list/tuple of same length as files
+ :param sockets: list/tuple of, or single socket
+ :param socket_descs: None or list/tuple of same length as sockets
+ :param procs: list/tuple of, or single Popen object or command itself
+ as str/list/tuple
+ :param proc_descs: None or list/tuple of same length as procs
+ :param dict subprocess_args: dict or args that are merged with
+ :py:data:`DEFAULT_SUBPROCESS_ARGS`
+ """
print('__init__')
+ # set files and file_descs and ensure that they matching lists
if files is None:
self.files = []
- if isinstance(files, str):
+ elif isstr(files):
+ self.files = [files, ]
+ elif isinstance(files, file):
self.files = [files, ]
else:
self.files = files
if file_descs is None:
- self.file_descs = [None for _ in self.files]
- elif len(self.files) == len(self.file_descs):
- self.file_descs = file_descs
+ temp_descs = [None for _ in self.files]
+ elif len(self.files) == len(file_descs):
+ temp_descs = file_descs
else:
raise ValueError('if given file descs, need desc for all files!')
+ # try to guess good file_desc values; ensure they are str
+ self.file_descs = []
+ for file_nr, (file, file_desc) in \
+ enumerate(zip(self.files, temp_descs)):
+ if isstr(file_desc):
+ self.file_descs.append(file_desc)
+ continue
+ elif file_desc is None: # need to guess something
+ if isstr(file):
+ self.file_descs.append(file)
+ else:
+ self.file_descs.append('file{0}'.format(file_nr))
+ else: # desc is neither str nor None
+ raise ValueError('file descs must be string or None!')
+
+ # set sockets and socket_descs and ensure that they matching lists
if sockets is None:
self.sockets = []
+ elif isinstance(sockets, Socket):
+ self.sockets = [sockets, ]
else:
self.sockets = sockets
- self.socket_descs = socket_descs
+ if socket_descs is None:
+ temp_descs = [None for _ in self.sockets]
+ elif len(self.sockets) == len(socket_descs):
+ temp_descs = socket_descs
+ else:
+ raise ValueError('if given socket descs, '
+ 'need descs for all sockets!')
+
+ # try to guess good socket_desc values; ensure they are str
+ self.socket_descs = []
+ for file_nr, (socket, socket_desc) in \
+ enumerate(zip(self.sockets, temp_descs)):
+ if isstr(socket_desc):
+ self.socket_descs.append(socket_desc)
+ elif socket_desc is None: # need to guess something
+ self.socket_descs.append('socket{0}'.format(socket_nr))
+ else: # desc is neither str nor None
+ raise ValueError('socket descs must be string or None!')
+
+ # set procs and proc_descs and ensure they matching lists
if procs is None:
- procs = []
+ self.procs = []
+ elif isstr(procs):
+ self.procs = [procs, ]
+ elif isinstance(procs, Popen):
+ self.procs = [procs, ]
else:
self.procs = procs
- self.proc_descs = proc_descs
+ if proc_descs is None:
+ temp_descs = [None for _ in self.procs]
+ elif len(proc_descs) == len(self.procs):
+ temp_descs = proc_descs
+ else:
+ raise ValueError('if given proc descs, need descs for all procs!')
+
+ # try to guess good proc_desc values; ensure they are str
+ self.proc_descs = []
+ for proc_nr, (proc, proc_desc) in \
+ enumerate(zip(self.procs, temp_descs)):
+ if isstr(proc_desc):
+ self.proc_descs.append(proc_desc)
+ elif proc_desc is None: # need to guess something
+ if isstr(proc):
+ self.proc_descs.append(proc)
+ elif isinstance(proc, (tuple, list)):
+ self.proc_descs.append(' '.join(proc))
+ elif isinstance(proc, Popen):
+ if isstr(proc.args):
+ self.proc_descs.append(proc.args)
+ else:
+ self.proc_descs.append(' '.join(proc.args))
+ else:
+ self.proc_descs.append('proc{0}'.format(proc_nr))
+ else: # desc is neither str nor None
+ raise ValueError('proc descs must be string or None!')
+
+ self.subprocess_args = DEFAULT_SUBPROCESS_ARGS
+ if subprocess_args is not None:
+ self.subprocess_args.update(subprocess_args)
def __enter__(self):
- """ called when entering run context """
+ """ called when entering run context
+
+ opens files, tries to create descs, and assembles args for Follower
+ """
print('__enter__')
args = []
# open files
self.file_handles = []
- for file_arg, new_desc in zip(self.files, self.file_descs):
+ for file_arg, desc in zip(self.files, self.file_descs):
if isinstance(file_arg, str):
new_handle = open(file_arg)
- if new_desc is None:
- new_desc = file_arg
else: # assume file_arg is a file handle
new_handle = file_arg
- if new_desc is None:
- new_desc = file_arg.name
self.file_handles.append(new_handle)
args.append(new_handle)
- args.append(new_desc)
+ args.append(desc)
# end: for files and file_descs
- # TODO: repeat for sockets and procs, add them to args
+ for sock_number, (socket, desc) in \
+ enumerate(zip(self.sockets, self.socket_descs)):
+ args.append(socket)
+ args.append(desc)
+
+ self.proc_objs = []
+ for proc, desc in zip(self.procs, self.proc_descs):
+ if isstr(proc) or isinstance(proc, (list,tuple)):
+ proc = Popen(proc, **self.subprocess_args)
+ self.proc_objs.append(proc)
+ args.append(proc.stdout)
+ args.append(desc + '_out')
+ args.append(proc.stderr)
+ args.append(desc + '_err')
+
return Follower(args)
def __exit__(self, exc_type, exc_value, traceback):
for handle in self.file_handles:
try:
handle.close()
- except:
+ except Exception:
+ warn('ignoring exception exiting follower context manager!')
+ for proc in self.proc_objs:
+ try:
+ proc.kill()
+ rest_out, rest_err = proc.communicate()
+ if rest_out or rest_err:
+ warn('Ignoring left-over output in proc')
+ except Exception:
warn('ignoring exception exiting follower context manager!')
-
- # TODO: communicate and wait for procs, close sockets
# end: class FollowContextManager