mirror of
https://github.com/cool-RR/PySnooper.git
synced 2026-01-23 10:15:11 +00:00
Show source path, especially when multiple files
This commit is contained in:
parent
7392765ada
commit
297b3cd8d7
13 changed files with 161 additions and 20 deletions
|
|
@ -37,6 +37,7 @@ number_to_bits(6)
|
|||
The output to stderr is:
|
||||
|
||||
```
|
||||
Source path:... /my_code/foo.py
|
||||
Starting var:.. number = 6
|
||||
15:29:11.327032 call 4 def number_to_bits(number):
|
||||
15:29:11.327032 line 5 if number:
|
||||
|
|
|
|||
|
|
@ -40,16 +40,16 @@ class UnavailableSource(object):
|
|||
return u'SOURCE IS UNAVAILABLE'
|
||||
|
||||
|
||||
source_cache = {}
|
||||
source_and_path_cache = {}
|
||||
|
||||
|
||||
def get_source_from_frame(frame):
|
||||
def get_path_and_source_from_frame(frame):
|
||||
globs = frame.f_globals or {}
|
||||
module_name = globs.get('__name__')
|
||||
file_name = frame.f_code.co_filename
|
||||
cache_key = (module_name, file_name)
|
||||
try:
|
||||
return source_cache[cache_key]
|
||||
return source_and_path_cache[cache_key]
|
||||
except KeyError:
|
||||
pass
|
||||
loader = globs.get('__loader__')
|
||||
|
|
@ -98,8 +98,9 @@ def get_source_from_frame(frame):
|
|||
source = [pycompat.text_type(sline, encoding, 'replace') for sline in
|
||||
source]
|
||||
|
||||
source_cache[cache_key] = source
|
||||
return source
|
||||
result = (file_name, source)
|
||||
source_and_path_cache[cache_key] = result
|
||||
return result
|
||||
|
||||
|
||||
def get_write_function(output, overwrite):
|
||||
|
|
@ -218,6 +219,7 @@ class Tracer:
|
|||
pycompat.collections_abc.Iterable) for x in custom_repr):
|
||||
custom_repr = (custom_repr,)
|
||||
self.custom_repr = custom_repr
|
||||
self.last_source_path = None
|
||||
|
||||
def __call__(self, function):
|
||||
if DISABLED:
|
||||
|
|
@ -323,6 +325,21 @@ class Tracer:
|
|||
# #
|
||||
### Finished checking whether we should trace this line. ##############
|
||||
|
||||
now_string = datetime_module.datetime.now().time().isoformat()
|
||||
line_no = frame.f_lineno
|
||||
source_path, source = get_path_and_source_from_frame(frame)
|
||||
if self.last_source_path != source_path:
|
||||
self.write(u'{indent}Source path:... {source_path}'.
|
||||
format(**locals()))
|
||||
self.last_source_path = source_path
|
||||
source_line = source[line_no - 1]
|
||||
thread_info = ""
|
||||
if self.thread_info:
|
||||
current_thread = threading.current_thread()
|
||||
thread_info = "{ident}-{name} ".format(
|
||||
ident=current_thread.ident, name=current_thread.getName())
|
||||
thread_info = self.set_thread_info_padding(thread_info)
|
||||
|
||||
### Reporting newish and modified variables: ##########################
|
||||
# #
|
||||
old_local_reprs = self.frame_to_local_reprs.get(frame, {})
|
||||
|
|
@ -343,15 +360,6 @@ class Tracer:
|
|||
# #
|
||||
### Finished newish and modified variables. ###########################
|
||||
|
||||
now_string = datetime_module.datetime.now().time().isoformat()
|
||||
line_no = frame.f_lineno
|
||||
source_line = get_source_from_frame(frame)[line_no - 1]
|
||||
thread_info = ""
|
||||
if self.thread_info:
|
||||
current_thread = threading.current_thread()
|
||||
thread_info = "{ident}-{name} ".format(
|
||||
ident=current_thread.ident, name=current_thread.getName())
|
||||
thread_info = self.set_thread_info_padding(thread_info)
|
||||
|
||||
### Dealing with misplaced function definition: #######################
|
||||
# #
|
||||
|
|
@ -360,8 +368,7 @@ class Tracer:
|
|||
# function definition is found.
|
||||
for candidate_line_no in itertools.count(line_no):
|
||||
try:
|
||||
candidate_source_line = \
|
||||
get_source_from_frame(frame)[candidate_line_no - 1]
|
||||
candidate_source_line = source[candidate_line_no - 1]
|
||||
except IndexError:
|
||||
# End of source file reached without finding a function
|
||||
# definition. Fall back to original source line.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ def main():
|
|||
|
||||
|
||||
expected_output = '''
|
||||
Source path:... Whatever
|
||||
12:18:08.017782 call 17 def main():
|
||||
12:18:08.018142 line 18 try:
|
||||
12:18:08.018181 line 19 bar()
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ def f5():
|
|||
|
||||
|
||||
expected_output = '''
|
||||
Source path:... Whatever
|
||||
21:10:42.298924 call 5 def main():
|
||||
21:10:42.299158 line 6 f2()
|
||||
21:10:42.299205 call 9 def f2():
|
||||
21:10:42.299246 line 10 f3()
|
||||
Source path:... Whatever
|
||||
21:10:42.299305 call 18 def f4():
|
||||
21:10:42.299348 line 19 f5()
|
||||
21:10:42.299386 call 22 def f5():
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ def mul(a, b):
|
|||
|
||||
def main():
|
||||
factorial(4)
|
||||
|
||||
|
||||
expected_output = '''
|
||||
Source path:... Whatever
|
||||
Starting var:.. x = 4
|
||||
20:28:17.875295 call 5 def factorial(x):
|
||||
20:28:17.875509 line 6 if x <= 1:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from pysnooper import pycompat
|
|||
from pysnooper.variables import needs_parentheses
|
||||
from .utils import (assert_output, assert_sample_output, VariableEntry,
|
||||
CallEntry, LineEntry, ReturnEntry, OpcodeEntry,
|
||||
ReturnValueEntry, ExceptionEntry)
|
||||
ReturnValueEntry, ExceptionEntry, SourcePathEntry)
|
||||
from . import mini_toolbox
|
||||
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ def test_chinese():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
CallEntry(),
|
||||
LineEntry(),
|
||||
VariableEntry('a'),
|
||||
|
|
|
|||
0
tests/test_multiple_files/__init__.py
Normal file
0
tests/test_multiple_files/__init__.py
Normal file
0
tests/test_multiple_files/multiple_files/__init__.py
Normal file
0
tests/test_multiple_files/multiple_files/__init__.py
Normal file
6
tests/test_multiple_files/multiple_files/bar.py
Normal file
6
tests/test_multiple_files/multiple_files/bar.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Copyright 2019 Ram Rachum and collaborators.
|
||||
# This program is distributed under the MIT license.
|
||||
|
||||
def bar_function(y):
|
||||
x = 7 * y
|
||||
return x
|
||||
11
tests/test_multiple_files/multiple_files/foo.py
Normal file
11
tests/test_multiple_files/multiple_files/foo.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Copyright 2019 Ram Rachum and collaborators.
|
||||
# This program is distributed under the MIT license.
|
||||
|
||||
import pysnooper
|
||||
|
||||
from .bar import bar_function
|
||||
|
||||
@pysnooper.snoop(depth=2)
|
||||
def foo_function():
|
||||
z = bar_function(3)
|
||||
return z
|
||||
51
tests/test_multiple_files/test_multiple_files.py
Normal file
51
tests/test_multiple_files/test_multiple_files.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright 2019 Ram Rachum and collaborators.
|
||||
# This program is distributed under the MIT license.
|
||||
|
||||
import io
|
||||
import textwrap
|
||||
import threading
|
||||
import types
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pysnooper.utils import truncate
|
||||
import pytest
|
||||
|
||||
import pysnooper
|
||||
from pysnooper.variables import needs_parentheses
|
||||
from ..utils import (assert_output, assert_sample_output, VariableEntry,
|
||||
CallEntry, LineEntry, ReturnEntry, OpcodeEntry,
|
||||
ReturnValueEntry, ExceptionEntry, SourcePathEntry)
|
||||
from .. import mini_toolbox
|
||||
from .multiple_files import foo
|
||||
|
||||
|
||||
def test_multiple_files():
|
||||
with mini_toolbox.OutputCapturer(stdout=False,
|
||||
stderr=True) as output_capturer:
|
||||
result = foo.foo_function()
|
||||
assert result == 21
|
||||
output = output_capturer.string_io.getvalue()
|
||||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(source_path_regex=r'.*foo\.py$'),
|
||||
CallEntry(),
|
||||
LineEntry(),
|
||||
SourcePathEntry(source_path_regex=r'.*bar\.py$'),
|
||||
VariableEntry(),
|
||||
CallEntry(),
|
||||
LineEntry(),
|
||||
VariableEntry(),
|
||||
LineEntry(),
|
||||
ReturnEntry(),
|
||||
ReturnValueEntry(),
|
||||
SourcePathEntry(source_path_regex=r'.*foo\.py$'),
|
||||
VariableEntry(),
|
||||
LineEntry(),
|
||||
ReturnEntry(),
|
||||
ReturnValueEntry(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ import pysnooper
|
|||
from pysnooper.variables import needs_parentheses
|
||||
from .utils import (assert_output, assert_sample_output, VariableEntry,
|
||||
CallEntry, LineEntry, ReturnEntry, OpcodeEntry,
|
||||
ReturnValueEntry, ExceptionEntry)
|
||||
ReturnValueEntry, ExceptionEntry, SourcePathEntry)
|
||||
from . import mini_toolbox
|
||||
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ def test_string_io():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry('x = 7'),
|
||||
|
|
@ -63,6 +64,7 @@ def test_thread_info():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry('x = 7'),
|
||||
|
|
@ -105,6 +107,7 @@ def test_multi_thread_info():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
|
|
@ -174,6 +177,7 @@ def test_callable():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry('x = 7'),
|
||||
|
|
@ -215,6 +219,7 @@ def test_watch():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('Foo'),
|
||||
VariableEntry('io.__name__', "'io'"),
|
||||
CallEntry('def my_function():'),
|
||||
|
|
@ -261,6 +266,7 @@ def test_watch_explode():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('Foo'),
|
||||
CallEntry('def my_function():'),
|
||||
LineEntry(),
|
||||
|
|
@ -315,6 +321,7 @@ def test_variables_classes():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('WithSlots'),
|
||||
CallEntry('def my_function():'),
|
||||
LineEntry(),
|
||||
|
|
@ -360,6 +367,7 @@ def test_single_watch_no_comma():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('Foo'),
|
||||
CallEntry('def my_function():'),
|
||||
LineEntry('foo = Foo()'),
|
||||
|
|
@ -392,6 +400,7 @@ def test_long_variable():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
CallEntry('def my_function():'),
|
||||
LineEntry('foo = list(range(1000))'),
|
||||
VariableEntry('foo', value_regex=regex),
|
||||
|
|
@ -419,6 +428,7 @@ def test_repr_exception():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('Bad'),
|
||||
CallEntry('def my_function():'),
|
||||
LineEntry('bad = Bad()'),
|
||||
|
|
@ -455,6 +465,7 @@ def test_depth():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry(),
|
||||
VariableEntry(),
|
||||
CallEntry('def f1(x1):'),
|
||||
|
|
@ -510,6 +521,7 @@ def test_method_and_prefix():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(prefix='ZZZ'),
|
||||
VariableEntry('self', prefix='ZZZ'),
|
||||
VariableEntry('self.x', '2', prefix='ZZZ'),
|
||||
CallEntry('def square(self):', prefix='ZZZ'),
|
||||
|
|
@ -542,6 +554,7 @@ def test_file_output():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('_foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(_foo):'),
|
||||
LineEntry('x = 7'),
|
||||
|
|
@ -577,6 +590,7 @@ def test_confusing_decorator_lines():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry(),
|
||||
|
|
@ -606,6 +620,7 @@ def test_lambda():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('x', '7'),
|
||||
CallEntry(source_regex='^my_function = pysnooper.*'),
|
||||
LineEntry(source_regex='^my_function = pysnooper.*'),
|
||||
|
|
@ -638,6 +653,7 @@ def test_unavailable_source():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry(stage='starting'),
|
||||
CallEntry('SOURCE IS UNAVAILABLE'),
|
||||
LineEntry('SOURCE IS UNAVAILABLE'),
|
||||
|
|
@ -666,6 +682,7 @@ def test_no_overwrite_by_default():
|
|||
assert_output(
|
||||
shortened_output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry('x = 7'),
|
||||
|
|
@ -698,6 +715,7 @@ def test_overwrite():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry('x = 7'),
|
||||
|
|
@ -793,6 +811,7 @@ def test_with_block():
|
|||
output,
|
||||
(
|
||||
# In first with
|
||||
SourcePathEntry(),
|
||||
VariableEntry('x', '2'),
|
||||
VariableEntry('bar1'),
|
||||
VariableEntry('bar2'),
|
||||
|
|
@ -897,6 +916,7 @@ def test_with_block_depth():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry(),
|
||||
VariableEntry(),
|
||||
VariableEntry(),
|
||||
|
|
@ -948,6 +968,7 @@ def test_cellvars():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry(),
|
||||
VariableEntry(),
|
||||
VariableEntry(),
|
||||
|
|
@ -1002,6 +1023,7 @@ def test_var_order():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry(),
|
||||
VariableEntry(),
|
||||
|
||||
|
|
@ -1087,6 +1109,7 @@ def test_generator():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('x1', '0'),
|
||||
VariableEntry(),
|
||||
CallEntry(),
|
||||
|
|
@ -1162,6 +1185,7 @@ def test_custom_repr():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('x', '10000'),
|
||||
CallEntry(),
|
||||
LineEntry(),
|
||||
|
|
@ -1188,6 +1212,7 @@ def test_custom_repr_single():
|
|||
assert_output(
|
||||
output,
|
||||
(
|
||||
SourcePathEntry(),
|
||||
VariableEntry('x', '10000'),
|
||||
CallEntry(),
|
||||
LineEntry(),
|
||||
|
|
|
|||
|
|
@ -167,6 +167,31 @@ class ReturnValueEntry(_BaseValueEntry):
|
|||
else:
|
||||
return True
|
||||
|
||||
class SourcePathEntry(_BaseValueEntry):
|
||||
def __init__(self, source_path=None, source_path_regex=None, prefix=''):
|
||||
_BaseValueEntry.__init__(self, prefix=prefix)
|
||||
if source_path is not None:
|
||||
assert source_path_regex is None
|
||||
|
||||
self.source_path = source_path
|
||||
self.source_path_regex = (None if source_path_regex is None else
|
||||
re.compile(source_path_regex))
|
||||
|
||||
_preamble_pattern = re.compile(
|
||||
r"""^Source path$"""
|
||||
)
|
||||
|
||||
def _check_preamble(self, preamble):
|
||||
return bool(self._preamble_pattern.match(preamble))
|
||||
|
||||
def _check_content(self, source_path):
|
||||
if self.source_path is not None:
|
||||
return source_path == self.source_path
|
||||
elif self.source_path_regex is not None:
|
||||
return self.source_path_regex.match(source_path)
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class _BaseEventEntry(_BaseEntry):
|
||||
def __init__(self, source=None, source_regex=None, thread_info=None,
|
||||
|
|
@ -278,7 +303,15 @@ def assert_sample_output(module):
|
|||
time_pattern = re.sub(r'\d', r'\\d', time)
|
||||
|
||||
def normalise(out):
|
||||
return re.sub(time_pattern, time, out).strip()
|
||||
out = re.sub(time_pattern, time, out).strip()
|
||||
out = re.sub(
|
||||
r'^( *)Source path:\.\.\. .*$',
|
||||
r'\1Source path:... Whatever',
|
||||
out,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
output = output_capturer.string_io.getvalue()
|
||||
|
||||
|
|
@ -290,3 +323,5 @@ def assert_sample_output(module):
|
|||
except AssertionError:
|
||||
print('\n\nActual Output:\n\n' + output) # to copy paste into expected_output
|
||||
raise # show pytest diff (may need -vv flag to see in full)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue