Add classes to automatically track attributes, keys, etc. of variables

# Conflicts:
#	pysnooper/tracer.py
This commit is contained in:
Alex Hall 2019-04-28 19:05:11 +02:00 committed by Ram Rachum
parent 5111a534d4
commit 56d3389500
3 changed files with 152 additions and 47 deletions

View file

@ -6,59 +6,19 @@ import re
import collections
import datetime as datetime_module
import itertools
try:
import reprlib
import builtins
except ImportError:
import repr as reprlib
import __builtin__ as builtins
from .variables import Variable
from .third_party import six
from . import utils
from .utils import get_shortish_repr, ensure_tuple
ipython_filename_pattern = re.compile('^<ipython-input-([0-9]+)-.*>$')
class Repr(reprlib.Repr, object): # reprlib.Repr is old-style in Python 2
def __init__(self):
super(Repr, self).__init__()
self.maxother = 100
def repr(self, x):
try:
return super(Repr, self).repr(x)
except Exception as e:
return '<{} instance at {:#x} (__repr__ raised {})>'.format(
x.__class__.__name__, id(x), e.__class__.__name__)
def repr_instance(self, x, level):
s = builtins.repr(x)
if len(s) > self.maxother:
i = max(0, (self.maxother - 3) // 2)
j = max(0, self.maxother - 3 - i)
s = s[:i] + '...' + s[len(s) - j:]
return s
repr_instance = Repr()
def get_shortish_repr(item):
r = repr_instance.repr(item)
r = r.replace('\r', '').replace('\n', '')
return r
def get_local_reprs(frame, variables=()):
result = {key: get_shortish_repr(value) for key, value
in frame.f_locals.items()}
for variable in variables:
try:
result[variable] = get_shortish_repr(
eval(variable, (frame.f_globals or {}), frame.f_locals)
)
except Exception:
pass
result.update(variable.items(frame))
return result
@ -143,9 +103,10 @@ class Tracer:
self.target_code_object = target_code_object
self._write = write
self.truncate = truncate
if isinstance(variables, six.string_types):
variables = (variables,)
self.variables = variables
self.variables = [
v if isinstance(v, Variable) else Variable(v)
for v in ensure_tuple(variables)
]
self.frame_to_old_local_reprs = collections.defaultdict(lambda: {})
self.frame_to_local_reprs = collections.defaultdict(lambda: {})
self.depth = depth

View file

@ -2,9 +2,17 @@
# This program is distributed under the MIT license.
import abc
import sys
from .pycompat import ABC
from .third_party import six
try:
import reprlib
import builtins
except ImportError:
import repr as reprlib
import __builtin__ as builtins
def _check_methods(C, *methods):
mro = C.__mro__
@ -31,6 +39,7 @@ class WritableStream(ABC):
return NotImplemented
file_reading_errors = (
IOError,
OSError,
@ -38,7 +47,47 @@ file_reading_errors = (
)
def shitcode(s):
return ''.join(
(c if (0 < ord(c) < 256) else '?') for c in s
)
class Repr(reprlib.Repr, object): # reprlib.Repr is old-style in Python 2
def __init__(self):
super(Repr, self).__init__()
self.maxother = 100
def repr(self, x):
try:
return super(Repr, self).repr(x)
except Exception as e:
return '<{} instance at {:#x} (__repr__ raised {})>'.format(
x.__class__.__name__, id(x), e.__class__.__name__)
def repr_instance(self, x, level):
s = builtins.repr(x)
if len(s) > self.maxother:
i = max(0, (self.maxother - 3) // 2)
j = max(0, self.maxother - 3 - i)
s = s[:i] + '...' + s[len(s) - j:]
return s
repr_instance = Repr()
def get_shortish_repr(item):
r = repr_instance.repr(item)
r = r.replace('\r', '').replace('\n', '')
return r
def ensure_tuple(x):
if isinstance(x, six.string_types):
x = (x,)
return tuple(x)

95
pysnooper/variables.py Normal file
View file

@ -0,0 +1,95 @@
import itertools
from copy import deepcopy
from .utils import get_shortish_repr, ensure_tuple
class Variable(object):
def __init__(self, source, exclude=()):
self.source = source
self.exclude = ensure_tuple(exclude)
self.code = compile(source, '<variable>', 'eval')
def items(self, frame):
try:
main_value = eval(self.code, frame.f_globals, frame.f_locals)
except Exception:
return ()
result = [(self.source, get_shortish_repr(main_value))]
for key in self._safe_keys(main_value):
if key in self.exclude:
continue
try:
value = self._get_value(main_value, key)
except Exception:
continue
result.append((
'({}){}'.format(self.source, self._format_key(key)),
get_shortish_repr(value)
))
return result
def _safe_keys(self, main_value):
try:
for key in self._keys(main_value):
yield key
except Exception:
pass
def _keys(self, main_value):
return ()
def _format_key(self, key):
raise NotImplementedError()
def _get_value(self, main_value, key):
raise NotImplementedError()
class Attrs(Variable):
def _keys(self, main_value):
return itertools.chain(
getattr(main_value, '__dict__', ()),
getattr(main_value, '__slots__', ())
)
def _format_key(self, key):
return '.' + key
def _get_value(self, main_value, key):
return getattr(main_value, key)
class Keys(Variable):
def _keys(self, main_value):
return main_value.keys()
def _format_key(self, key):
return '[{!r}]'.format(key)
def _get_value(self, main_value, key):
return main_value[key]
class Indices(Keys):
_slice = slice(None)
def _keys(self, main_value):
return range(len(main_value))[self._slice]
def __getitem__(self, item):
assert isinstance(item, slice)
result = deepcopy(self)
result._slice = item
return result
class Enumerate(Variable):
def _keys(self, main_value):
return enumerate(main_value)
def _format_key(self, key):
return '<{}>'.format(key[0])
def _get_value(self, main_value, key):
return key[1]