Skip to content

Commit d3f3117

Browse files
committed
Add option to hide variables by name
1 parent 32c19bf commit d3f3117

9 files changed

+71
-23
lines changed

README.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ File demo.py, line 6, in <lambda>
5656
5757
TypeError: unsupported operand type(s) for +: 'int' and 'str'
5858
```
59-
I sometimes use this locally instead of a real debugger, but mostly it helps me sleep when my code runs somewhere where the only debug tool is a log file (though it's not a fully-grown [error monitoring system](https://sentry.io/welcome/)).
59+
I rarely use this locally instead of a real debugger, but it helps me sleep when my code runs somewhere where the only debug tool is a log file (though it's not a fully-grown [error monitoring system](https://sentry.io/welcome/)).
6060

6161
By default, it tries to be somewhat polite about screen space (showing only a handful of source lines & the function header, and only the variables _in those lines_, and only (?) 500 characters per variable). You can [configure](https://github.com/cknd/stackprinter/blob/master/stackprinter/__init__.py#L28-L137) exactly how verbose things should be.
6262

@@ -106,7 +106,19 @@ except:
106106
logger.exception('The front fell off.') # Logs a rich traceback along with the given message
107107
```
108108

109-
For all the config options [see the docstring of `format()`](https://github.com/cknd/stackprinter/blob/master/stackprinter/__init__.py#L28-L137).
109+
For all the config options [see the docstring of `format()`](https://github.com/cknd/stackprinter/blob/master/stackprinter/__init__.py#L28-L149).
110+
The same config kwargs are accepted by `format()`, `show()` and `set_excepthook()`. They allow you to tweak the formatting, hide certain variables by name, skip variables in calls within certain files, and some other stuff.
111+
112+
```python
113+
try:
114+
something()
115+
except RuntimeError as exc:
116+
stackprinter.show(exc, suppressed_vars=[r".*secret.*"],
117+
suppressed_paths=[r"lib/python.*/site-packages/boringstuff"],
118+
truncate_vals=9001)
119+
```
120+
121+
110122

111123
## Printing the current call stack
112124
To see your own thread's current call stack, call `show` or `format` anywhere outside of exception handling.

demo.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ def dangerous_function(blub):
99
somelist = [[1,2], [3,4]]
1010
anotherlist = [['5', 6]]
1111
spam = numpy.zeros((3,3))
12+
supersecret = "you haven't seen this"
1213
dangerous_function(somelist + anotherlist)
1314
except:
14-
stackprinter.show(style='plaintext', source_lines=4)
15+
stackprinter.show(style='plaintext', source_lines=4, suppressed_vars=[r".*secret.*"])
1516

demo_complex.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
stackprinter.show(style='darkbg2',
1010
line_wrap=40,
1111
reverse=False,
12-
suppressed_paths=[r"lib/python.*/site-packages/numpy"])
12+
suppressed_paths=[r"lib/python.*/site-packages/numpy"],
13+
suppressed_vars=[r".*secret.*"])

stackprinter/__init__.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def show_or_format(thing=None, *args, **kwargs):
2626

2727
@_guess_thing
2828
def format(thing=None, **kwargs):
29-
"""
29+
r"""
3030
Render the traceback of an exception or a frame's call stack
3131
3232
@@ -128,6 +128,16 @@ def format(thing=None, **kwargs):
128128
By default, this list is `[KeyboardInterrupt]`. Set to `[]`
129129
to force verbose formatting even on a keyboard interrupt.
130130
131+
suppressed_vars: list of regex patterns
132+
Don't show the content of variables whose name matches any of the given
133+
patterns.
134+
Internally, this doesn't just filter the output, but stackprinter won't
135+
even try to access these values at all. So this can also be used as a
136+
workaround for rare issues around dynamic attribute lookups.
137+
138+
Example:
139+
`suppressed_vars=[r".*password.*", r"certainobject\.certainproperty"]`
140+
131141
reverse: bool
132142
List the innermost frame first.
133143

stackprinter/extraction.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import inspect
33
from collections import OrderedDict, namedtuple
44
from stackprinter.source_inspection import annotate
5+
from stackprinter.utils import match
56

67
NON_FUNCTION_SCOPES = ['<module>', '<lambda>', '<listcomp>']
78

@@ -16,7 +17,7 @@ def __str__(self):
1617
(self.filename, self.lineno, self.function))
1718

1819

19-
def get_info(tb_or_frame, lineno=None):
20+
def get_info(tb_or_frame, lineno=None, suppressed_vars=[]):
2021
"""
2122
Get a frame representation that's easy to format
2223
@@ -101,7 +102,7 @@ def get_info(tb_or_frame, lineno=None):
101102
head_lns = []
102103

103104
names = name2lines.keys()
104-
assignments = get_vars(names, frame.f_locals, frame.f_globals)
105+
assignments = get_vars(names, frame.f_locals, frame.f_globals, suppressed_vars)
105106

106107
finfo = FrameInfo(filename, function, lineno, source_map, head_lns,
107108
line2names, name2lines, assignments)
@@ -139,15 +140,18 @@ def get_source(frame):
139140
return lines, startline
140141

141142

142-
def get_vars(names, loc, glob):
143+
def get_vars(names, loc, glob, suppressed_vars):
143144
assignments = []
144145
for name in names:
145-
try:
146-
val = lookup(name, loc, glob)
147-
except LookupError:
148-
pass
146+
if match(name, suppressed_vars):
147+
assignments.append((name, CensoredVariable()))
149148
else:
150-
assignments.append((name, val))
149+
try:
150+
val = lookup(name, loc, glob)
151+
except LookupError:
152+
pass
153+
else:
154+
assignments.append((name, val))
151155
return OrderedDict(assignments)
152156

153157

@@ -174,6 +178,10 @@ def lookup(name, scopeA, scopeB):
174178
return val
175179

176180

181+
class CensoredVariable():
182+
def __repr__(self):
183+
return "*****"
184+
177185
class UnresolvedAttribute():
178186
"""
179187
Container value for failed dot attribute lookups

