Add prefix feature

This commit is contained in:
Ram Rachum 2019-04-21 21:35:43 +03:00
parent 621a4865cc
commit 28a8b24494
5 changed files with 84 additions and 44 deletions

View file

@ -37,31 +37,31 @@ We're writing a function that converts a number to binary, by returing a list of
The output to stderr is:
............... number = 6
00:24:15.284000 call 3 @pysnooper.snoop()
00:24:15.284000 line 5 if number:
00:24:15.284000 line 6 bits = []
............... bits = []
00:24:15.284000 line 7 while number:
00:24:15.284000 line 8 number, remainder = divmod(number, 2)
............... number = 3
............... remainder = 0
00:24:15.284000 line 9 bits.insert(0, remainder)
............... bits = [0]
00:24:15.284000 line 7 while number:
00:24:15.284000 line 8 number, remainder = divmod(number, 2)
............... number = 1
............... remainder = 1
00:24:15.284000 line 9 bits.insert(0, remainder)
............... bits = [1, 0]
00:24:15.284000 line 7 while number:
00:24:15.284000 line 8 number, remainder = divmod(number, 2)
............... number = 0
00:24:15.284000 line 9 bits.insert(0, remainder)
............... bits = [1, 1, 0]
00:24:15.284000 line 7 while number:
00:24:15.284000 line 10 return bits
00:24:15.284000 return 10 return bits
Starting var:.. number = 6
21:14:32.099769 call 3 @pysnooper.snoop()
21:14:32.099769 line 5 if number:
21:14:32.099769 line 6 bits = []
New var:....... bits = []
21:14:32.099769 line 7 while number:
21:14:32.099769 line 8 number, remainder = divmod(number, 2)
New var:....... remainder = 0
Modified var:.. number = 3
21:14:32.099769 line 9 bits.insert(0, remainder)
Modified var:.. bits = [0]
21:14:32.099769 line 7 while number:
21:14:32.099769 line 8 number, remainder = divmod(number, 2)
Modified var:.. number = 1
Modified var:.. remainder = 1
21:14:32.099769 line 9 bits.insert(0, remainder)
Modified var:.. bits = [1, 0]
21:14:32.099769 line 7 while number:
21:14:32.099769 line 8 number, remainder = divmod(number, 2)
Modified var:.. number = 0
21:14:32.099769 line 9 bits.insert(0, remainder)
Modified var:.. bits = [1, 1, 0]
21:14:32.099769 line 7 while number:
21:14:32.099769 line 10 return bits
21:14:32.099769 return 10 return bits
# Features #

View file

@ -21,38 +21,29 @@ from .tracer import Tracer
def get_write_function(output):
if output is None:
def write(s):
s += '\n'
if isinstance(s, bytes): # Python 2 compatibility
s = s.decode('utf-8')
stderr = sys.stderr
stderr.write(s)
elif isinstance(output, (pycompat.PathLike, str)):
def write(s):
s += '\n'
if isinstance(s, bytes): # Python 2 compatibility
s = s.decode('utf-8')
with open(output_path, 'a') as output_file:
output_file.write(s)
else:
assert isinstance(output, utils.WritableStream)
def write(s):
s += '\n'
if isinstance(s, bytes): # Python 2 compatibility
s = s.decode('utf-8')
output.write(s)
return write
def snoop(output=None, variables=(), depth=1):
def snoop(output=None, variables=(), depth=1, prefix=''):
write = get_write_function(output)
@decorator.decorator
def decorate(function, *args, **kwargs):
target_code_object = function.__code__
with Tracer(target_code_object=target_code_object,
write=write, variables=variables,
depth=depth):
depth=depth, prefix=prefix):
return function(*args, **kwargs)
return decorate

View file

@ -96,14 +96,22 @@ def get_source_from_frame(frame):
return source
class Tracer:
def __init__(self, target_code_object, write, variables=(), depth=1):
def __init__(self, target_code_object, write, variables=(), depth=1,
prefix=''):
self.target_code_object = target_code_object
self.write = write
self._write = write
self.variables = variables
self.frame_to_old_local_reprs = collections.defaultdict(lambda: {})
self.frame_to_local_reprs = collections.defaultdict(lambda: {})
self.depth = depth
self.prefix = prefix
assert self.depth >= 1
def write(self, s):
s = '{self.prefix}{s}\n'.format(**locals())
if isinstance(s, bytes): # Python 2 compatibility
s = s.decode()
self._write(s)
def __enter__(self):
self.original_trace_function = sys.gettrace()

View file

@ -135,3 +135,37 @@ def test_depth():
)
)
def test_method_and_prefix():
class Baz(object):
def __init__(self):
self.x = 2
@pysnooper.snoop(variables=('self.x'), prefix='ZZZ')
def square(self):
foo = 7
self.x **= 2
return self
baz = Baz()
with sys_tools.OutputCapturer(stdout=False,
stderr=True) as output_capturer:
result = baz.square()
assert result is baz
assert result.x == 4
output = output_capturer.string_io.getvalue()
assert_output(
output,
(
VariableEntry(),
CallEntry(),
LineEntry('foo = 7'),
VariableEntry('foo', '7'),
LineEntry('self.x **= 2'),
LineEntry(),
ReturnEntry(),
),
prefix='ZZZ'
)

View file

@ -16,7 +16,8 @@ class _BaseEntry(pysnooper.pycompat.ABC):
class VariableEntry(_BaseEntry):
line_pattern = re.compile(
r"""^(?P<indent>(?: {4})*)(?P<stage>New|Modified|Starting) var:"""
r"""^(?P<prefix>.*?)(?P<indent>(?: {4})*)"""
r"""(?P<stage>New|Modified|Starting) var:"""
r"""\.{2,7} (?P<name>[^ ]+) = (?P<value>.+)$"""
)
def __init__(self, name=None, value=None, stage=None,
@ -62,7 +63,7 @@ class VariableEntry(_BaseEntry):
match = self.line_pattern.match(s)
if not match:
return False
indent, stage, name, value = match.groups()
_, _, stage, name, value = match.groups()
return (self._check_name(name) and self._check_value(value) and
self._check_stage(stage))
@ -79,8 +80,9 @@ class _BaseEventEntry(_BaseEntry):
re.compile(source_regex))
line_pattern = re.compile(
(r"""^(?P<indent>(?: {4})*)[0-9:.]{15} (?P<event_name>[a-z]*) +"""
r"""(?P<line_number>[0-9]*) +(?P<source>.*)$""")
(r"""^(?P<prefix>.*?)(?P<indent>(?: {4})*)[0-9:.]{15} """
r"""(?P<event_name>[a-z]*) +(?P<line_number>[0-9]*) """
r"""+(?P<source>.*)$""")
)
@caching.CachedProperty
@ -99,7 +101,7 @@ class _BaseEventEntry(_BaseEntry):
match = self.line_pattern.match(s)
if not match:
return False
indent, event_name, _, source = match.groups()
_, _, event_name, _, source = match.groups()
return event_name == self.event_name and self._check_source(source)
@ -124,13 +126,18 @@ class OutputFailure(Exception):
pass
def assert_output(output, expected_entries):
def assert_output(output, expected_entries, prefix=None):
lines = tuple(filter(None, output.split('\n')))
if len(lines) != len(expected_entries):
raise OutputFailure(
'Output has {len(lines)} lines, while we expect '
'{len(expected_entries)} lines.'.format(**locals())
)
if prefix is not None:
for line in lines:
if not line.startswith(prefix):
raise OutputFailure(line)
for expected_entry, line in zip(expected_entries, lines):
if not expected_entry.check(line):
raise OutputFailure(line)