diff options
-rw-r--r-- | command.py | 21 | ||||
-rw-r--r-- | command_context.py | 52 | ||||
-rw-r--r-- | utils.py | 6 |
3 files changed, 54 insertions, 25 deletions
@@ -326,14 +326,14 @@ class Command(object): terminal_chain = cls._iochain_check_terminals(io_chain, terminal_chain) magic_fds = {} - input_cache = cmd_ctxt.setdefault('input_cache', {}) + input_cache = cmd_ctxt.setdefault('input_cache', {}, bypass=True) worklist = list(reversed(tailshake(terminal_chain, partitioner=lambda x: not (tuplist(x)) or protodecl(x)))) while worklist: flt, io_decl = worklist.pop() flt_ctxt = cmd_ctxt.ensure_filter(flt) - if not filter_backtrack[flt] and not flt_ctxt['out']: + if not filter_backtrack[flt] and 'out' not in flt_ctxt: # INFILTER in in-mode log.debug("Run `{0}' filter with `{1}' io decl. as INFILTER" .format(flt.__class__.__name__, io_decl)) @@ -342,15 +342,16 @@ class Command(object): else: in_obj = flt.in_format.as_instance(*io_decl) input_cache[io_decl] = in_obj - elif filter_backtrack[flt] and not flt_ctxt['out']: + elif filter_backtrack[flt] and 'out' not in flt_ctxt: # not INFILTER in either mode (nor output already precomputed?) log.debug("Run `{0}' filter with `{1}' io decl. as DOWNFILTER" .format(flt.__class__.__name__, io_decl)) - inputs = map(lambda x: cmd_ctxt.filter(x.__class__.__name__)['out'], + inputs = map(lambda x: cmd_ctxt.filter(x.__class__.__name__) + .get('out'), filter_backtrack[flt]) - notyet, ok = bifilter(lambda x: - cmd_ctxt.filter(x.__class__.__name__)['out'] is None, - filter_backtrack[flt]) + ok, notyet = bifilter(lambda x: 'out' in + cmd_ctxt.filter(x.__class__.__name__), + filter_backtrack[flt]) if notyet: log.debug("Backtrack with inclusion of {0} to feed `{1}'" .format(', '.join("`{0}'" @@ -375,8 +376,8 @@ class Command(object): assert all(inputs) in_obj = flt.in_format.as_instance(*inputs) - if not flt_ctxt['out'] or flt not in terminals: - if not flt_ctxt['out']: + if 'out' not in flt_ctxt or flt not in terminals: + if 'out' not in flt_ctxt: if flt.__class__.name in cmd_ctxt['filter_noop']: ret = in_obj else: @@ -438,7 +439,7 @@ class Command(object): 'system': getattr(opts, 'sys', ''), 'system_extra': getattr(opts, 'dist', '').split(','), 'quiet': getattr(opts, 'quiet', False), - }) + }, bypass=True) cmd_ctxt.ensure_filters(apply_intercalate(self._filter_chain)) io_driver = any2iter(self._fnc(cmd_ctxt, **kwargs)) io_handler = (self._iochain_proceed, lambda c, ec=EC.EXIT_SUCCESS: ec) diff --git a/command_context.py b/command_context.py index 2cb43d0..e197f5b 100644 --- a/command_context.py +++ b/command_context.py @@ -5,12 +5,14 @@ """Command context, i.e., state distributed along filters chain""" __author__ = "Jan Pokorný <jpokorny @at@ Red Hat .dot. com>" -from collections import MutableMapping +from collections import MutableMapping, MutableSequence, MutableSet import logging from .error import ClufterError +from .utils import isinstanceexcept log = logging.getLogger(__name__) +mutables = (MutableMapping, MutableSequence, MutableSet) class CommandContextError(ClufterError): @@ -19,26 +21,46 @@ class CommandContextError(ClufterError): class CommandContextBase(MutableMapping): """Object representing command context""" - def __init__(self, initial=None, parent=None): - try: - self._dict = initial if type(initial) is dict else dict(initial) - except TypeError: - self._dict = {} + def __init__(self, initial=None, parent=None, bypass=False): self._parent = parent if parent is not None else self + if isinstance(initial, CommandContextBase): + assert initial._parent is None + self._dict = initial._dict # trust dict to have expected props + else: + self._dict = {} + if initial is not None: + if not isinstance(initial, MutableMapping): + initial = dict(initial) + map(lambda (k, v): self.setdefault(k, v, bypass=bypass), + initial.iteritems()) def __delitem__(self, key): del self._dict[key] def __getitem__(self, key): try: - return self._dict[key] + ret = self._dict[key] except KeyError: - pass - # make it soft-error (->setdefault reimpl.) - return None if self._parent is self else self._parent[key] + if self._parent is self: + raise + ret = self._parent[key] + if isinstanceexcept(ret, mutables, CommandContextBase): + ret = ret.copy() + return ret - def setdefault(self, key, default=None): - return self._dict.setdefault(key, default) + def setdefault(self, key, *args, **kwargs): + """Allows implicit arrangements to be bypassed via `bypass` flag""" + assert len(args) < 2 + bypass = kwargs.get('bypass', False) + if bypass: + return self._dict.setdefault(key, *args) + try: + return self.__getitem__(key) + except KeyError: + if not args: + raise + self.__setitem__(key, *args) + return args[0] def __iter__(self): return iter(self._dict) @@ -49,7 +71,9 @@ class CommandContextBase(MutableMapping): def __setitem__(self, key, value): # XXX value could be also any valid dict constructor argument self._dict[key] = CommandContextBase(initial=value, parent=self) \ - if isinstance(value, dict) else value + if isinstanceexcept(value, MutableMapping, + CommandContextBase) \ + else value @property def parent(self): @@ -61,7 +85,7 @@ class CommandContext(CommandContextBase): # filter_context ... where global arguments for filters to be stored # filters ... where filter instance + arguments hybrid is stored super(CommandContext, self).__init__(*args, **kwargs) - # could be cycle up to self if {} was used instead + # could be cycle up to self if not bypassed self['__filter_context__'] = CommandContextBase() self['__filters__'] = CommandContextBase() @@ -46,9 +46,13 @@ filterdict_invpop = \ # -# function introspection related +# introspection related # +def isinstanceexcept(subj, obj, exc=()): + return isinstance(subj, obj) and not isinstance(subj, exc) + + def func_defaults_varnames(func, skip=0): """Using introspection, get arg defaults (dict) + all arg names (tuple) |