Skip to content

Commit

Permalink
Add pdb support.
Browse files Browse the repository at this point in the history
  • Loading branch information
gaogaotiantian committed Dec 9, 2020
1 parent 0a874e7 commit bbad945
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 55 deletions.
69 changes: 19 additions & 50 deletions README.md
Expand Up @@ -27,7 +27,7 @@ a = 1
will generate

```
<module> (my_script.py:5):
> <module> (my_script.py:5):
a:
0
->
Expand All @@ -41,22 +41,8 @@ from watchpoints import watch

a = []
watch(a)
a.append(1)
a = {}
```

```
<module> (my_script.py:5):
a:
[]
->
[1]
<module> (my_script.py:6):
a:
[1]
->
{}
a.append(1) # Trigger
a = {} # Trigger
```

Even better, it can track the changes of the object after the changes of the variable
Expand All @@ -66,22 +52,8 @@ from watchpoints import watch

a = []
watch(a)
a = {}
a["a"] = 2
```

```
<module> (my_script.py:5):
a:
[]
->
{}
<module> (my_script).py:6):
a:
{}
->
{'a': 2}
a = {} # Trigger
a["a"] = 2 # Trigger
```

Without doubts, it works whenever the object is changed, even if it's not in the same scope
Expand All @@ -98,7 +70,7 @@ func(a)
```

```
func (my_script.py:4):
> func (my_script.py:4):
a:
{}
->
Expand All @@ -117,22 +89,8 @@ class MyObj:
obj = MyObj()
d = {"a": 0}
watch(obj.a, d["a"]) # Yes you can do this
obj.a = 1
d["a"] = 1
```

```
<module> (my_script.py:10):
obj.a:
0
->
1
<module> (my_script.py:11):
d["a"]:
0
->
1
obj.a = 1 # Trigger
d["a"] = 1 # Trigger
```

**watchpoints will try to guess what you want to monitor, and monitor it as you expect**(well most of the time)
Expand Down Expand Up @@ -199,6 +157,17 @@ Use ```restore()``` to restore the default callback
watch.restore()
```

### Integrating with pdb

watchpoints can be used with pdb with ease. You can trigger pdb just like using ```breakpoint()``` when
your monitored variable is changed. Simply do

```python
watch.config(pdb=True)
```

When you are in pdb, use ```q(uit)``` command to exit pdb, and the next change on the variable will trigger the pdb again.

## Bugs/Requests

Please send bug reports and feature requests through [github issue tracker](https://github.com/gaogaotiantian/watchpoints/issues).
Expand Down
34 changes: 30 additions & 4 deletions src/watchpoints/watch.py
Expand Up @@ -2,7 +2,9 @@
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


from bdb import BdbQuit
import inspect
import pdb
import sys
from .util import getargnodes
from .watch_element import WatchElement
Expand All @@ -15,6 +17,8 @@ def __init__(self):
self.tracefunc_stack = []
self.enable = False
self._callback = self._default_callback
self.pdb = None
self.pdb_enable = False

def __call__(self, *args, **kwargs):
frame = inspect.currentframe().f_back
Expand All @@ -40,18 +44,25 @@ def start_trace(self, frame):
if not self.enable:
self.enable = True
self.tracefunc_stack.append(sys.gettrace())
sys.settrace(self.tracefunc)
frame.f_trace = self.tracefunc
self._prev_funcname = frame.f_code.co_name
self._prev_filename = frame.f_code.co_filename
self._prev_lineno = frame.f_lineno
while frame:
frame.f_trace = self.tracefunc
frame = frame.f_back

sys.settrace(self.tracefunc)

def stop_trace(self, frame):
if self.enable:
self.enable = False
tf = self.tracefunc_stack.pop()
sys.settrace(tf)
frame.f_trace = tf
self.enable = False
while frame:
frame.f_trace = tf
frame = frame.f_back

sys.settrace(tf)

def unwatch(self, *args):
frame = inspect.currentframe().f_back
Expand All @@ -69,6 +80,10 @@ def config(self, **kwargs):
if "callback" in kwargs:
self._callback = kwargs["callback"]

if "pdb" in kwargs:
self.pdb = pdb.Pdb()
self.pdb.reset()

def restore(self):
self._callback = self._default_callback

Expand All @@ -77,6 +92,8 @@ def tracefunc(self, frame, event, arg):
for elem in self.watch_list:
changed, exist = elem.changed(frame)
if changed:
if self.pdb:
self.pdb_enable = True
if elem._callback:
elem._callback(frame, elem, (self._prev_funcname, self._prev_filename, self._prev_lineno))
else:
Expand All @@ -92,6 +109,15 @@ def tracefunc(self, frame, event, arg):
self._prev_filename = frame.f_code.co_filename
self._prev_lineno = frame.f_lineno

if self.pdb_enable:
try:
self.pdb.trace_dispatch(frame, event, arg)
except BdbQuit:
self.pdb_enable = False
self.pdb.reset()
self.stop_trace(frame)
self.start_trace(frame)

return self.tracefunc

def _default_callback(self, frame, elem, exec_info):
Expand Down
2 changes: 1 addition & 1 deletion src/watchpoints/watch_print.py
Expand Up @@ -26,7 +26,7 @@ def __call__(self, frame, elem, exec_info):
p("")

def _file_string(self, exec_info):
return f"{exec_info[0]} ({exec_info[1]}:{exec_info[2]}):"
return f"> {exec_info[0]} ({exec_info[1]}:{exec_info[2]}):"

def printer(self, obj):
if type(obj) is str:
Expand Down

0 comments on commit bbad945

Please sign in to comment.