kalong 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kalong/__init__.py CHANGED
@@ -1,7 +1,10 @@
1
1
  """A new take on debugging"""
2
- __version__ = "0.4.2"
2
+
3
+ __version__ = "0.5.0"
3
4
  import os
4
5
  import sys
6
+ from pathlib import Path
7
+ from subprocess import run
5
8
 
6
9
  from .config import Config
7
10
 
@@ -31,12 +34,14 @@ def break_above(level):
31
34
  start_trace(frame)
32
35
 
33
36
 
34
- def start_trace(break_=False, full=False):
37
+ def start_trace(break_=False, full=False, skip_frames=None):
35
38
  from .stepping import add_step, start_trace
36
39
 
37
40
  frame = sys._getframe().f_back
38
41
  add_step(
39
- "stepInto" if break_ else ("trace" if full else "continue"), frame
42
+ "stepInto" if break_ else ("trace" if full else "continue"),
43
+ frame,
44
+ skip_frames,
40
45
  )
41
46
  start_trace(frame)
42
47
 
@@ -49,23 +54,23 @@ def stop_trace():
49
54
 
50
55
 
51
56
  class trace:
52
- def __init__(self, break_=False, full=False):
53
- self.break_ = break_
54
- self.full = full
57
+ def __init__(self, **kwargs):
58
+ self.kwargs = kwargs
55
59
 
56
60
  def __enter__(self):
57
- start_trace(self.break_, self.full)
61
+ stop_trace()
62
+ start_trace(**self.kwargs)
58
63
 
59
64
  def __exit__(self, *args):
60
65
  stop_trace()
61
66
 
62
67
 
63
- def run_file(filename, *args):
64
- from .utils import fake_argv
65
-
68
+ def run_file(filename, *args, break_=True):
66
69
  # Cleaning __main__ namespace
67
70
  import __main__
68
71
 
72
+ from .utils import fake_argv
73
+
69
74
  __main__.__dict__.clear()
