diff --git a/README.md b/README.md index 9b43596..64f2c1a 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Start all snoop lines with a prefix, to grep for them easily: @pysnooper.snoop(prefix='ZZZ ') ``` -On multi-threaded apps identify which thread are snooped in output:: +On multi-threaded apps identify which thread are snooped in output: ```python @pysnooper.snoop(thread_info=True) @@ -151,6 +151,24 @@ On multi-threaded apps identify which thread are snooped in output:: PySnooper supports decorating generators. +You can also customize the repr of an object: + +```python +def large(l): + return isinstance(l, list) and len(l) > 5 + +def print_list_size(l): + return 'list(size={})'.format(len(l)) + +@pysnooper.snoop(custom_repr=((large, print_list_size),)) +def sum_to_x(x): + l = list(range(x)) + return sum(l) + +sum_to_x(10000) +``` + +You will get `l = list(size=10000)` for the list # Installation # diff --git a/pysnooper/tracer.py b/pysnooper/tracer.py index 480d60a..a478d00 100644 --- a/pysnooper/tracer.py +++ b/pysnooper/tracer.py @@ -21,11 +21,11 @@ if pycompat.PY2: ipython_filename_pattern = re.compile('^$') -def get_local_reprs(frame, watch=()): +def get_local_reprs(frame, watch=(), custom_repr=()): code = frame.f_code vars_order = code.co_varnames + code.co_cellvars + code.co_freevars + tuple(frame.f_locals.keys()) - result_items = [(key, utils.get_shortish_repr(value)) for key, value in frame.f_locals.items()] + result_items = [(key, utils.get_shortish_repr(value, custom_repr=custom_repr)) for key, value in frame.f_locals.items()] result_items.sort(key=lambda key_value: vars_order.index(key_value[0])) result = collections.OrderedDict(result_items) @@ -189,6 +189,7 @@ class Tracer: prefix='', overwrite=False, thread_info=False, + custom_repr=(), ): self._write = get_write_function(output, overwrite) @@ -208,6 +209,7 @@ class Tracer: self.target_codes = set() self.target_frames = set() self.thread_local = threading.local() + self.custom_repr = custom_repr def __call__(self, function): self.target_codes.add(function.__code__) @@ -310,7 +312,7 @@ class Tracer: # # old_local_reprs = self.frame_to_local_reprs.get(frame, {}) self.frame_to_local_reprs[frame] = local_reprs = \ - get_local_reprs(frame, watch=self.watch) + get_local_reprs(frame, watch=self.watch, custom_repr=self.custom_repr) newish_string = ('Starting var:.. ' if event == 'call' else 'New var:....... ') @@ -383,7 +385,7 @@ class Tracer: thread_global.depth -= 1 if not ended_by_exception: - return_value_repr = utils.get_shortish_repr(arg) + return_value_repr = utils.get_shortish_repr(arg, custom_repr=self.custom_repr) self.write('{indent}Return value:.. {return_value_repr}'. format(**locals())) diff --git a/pysnooper/utils.py b/pysnooper/utils.py index 199c0e9..a1a6f80 100644 --- a/pysnooper/utils.py +++ b/pysnooper/utils.py @@ -49,9 +49,14 @@ def shitcode(s): ) -def get_shortish_repr(item): +def get_shortish_repr(item, custom_repr=()): try: - r = repr(item) + for condition, action in custom_repr: + if condition(item): + r = action(item) + break + else: + r = repr(item) except Exception: r = 'REPR FAILED' r = r.replace('\r', '').replace('\n', '') diff --git a/tests/test_pysnooper.py b/tests/test_pysnooper.py index fcb33b1..e4e74e9 100644 --- a/tests/test_pysnooper.py +++ b/tests/test_pysnooper.py @@ -1131,4 +1131,32 @@ def test_generator(): ) +def test_custom_repr(): + string_io = io.StringIO() + def large(l): + return isinstance(l, list) and len(l) > 5 + + def print_list_size(l): + return 'list(size={})'.format(len(l)) + + @pysnooper.snoop(string_io, custom_repr=((large, print_list_size),)) + def sum_to_x(x): + l = list(range(x)) + return sum(l) + + result = sum_to_x(10000) + + output = string_io.getvalue() + assert_output( + output, + ( + VariableEntry('x', '10000'), + CallEntry(), + LineEntry(), + VariableEntry('l', 'list(size=10000)'), + LineEntry(), + ReturnEntry(), + ReturnValueEntry('49995000'), + ) + )