diff --git a/pysnooper/pysnooper.py b/pysnooper/pysnooper.py index 849d923..807ca6b 100644 --- a/pysnooper/pysnooper.py +++ b/pysnooper/pysnooper.py @@ -39,7 +39,7 @@ def get_write_and_truncate_functions(output): return (write, truncate) -def snoop(output=None, variables=(), depth=1, prefix='', overwrite=False): +def snoop(output=None, variables=(), exploded_variables=(), depth=1, prefix='', overwrite=False): ''' Snoop on the function, writing everything it's doing to stderr. @@ -75,7 +75,8 @@ def snoop(output=None, variables=(), depth=1, prefix='', overwrite=False): target_code_object = function.__code__ tracer = Tracer(target_code_object=target_code_object, write=write, truncate=truncate, variables=variables, depth=depth, - prefix=prefix, overwrite=overwrite) + prefix=prefix, overwrite=overwrite, + exploded_variables=exploded_variables) def inner(function_, *args, **kwargs): with tracer: diff --git a/pysnooper/tracer.py b/pysnooper/tracer.py index 583074f..a95c17d 100644 --- a/pysnooper/tracer.py +++ b/pysnooper/tracer.py @@ -7,7 +7,7 @@ import collections import datetime as datetime_module import itertools -from .variables import Variable +from .variables import CommonVariable, Exploded, BaseVariable from .third_party import six from .utils import get_shortish_repr, ensure_tuple @@ -99,13 +99,16 @@ def get_source_from_frame(frame): class Tracer: def __init__(self, target_code_object, write, truncate, variables=(), - depth=1, prefix='', overwrite=False): + exploded_variables=(), depth=1, prefix='', overwrite=False): self.target_code_object = target_code_object self._write = write self.truncate = truncate self.variables = [ - v if isinstance(v, Variable) else Variable(v) + v if isinstance(v, BaseVariable) else CommonVariable(v) for v in ensure_tuple(variables) + ] + [ + v if isinstance(v, BaseVariable) else Exploded(v) + for v in ensure_tuple(exploded_variables) ] self.frame_to_old_local_reprs = collections.defaultdict(lambda: {}) self.frame_to_local_reprs = collections.defaultdict(lambda: {}) diff --git a/pysnooper/variables.py b/pysnooper/variables.py index 4fab949..de9cfd7 100644 --- a/pysnooper/variables.py +++ b/pysnooper/variables.py @@ -4,7 +4,7 @@ from copy import deepcopy from .utils import get_shortish_repr, ensure_tuple -class Variable(object): +class BaseVariable(object): def __init__(self, source, exclude=()): self.source = source self.exclude = ensure_tuple(exclude) @@ -15,6 +15,14 @@ class Variable(object): main_value = eval(self.code, frame.f_globals, frame.f_locals) except Exception: return () + return self._items(main_value) + + def _items(self, key): + raise NotImplementedError() + + +class CommonVariable(BaseVariable): + def _items(self, main_value): result = [(self.source, get_shortish_repr(main_value))] for key in self._safe_keys(main_value): if key in self.exclude: @@ -46,7 +54,7 @@ class Variable(object): raise NotImplementedError() -class Attrs(Variable): +class Attrs(CommonVariable): def _keys(self, main_value): return itertools.chain( getattr(main_value, '__dict__', ()), @@ -60,7 +68,7 @@ class Attrs(Variable): return getattr(main_value, key) -class Keys(Variable): +class Keys(CommonVariable): def _keys(self, main_value): return main_value.keys() @@ -82,3 +90,20 @@ class Indices(Keys): result = deepcopy(self) result._slice = item return result + + +class Exploded(BaseVariable): + def _items(self, main_value): + typ = main_value.__class__ + + def has_method(name): + return callable(getattr(typ, name, None)) + + cls = Attrs + if has_method('__getitem__'): + if has_method('keys'): + cls = Keys + elif has_method('__len__'): + cls = Indices + + return cls(self.source, self.exclude)._items(main_value)