Skip to content

Commit 855598e

Browse files
committed
Add CallRecordingPanel
1 parent d2cb9b1 commit 855598e

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

debug_toolbar/panels/__init__.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from debug_toolbar import settings as dt_settings
88
from debug_toolbar.utils import get_name_from_obj
9+
from debug_toolbar.utils.function_wrapper import FunctionWrapper
10+
from debug_toolbar.utils.patch_context import PatchContext
911

1012

1113
class Panel(object):
@@ -171,3 +173,33 @@ class DebugPanel(Panel):
171173
def __init__(self, *args, **kwargs):
172174
warnings.warn("DebugPanel was renamed to Panel.", DeprecationWarning)
173175
super(DebugPanel, self).__init__(*args, **kwargs)
176+
177+
178+
class CallRecordingPanel(Panel):
179+
def __init__(self, *args, **kwargs):
180+
super(CallRecordingPanel, self).__init__(*args, **kwargs)
181+
self.calls = []
182+
self._context = []
183+
184+
for context in self.get_context():
185+
self.add_context(context)
186+
187+
def get_context(self):
188+
"""
189+
>>> def get_context(self):
190+
>>> return [
191+
>>> PatchContext('foo.bar', FunctionWrapper(self.calls))
192+
>>> ]
193+
"""
194+
raise NotImplementedError
195+
196+
def add_context(self, context):
197+
self._context.append(context)
198+
199+
def enable_instrumentation(self):
200+
for context in self._context:
201+
context.patch()
202+
203+
def disable_instrumentation(self):
204+
for context in self._context:
205+
context.unpatch()
File renamed without changes.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import absolute_import
2+
3+
from time import time
4+
5+
6+
class FunctionWrapper(object):
7+
def __init__(self, data):
8+
self.data = data
9+
10+
def __call__(self, func, *args, **kwargs):
11+
__traceback_hide__ = True # NOQA
12+
13+
start = time()
14+
try:
15+
return func(*args, **kwargs)
16+
finally:
17+
end = time()
18+
19+
if getattr(func, 'im_class', None):
20+
arg_str = repr(args[1:])
21+
else:
22+
arg_str = repr(args)
23+
24+
data = {
25+
'type': self.name or func.__name__,
26+
'name': func.__name__,
27+
'args': arg_str,
28+
'kwargs': repr(kwargs),
29+
'start': start,
30+
'end': end,
31+
}
32+
33+
self.record(data)
34+
35+
def record(self, data):
36+
self.data.append(data)

debug_toolbar/utils/patch_context.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from __future__ import absolute_import
2+
3+
4+
def _dot_lookup(thing, comp, import_path):
5+
try:
6+
return getattr(thing, comp)
7+
except AttributeError:
8+
__import__(import_path)
9+
return getattr(thing, comp)
10+
11+
12+
def import_string(target):
13+
components = target.split('.')
14+
import_path = components.pop(0)
15+
thing = __import__(import_path)
16+
17+
for comp in components:
18+
import_path += ".%s" % comp
19+
thing = _dot_lookup(thing, comp, import_path)
20+
return thing
21+
22+
23+
class PatchContext(object):
24+
def __init__(self, target, callback):
25+
target, attr = target.rsplit('.', 1)
26+
target = import_string(target)
27+
self.func = getattr(target, attr)
28+
self.target = target
29+
self.attr = attr
30+
self.callback = callback
31+
32+
def __enter__(self):
33+
self.patch()
34+
return self
35+
36+
def __exit__(self, exc_type, exc_value, traceback):
37+
self.unpatch()
38+
39+
def patch(self):
40+
func = getattr(self.target, self.attr)
41+
42+
def wrapped(*args, **kwargs):
43+
__traceback_hide__ = True # NOQA
44+
return self.callback(self.func, *args, **kwargs)
45+
46+
wrapped.__name__ = func.__name__
47+
if hasattr(func, '__doc__'):
48+
wrapped.__doc__ = func.__doc__
49+
if hasattr(func, '__module__'):
50+
wrapped.__module__ = func.__module__
51+
52+
setattr(self.target, self.attr, wrapped)
53+
54+
def unpatch(self):
55+
setattr(self.target, self.attr, self.func)

0 commit comments

Comments
 (0)