70
75
  __main__.__dict__.update(
71
76
  {
@@ -82,7 +87,7 @@ def run_file(filename, *args):
82
87
  globals = __main__.__dict__
83
88
  locals = globals
84
89
  with fake_argv(filename, *args):
85
- with trace(break_=True):
90
+ with trace(break_=break_):
86
91
  exec(statement, globals, locals)
87
92
 
88
93
 
@@ -93,3 +98,46 @@ def shell():
93
98
  frame = sys._getframe()
94
99
  # Enter the websocket communication loop that pauses the execution
95
100
  communicate(frame, "shell", [])
101
+
102
+
103
+ def main():
104
+ os.environ["PYTHONBREAKPOINT"] = "kalong.breakpoint"
105
+ config.from_args()
106
+
107
+ if config.server:
108
+ from .server import serve
109
+
110
+ serve()
111
+
112
+ elif config.inject:
113
+ kalong_dir = Path(__file__).resolve().parent.parent
114
+ gdb_command = (
115
+ ["gdb", "-p", str(config.inject), "-batch"]
116
+ + [
117
+ "-eval-command=call %s" % hook
118
+ for hook in [
119
+ "(int) PyGILState_Ensure()", # Getting the GIL
120
+ '(int) PyRun_SimpleString("'
121
+ f"print('* Kalong injection from {os.getpid()} *');"
122
+ "import sys;" # Putting kalong project directory in sys path:
123
+ f"sys.path.insert(0, '{kalong_dir}');"
124
+ "import kalong;" # Setting breakpoint:
125
+ "kalong.break_above(2);"
126
+ '")',
127
+ # Releasing the GIL with the PyGILState_Ensure handle:
128
+ "(void) PyGILState_Release($1)",
129
+ ]
130
+ ]
131
+ )
132
+ print(f'Running: {" ".join(gdb_command)}')
133
+ run(gdb_command)
134
+
135
+ else:
136
+ if config.command:
137
+ run_file(*config.command, break_=config.break_at_start)
138
+ else:
139
+ shell()
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()
kalong/__main__.py CHANGED
@@ -1,39 +1,4 @@
1
- import os
2
- from pathlib import Path
3
- from subprocess import run
1
+ from kalong import main
4
2
 
5
- from . import config, run_file, shell
6
-
7
- os.environ["PYTHONBREAKPOINT"] = "kalong.breakpoint"
8
- config.from_args()
9
-
10
- if config.server:
11
- from .server import serve
12
-
13
- serve()
14
-
15
- elif config.inject:
16
- kalong_dir = Path(__file__).resolve().parent.parent
17
- gdb_command = ["gdb", "-p", str(config.inject), "-batch"] + [
18
- "-eval-command=call %s" % hook
19
- for hook in [
20
- "(int) PyGILState_Ensure()", # Getting the GIL
21
- '(int) PyRun_SimpleString("'
22
- f"print('* Kalong injection from {os.getpid()} *');"
23
- "import sys;" # Putting kalong project directory in sys path:
24
- f"sys.path.insert(0, '{kalong_dir}');"
25
- "import kalong;" # Setting breakpoint:
26
- "kalong.break_above(2);"
27
- '")',
28
- # Releasing the GIL with the PyGILState_Ensure handle:
29
- "(void) PyGILState_Release($1)",
30
- ]
31
- ]
32
- print(f'Running: {" ".join(gdb_command)}')
33
- run(gdb_command)
34
-
35
- else:
36
- if config.command:
37
- run_file(*config.command)
38
- else:
39
- shell()
3
+ if __name__ == "__main__":
4
+ main()
kalong/communication.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
  import json
3
3
  import linecache
4
4
  import logging
5
+ import sys
5
6
  import threading
6
7
 
7
8
  from aiohttp import WSMsgType
@@ -11,25 +12,35 @@ from .debugger import (
11
12
  get_frame,
12
13
  get_title,
13
14
  serialize_answer,
15
+ serialize_answer_recursive,
14
16
  serialize_diff_eval,
17
+ serialize_exception,
15
18
  serialize_frames,
16
19
  serialize_inspect,
17
20
  serialize_inspect_eval,
18
21
  serialize_suggestion,
22
+ serialize_table,
19
23
  )
24
+ from .errors import SetFrameError
20
25
  from .loops import get_loop
21
26
  from .stepping import add_step, clear_step, stop_trace
22
27
  from .utils import basicConfig, get_file_from_code
23
28
  from .websockets import die, websocket_state
24
- from .errors import SetFrameError
25
29
 
26
30
  log = logging.getLogger(__name__)
27
31
  basicConfig(level=config.log_level)
28
32
 
29
33
 
34
+ def exception_handler(loop, context):
35
+ exception = context["exception"]
36
+ message = context["message"]
37
+ logging.error(f"Task failed, msg={message}, exception={exception}")
38
+
39
+
30
40
  def communicate(frame, event, arg):
31
41
  loop = get_loop()
32
42
 
43
+ loop.set_exception_handler(exception_handler)
33
44
  try:
34
45
  loop.run_until_complete(communication_loop(frame, event, arg))
35
46
  except asyncio.CancelledError:
@@ -41,19 +52,115 @@ async def init(ws, frame, event, arg):
41
52
  tb = arg[2] if event == "exception" else None
42
53
 
43
54
  await ws.send_json({"type": "SET_THEME", "theme": event})
44
- await ws.send_json(
45
- {"type": "SET_TITLE", "title": get_title(frame, event, arg)}
46
- )
55
+ await ws.send_json({"type": "SET_TITLE", "title": get_title(frame, event, arg)})
47
56
  await ws.send_json(
48
57
  {
49
58
  "type": "SET_FRAMES",
50
- "frames": list(serialize_frames(frame, tb))
51
- if event != "shell"
52
- else [],
59
+ "frames": list(serialize_frames(frame, tb)) if event != "shell" else [],
53
60
  }
54
61
  )
55
62
 
56
63
 
64
+ async def handle_message(ws, data, frame, event, arg):
65
+ if data["type"] == "HELLO":
66
+ await init(ws, frame, event, arg)
67
+ response = {
68
+ "type": "SET_INFO",
69
+ "config": config.__dict__,
70
+ "main": threading.current_thread() is threading.main_thread(),
71
+ }
72
+
73
+ elif data["type"] == "GET_FILE":
74
+ filename = data["filename"]
75
+ file = "".join(linecache.getlines(filename))
76
+ if not file:
77
+ file = get_file_from_code(frame, filename)
78
+ response = {
79
+ "type": "SET_FILE",
80
+ "filename": filename,
81
+ "source": file,
82
+ }
83
+
84
+ elif data["type"] == "SET_PROMPT" or data["type"] == "REFRESH_PROMPT":
85
+ try:
86
+ eval_fun = (
87
+ serialize_inspect_eval
88
+ if data.get("command") == "inspect"
89
+ else serialize_diff_eval
90
+ if data.get("command") == "diff"
91
+ else serialize_table
92
+ if data.get("command") == "table"
93
+ else serialize_answer_recursive
94
+ if data.get("command") == "recursive_debug"
95
+ else serialize_answer
96
+ )
97
+ response = {
98
+ "type": "SET_ANSWER",
99
+ "key": data["key"],
100
+ "command": data.get("command"),
101
+ "frame": data.get("frame"),
102
+ **eval_fun(
103
+ data["prompt"],
104
+ get_frame(frame, data.get("frame")),
105
+ ),
106
+ }
107
+ except SetFrameError as e:
108
+ frame = e.frame
109
+ event = e.event
110
+ arg = e.arg
111
+
112
+ await init(ws, frame, event, arg)
113
+
114
+ response = {
115
+ "type": "SET_ANSWER",
116
+ "key": data["key"],
117
+ "command": data.get("command"),
118
+ "frame": data.get("frame"),
119
+ "prompt": data["prompt"].strip(),
120
+ "answer": "",
121
+ "duration": 0,
122
+ }
123
+
124
+ elif data["type"] == "REQUEST_INSPECT":
125
+ response = {
126
+ "type": "SET_ANSWER",
127
+ "key": data["key"],
128
+ "command": data.get("command"),
129
+ **serialize_inspect(data["id"]),
130
+ }
131
+
132
+ elif data["type"] == "REQUEST_SUGGESTION":
133
+ response = {
134
+ "type": "SET_SUGGESTION",
135
+ **serialize_suggestion(
136
+ data["prompt"],
137
+ data["from"],
138
+ data["to"],
139
+ data["cursor"],
140
+ get_frame(frame, data.get("frame")),
141
+ ),
142
+ }
143
+
144
+ elif data["type"] == "DO_COMMAND":
145
+ command = data["command"]
146
+ response = {"type": "ACK", "command": command}
147
+ if command == "run":
148
+ clear_step()
149
+ stop_trace(frame)
150
+ elif command == "stop":
151
+ clear_step()
152
+ stop_trace(frame)
153
+ die()
154
+ else:
155
+ step_frame = get_frame(frame, data.get("frame"))
156
+ add_step(command, step_frame)
157
+ response["stop"] = True
158
+
159
+ else:
160
+ raise ValueError(f"Unknown type {data['type']}")
161
+ return response
162
+
163
+
57
164
  async def communication_loop(frame_, event_, arg_):
58
165
  frame = frame_
59
166
  event = event_
@@ -76,108 +183,27 @@ async def communication_loop(frame_, event_, arg_):
76
183
  async for msg in ws:
77
184
  if msg.type == WSMsgType.TEXT:
78
185
  data = json.loads(msg.data)
79
- if data["type"] == "HELLO":
80
- await init(ws, frame, event, arg)
81
- response = {
82
- "type": "SET_INFO",
83
- "config": config.__dict__,
84
- "main": threading.current_thread()
85
- is threading.main_thread(),
86
- }
87
-
88
- elif data["type"] == "GET_FILE":
89
- filename = data["filename"]
90
- file = "".join(linecache.getlines(filename))
91
- if not file:
92
- file = get_file_from_code(frame, filename)
93
- response = {
94
- "type": "SET_FILE",
95
- "filename": filename,
96
- "source": file,
97
- }
98
-
99
- elif (
100
- data["type"] == "SET_PROMPT"
101
- or data["type"] == "REFRESH_PROMPT"
102
- ):
103
- try:
104
- eval_fun = (
105
- serialize_inspect_eval
106
- if data.get("command") == "inspect"
107
- else serialize_diff_eval
108
- if data.get("command") == "diff"
109
- else serialize_answer
110
- )
111
- response = {
112
- "type": "SET_ANSWER",
113
- "key": data["key"],
114
- "command": data.get("command"),
115
- "frame": data.get("frame"),
116
- **eval_fun(
117
- data["prompt"],
118
- get_frame(frame, data.get("frame")),
119
- ),
120
- }
121
- except SetFrameError as e:
122
- frame = e.frame
123
- event = e.event
124
- arg = e.arg
125
-
126
- await init(ws, frame, event, arg)
127
-
128
- response = {
129
- "type": "SET_ANSWER",
130
- "key": data["key"],
131
- "command": data.get("command"),
132
- "frame": data.get("frame"),
133
- "prompt": data["prompt"].strip(),
134
- "answer": "",
135
- "duration": 0,
136
- }
137
-
138
- elif data["type"] == "REQUEST_INSPECT":
186
+ try:
187
+ response = await handle_message(ws, data, frame, event, arg)
188
+ except Exception as e:
189
+ log.error(f"Error handling message {data}", exc_info=e)
139
190
  response = {
140
191
  "type": "SET_ANSWER",
192
+ "prompt": data["prompt"].strip(),
141
193
  "key": data["key"],
142
194
  "command": data.get("command"),
143
- **serialize_inspect(data["id"]),
195
+ "frame": data.get("frame"),
196
+ "answer": [serialize_exception(*sys.exc_info(), "internal")],
144
197
  }
198
+ log.debug(f"Got {data} answering with {response}")
199
+ response["local"] = True
145
200
 
146
- elif data["type"] == "REQUEST_SUGGESTION":
147
- response = {
148
- "type": "SET_SUGGESTION",
149
- **serialize_suggestion(
150
- data["prompt"],
151
- data["from"],
152
- data["to"],
153
- data["cursor"],
154
- get_frame(frame, data.get("frame")),
155
- ),
156
- }
201
+ if response.pop("recursive", False):
202
+ await ws.send_json({"type": "PAUSE", "recursive": True})
203
+ await init(ws, frame, event, arg)
157
204
 
158
- elif data["type"] == "DO_COMMAND":
159
- command = data["command"]
160
- response = {"type": "ACK", "command": command}
161
- if command == "run":
162
- clear_step()
163
- stop_trace(frame)
164
- elif command == "stop":
165
- clear_step()
166
- stop_trace(frame)
167
- die()
168
- else:
169
- step_frame = get_frame(frame, data.get("frame"))
170
- add_step(command, step_frame)
171
- stop = True
172
-
173
- else:
174
- response = {
175
- "type": "error",
176
- "message": f"Unknown type {data['type']}",
177
- }
205
+ stop = response.pop("stop", False)
178
206
 
179
- log.debug(f"Got {data} answering with {response}")
180
- response["local"] = True
181
207
  try:
182
208
  await ws.send_json(response)
183
209
  except ConnectionResetError:
kalong/config.py CHANGED
@@ -24,17 +24,14 @@ class Config:
24
24
  parser.add_argument(
25
25
  "--server",
26
26
  action="store_true",
27
- help="Launch the kalong server. "
28
- "This option is used by kalong itself",
27
+ help="Launch the kalong server. " "This option is used by kalong itself",
29
28
  )
30
29
  parser.add_argument(
31
30
  "--protocol",
32
31
  default=self.protocol,
33
32
  help="Protocol for contacting kalong server",
34
33
  )
35
- parser.add_argument(
36
- "--host", default=self.host, help="Host of kalong server"
37
- )
34
+ parser.add_argument("--host", default=self.host, help="Host of kalong server")
38
35
  parser.add_argument(
39
36
  "--port", type=int, default=self.port, help="Port of kalong server"
40
37
  )
@@ -64,6 +61,11 @@ class Config:
64
61
  help="Pid of a running process in which a debugger will be "
65
62
  "injected with gdb. This needs a working gdb and ptrace enabled",
66
63
  )
64
+ parser.add_argument(
65
+ "--break-at-start",
66
+ action="store_true",
67
+ help="Break at the start of the python file",
68
+ )
67
69
  parser.add_argument(
68
70
  "command",
69
71
  nargs=REMAINDER,