stackprinter/formatting.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def format_summary(frames, style='plaintext', source_lines=1, reverse=False,
3939

4040
def format_stack(frames, style='plaintext', source_lines=5,
4141
show_signature=True, show_vals='like_source',
42-
truncate_vals=500, line_wrap=60, reverse=False, suppressed_paths=None):
42+
truncate_vals=500, line_wrap=60, reverse=False,
43+
suppressed_paths=None, suppressed_vars=[]):
4344
"""
4445
Render a list of frames (or FrameInfo tuples)
4546
@@ -60,20 +61,22 @@ def format_stack(frames, style='plaintext', source_lines=5,
6061
show_vals=show_vals,
6162
truncate_vals=truncate_vals,
6263
line_wrap=line_wrap,
63-
suppressed_paths=suppressed_paths)
64+
suppressed_paths=suppressed_paths,
65+
suppressed_vars=suppressed_vars)
6466

6567
verbose_formatter = get_formatter(style=style,
6668
source_lines=source_lines,
6769
show_signature=show_signature,
6870
show_vals=show_vals,
6971
truncate_vals=truncate_vals,
7072
line_wrap=line_wrap,
71-
suppressed_paths=suppressed_paths)
73+
suppressed_paths=suppressed_paths,
74+
suppressed_vars=suppressed_vars)
7275

7376
frame_msgs = []
7477
parent_is_boring = True
7578
for frame in frames:
76-
fi = ex.get_info(frame)
79+
fi = ex.get_info(frame, suppressed_vars=suppressed_vars)
7780
is_boring = match(fi.filename, suppressed_paths)
7881
if is_boring:
7982
if parent_is_boring:
@@ -111,7 +114,7 @@ def format_stack_from_frame(fr, add_summary=False, **kwargs):
111114

112115
def format_exc_info(etype, evalue, tb, style='plaintext', add_summary='auto',
113116
reverse=False, suppressed_exceptions=[KeyboardInterrupt],
114-
**kwargs):
117+
suppressed_vars=[], **kwargs):
115118
"""
116119
Format an exception traceback, including the exception message
117120
@@ -149,6 +152,7 @@ def format_exc_info(etype, evalue, tb, style='plaintext', add_summary='auto',
149152
style=style,
150153
add_summary=add_summary,
151154
reverse=reverse,
155+
suppressed_vars=suppressed_vars,
152156
**kwargs)
153157

154158
if style == 'plaintext':
@@ -161,7 +165,8 @@ def format_exc_info(etype, evalue, tb, style='plaintext', add_summary='auto',
161165
# Now, actually do some formatting:
162166
parts = []
163167
if tb:
164-
frameinfos = [ex.get_info(tb_) for tb_ in _walk_traceback(tb)]
168+
frameinfos = [ex.get_info(tb_, suppressed_vars=suppressed_vars)
169+
for tb_ in _walk_traceback(tb)]
165170
if (suppressed_exceptions and
166171
issubclass(etype, tuple(suppressed_exceptions))):
167172
summary = format_summary(frameinfos, style=style,

stackprinter/frame_formatting.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class FrameFormatter():
2424
def __init__(self, source_lines=5, source_lines_after=1,
2525
show_signature=True, show_vals='like_source',
2626
truncate_vals=500, line_wrap: int = 60,
27-
suppressed_paths=None):
27+
suppressed_paths=None, suppressed_vars=None):
2828
"""
2929
Formatter for single frames.
3030
@@ -89,6 +89,7 @@ def __init__(self, source_lines=5, source_lines_after=1,
8989
self.truncate_vals = truncate_vals
9090
self.line_wrap = line_wrap
9191
self.suppressed_paths = suppressed_paths
92+
self.suppressed_vars = suppressed_vars
9293

9394
def __call__(self, frame, lineno=None):
9495
"""
@@ -123,7 +124,7 @@ def __call__(self, frame, lineno=None):
123124
"%s. Got %r" % (accepted_types, frame))
124125

125126
try:
126-
finfo = ex.get_info(frame, lineno)
127+
finfo = ex.get_info(frame, lineno, self.suppressed_vars)
127128

128129
return self._format_frame(finfo)
129130
except Exception as exc:

tests/source.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def spam_spam_spam(val):
6868
bla.nonexistant_attribute
6969
except:
7070
pass
71-
71+
supersecret = "you haven't seen this"
7272
boing = np.\
7373
random.rand(*bla.T.\
7474
T.T)

tests/test_frame_inspection.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55
@pytest.fixture
66
def frameinfo():
77
somevalue = 'spam'
8+
9+
supersecretthings = "gaaaah"
10+
class Blob():
11+
pass
12+
someobject = Blob()
13+
someobject.secretattribute = "uarrgh"
14+
815
fr = sys._getframe()
9-
return ex.get_info(fr)
16+
hidden = [r".*secret.*", r"someobject\..*attribute"]
17+
return ex.get_info(fr, suppressed_vars=hidden)
1018

1119
def test_frameinfo(frameinfo):
1220
fi = frameinfo
1321
assert fi.filename.endswith('test_frame_inspection.py')
1422
assert fi.function == 'frameinfo'
1523
assert fi.assignments['somevalue'] == 'spam'
24+
assert isinstance(fi.assignments['supersecretthings'], ex.CensoredVariable)
25+
assert isinstance(fi.assignments['someobject.secretattribute'], ex.CensoredVariable)

0 commit comments

Comments
 (0)