From: Christian Herdtweck Date: Thu, 1 Oct 2015 16:09:38 +0000 (+0200) Subject: finished argument parsing and dealing with procs and sockets in FollowContextManager X-Git-Tag: v1.2~103 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=b3a9d748cdb292fe79c0b084cf62ab9c28656eca;p=pyi2ncommon finished argument parsing and dealing with procs and sockets in FollowContextManager --- diff --git a/follow.py b/follow.py index f113f71..5cdd6ad 100644 --- a/follow.py +++ b/follow.py @@ -95,6 +95,8 @@ NOT possible:: from __future__ import print_function from warnings import warn import select +from subprocess import Popen, PIPE +from type_helpers import isstr # ############################################################################# # CONSTANTS @@ -226,7 +228,7 @@ class Follower(object): 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!') @@ -300,6 +302,8 @@ class Follower(object): # 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 @@ -317,60 +321,164 @@ class FollowContextManager(object): 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): @@ -382,10 +490,16 @@ class FollowContextManager(object): 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