Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New action - UI - to provide interactive visualizer for tracked result #124

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,24 @@ In a terminal it would look like:

-----

Action ``UI`` provide interactive visualizer for tracked result:

.. sourcecode:: python

import hunter
hunter.trace(action=hunter.UI)

import os
os.path.join('a', 'b')
hunter.stop()
hunter.UI.start()

That would result in:

.. image:: https://raw.githubusercontent.com/seniorsolt/python-hunter/master/docs/UI.png

-----

You can give it a tree-like configuration where you can optionally configure specific actions for parts of the
tree (like dumping variables or a pdb set_trace):

Expand Down
Binary file added docs/UI.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 83 additions & 1 deletion src/hunter/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
import collections
import opcode
import os
import sys
import threading
from collections import defaultdict
from itertools import islice
from os import getpid
from typing import ClassVar

from PyQt5.QtWidgets import QApplication

from . import config
from .util import BUILTIN_SYMBOLS
from .util import BUILTIN_SYMBOLS, TracebackVisualizer
from .util import CALL_COLORS
from .util import CODE_COLORS
from .util import MISSING
Expand All @@ -33,6 +36,7 @@
'CodePrinter',
'CallPrinter',
'VarsPrinter',
'UI'
]

BUILTIN_REPR_FUNCS = {'repr': repr, 'safe_repr': safe_repr}
Expand Down Expand Up @@ -871,3 +875,81 @@ def __call__(self, event):
thread_prefix,
filename_prefix,
)


class UI(ColorStreamAction):
"""track events and show the result with UI. To open one should use UI.start() after tracking finished"""
events = []
counter = 0

@classmethod
def cleanup(cls):
cls.locals = defaultdict(list)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.locals = defaultdict(list)

def __call__(self, event):
ident = (event.module, event.function)
thread = threading.current_thread()
stack = self.locals[thread.ident]
self.counter += 1
pid_prefix = ""
thread_prefix = thread.name
filename_prefix = f"{event.filename}:{event.lineno}"

if event.kind == 'call':
code = event.code
stack.append(ident)
args = {var_display: self.try_repr(event.locals.get(var_lookup, None))
for _, var_lookup, var_display in get_arguments(code)}
initial_args = {var_display: event.locals.get(var_lookup, None)
for _, var_lookup, var_display in get_arguments(code)}

event_data = {
"pid_prefix": pid_prefix,
"thread_prefix": thread_prefix,
"filename_prefix": filename_prefix,
"kind": event.kind,
"depth": len(stack) - 1,
"function": event.function,
"args": {"repr_args": f"'{args}'", "initial_args": initial_args},
"color": self.event_colors.get(event.kind, ""),
"counter": self.counter
}

elif event.kind in ('return', 'exception'):
if stack and stack[-1] == ident:
stack.pop()
event_data = {
"pid_prefix": pid_prefix,
"thread_prefix": thread_prefix,
"filename_prefix": filename_prefix,
"kind": event.kind,
"depth": len(stack),
"function": event.function,
"args": {"repr_args": self.try_repr(event.arg), "initial_args": event.arg},
"color": self.event_colors.get(event.kind, ""),
"counter": self.counter
}
else:
event_data = {
"pid_prefix": pid_prefix,
"thread_prefix": thread_prefix,
"filename_prefix": filename_prefix,
"kind": event.kind,
"depth": len(stack),
"function": event.function,
"args": {"repr_args": self.try_source(event).strip(), "initial_args": self.try_source(event).strip()},
"color": self.event_colors.get(event.kind, ""),
"counter": self.counter
}
UI.events.append(event_data)

@classmethod
def start(cls):
app = QApplication(sys.argv)
ui = TracebackVisualizer(cls.events)
ui.show()
sys.exit(app.exec_())
Loading