From 28a8b2449485bfeb8741e7c232b8e1c7b8f8134d Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Sun, 21 Apr 2019 21:35:43 +0300 Subject: [PATCH] Add prefix feature --- README.md | 50 ++++++++++++++++++++--------------------- pysnooper/pysnooper.py | 13 ++--------- pysnooper/tracer.py | 12 ++++++++-- tests/test_pysnooper.py | 34 ++++++++++++++++++++++++++++ tests/utils.py | 19 +++++++++++----- 5 files changed, 84 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index bb20bf3..eafd0b5 100644 --- a/README.md +++ b/README.md @@ -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 # diff --git a/pysnooper/pysnooper.py b/pysnooper/pysnooper.py index 917fe00..73c5cb6 100644 --- a/pysnooper/pysnooper.py +++ b/pysnooper/pysnooper.py @@ -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 diff --git a/pysnooper/tracer.py b/pysnooper/tracer.py index e693ee0..c66d9f2 100644 --- a/pysnooper/tracer.py +++ b/pysnooper/tracer.py @@ -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() diff --git a/tests/test_pysnooper.py b/tests/test_pysnooper.py index 5de04aa..5c7f7b3 100644 --- a/tests/test_pysnooper.py +++ b/tests/test_pysnooper.py @@ -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' + ) diff --git a/tests/utils.py b/tests/utils.py index 638a098..6ecd24c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -16,7 +16,8 @@ class _BaseEntry(pysnooper.pycompat.ABC): class VariableEntry(_BaseEntry): line_pattern = re.compile( - r"""^(?P(?: {4})*)(?PNew|Modified|Starting) var:""" + r"""^(?P.*?)(?P(?: {4})*)""" + r"""(?PNew|Modified|Starting) var:""" r"""\.{2,7} (?P[^ ]+) = (?P.+)$""" ) 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(?: {4})*)[0-9:.]{15} (?P[a-z]*) +""" - r"""(?P[0-9]*) +(?P.*)$""") + (r"""^(?P.*?)(?P(?: {4})*)[0-9:.]{15} """ + r"""(?P[a-z]*) +(?P[0-9]*) """ + r"""+(?P.*)$""") ) @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)