summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command.py21
-rw-r--r--command_context.py52
-rw-r--r--utils.py6
3 files changed, 54 insertions, 25 deletions
diff --git a/command.py b/command.py
index 9a49709..92420d5 100644
--- a/command.py
+++ b/command.py
@@ -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()
diff --git a/utils.py b/utils.py
index 00cf5cc..08dbd21 100644
--- a/utils.py
+++ b/utils.py
@@ -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)