kalong 0.4.3__tar.gz → 0.5.0__tar.gz

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.
Files changed (34) hide show
  1. kalong-0.5.0/.gitignore +16 -0
  2. kalong-0.5.0/PKG-INFO +22 -0
  3. kalong-0.5.0/README.md +0 -0
  4. {kalong-0.4.3 → kalong-0.5.0}/kalong/__init__.py +59 -11
  5. kalong-0.5.0/kalong/__main__.py +4 -0
  6. kalong-0.5.0/kalong/communication.py +221 -0
  7. {kalong-0.4.3 → kalong-0.5.0}/kalong/config.py +7 -5
  8. {kalong-0.4.3 → kalong-0.5.0}/kalong/debugger.py +84 -22
  9. {kalong-0.4.3 → kalong-0.5.0}/kalong/forking.py +1 -3
  10. {kalong-0.4.3 → kalong-0.5.0}/kalong/loops.py +2 -2
  11. {kalong-0.4.3 → kalong-0.5.0}/kalong/server.py +5 -14
  12. kalong-0.5.0/kalong/static/assets/index-ubiYZoKg.js +226 -0
  13. {kalong-0.4.3 → kalong-0.5.0}/kalong/static/index.html +3 -4
  14. {kalong-0.4.3 → kalong-0.5.0}/kalong/stepping.py +21 -3
  15. {kalong-0.4.3 → kalong-0.5.0}/kalong/tracing.py +12 -14
  16. {kalong-0.4.3 → kalong-0.5.0}/kalong/utils/__init__.py +12 -3
  17. kalong-0.5.0/kalong/utils/doc_lookup/lookup.json +9861 -0
  18. {kalong-0.4.3 → kalong-0.5.0}/kalong/utils/iterators.py +14 -0
  19. {kalong-0.4.3 → kalong-0.5.0}/kalong/utils/obj.py +14 -16
  20. {kalong-0.4.3 → kalong-0.5.0}/kalong/websockets.py +5 -14
  21. kalong-0.5.0/pyproject.toml +58 -0
  22. kalong-0.4.3/PKG-INFO +0 -27
  23. kalong-0.4.3/kalong/__main__.py +0 -39
  24. kalong-0.4.3/kalong/communication.py +0 -198
  25. kalong-0.4.3/kalong/static/assets/index-666d143f.js +0 -196
  26. kalong-0.4.3/kalong/utils/doc_lookup/lookup.json +0 -1
  27. kalong-0.4.3/pyproject.toml +0 -56
  28. {kalong-0.4.3 → kalong-0.5.0}/LICENSE +0 -0
  29. {kalong-0.4.3 → kalong-0.5.0}/kalong/errors.py +0 -0
  30. /kalong-0.4.3/kalong/static/assets/FiraCode-Bold-38f445df.otf → /kalong-0.5.0/kalong/static/assets/FiraCode-Bold-Ba6ukUQM.otf +0 -0
  31. /kalong-0.4.3/kalong/static/assets/FiraCode-Regular-825cd631.otf → /kalong-0.5.0/kalong/static/assets/FiraCode-Regular-DP3ilQdk.otf +0 -0
  32. /kalong-0.4.3/kalong/static/assets/favicon-208a422a.svg → /kalong-0.5.0/kalong/static/assets/favicon-rdSYpj7B.svg +0 -0
  33. {kalong-0.4.3 → kalong-0.5.0}/kalong/utils/doc_lookup/__init__.py +0 -0
  34. {kalong-0.4.3 → kalong-0.5.0}/kalong/utils/io.py +0 -0
@@ -0,0 +1,16 @@
1
+ .eggs
2
+ .cache/
3
+ __pycache__/
4
+ build
5
+ .env
6
+ .coverage
7
+ .pytest_cache
8
+ pip-wheel-metadata
9
+ examples/unrest-test.db
10
+ htmlcov
11
+ node_modules/
12
+ lib/
13
+ coverage/
14
+ kalong/static/
15
+ yarn-error.log
16
+ dist/
kalong-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.3
2
+ Name: kalong
3
+ Version: 0.5.0
4
+ Summary: A new take on python debugging
5
+ Author-email: Florian Mounier <paradoxxx.zero@gmail.com>
6
+ License-Expression: GPL-3.0-or-later
7
+ License-File: LICENSE
8
+ Keywords: debugger
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.8
16
+ Requires-Dist: aiohttp>=3.10.6
17
+ Requires-Dist: cutter>=0.5.0
18
+ Requires-Dist: jedi>=0.19.1
19
+ Provides-Extra: disassembly
20
+ Requires-Dist: uncompyle6>=3.9.2; extra == 'disassembly'
21
+ Provides-Extra: recursive
22
+ Requires-Dist: nest-asyncio>=1.6.0; extra == 'recursive'
kalong-0.5.0/README.md ADDED
File without changes
@@ -1,7 +1,10 @@
1
1
  """A new take on debugging"""
2
- __version__ = "0.4.3"
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()
@@ -0,0 +1,4 @@
1
+ from kalong import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,221 @@
1
+ import asyncio
2
+ import json
3
+ import linecache
4
+ import logging
5
+ import sys
6
+ import threading
7
+
8
+ from aiohttp import WSMsgType
9
+
10
+ from . import config
11
+ from .debugger import (
12
+ get_frame,
13
+ get_title,
14
+ serialize_answer,
15
+ serialize_answer_recursive,
16
+ serialize_diff_eval,
17
+ serialize_exception,
18
+ serialize_frames,
19
+ serialize_inspect,
20
+ serialize_inspect_eval,
21
+ serialize_suggestion,
22
+ serialize_table,
23
+ )
24
+ from .errors import SetFrameError
25
+ from .loops import get_loop
26
+ from .stepping import add_step, clear_step, stop_trace
27
+ from .utils import basicConfig, get_file_from_code
28
+ from .websockets import die, websocket_state
29
+
30
+ log = logging.getLogger(__name__)
31
+ basicConfig(level=config.log_level)
32
+
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
+
40
+ def communicate(frame, event, arg):
41
+ loop = get_loop()
42
+
43
+ loop.set_exception_handler(exception_handler)
44
+ try:
45
+ loop.run_until_complete(communication_loop(frame, event, arg))
46
+ except asyncio.CancelledError:
47
+ log.info("Loop got cancelled")
48
+ die()
49
+
50
+
51
+ async def init(ws, frame, event, arg):
52
+ tb = arg[2] if event == "exception" else None
53
+
54
+ await ws.send_json({"type": "SET_THEME", "theme": event})
55
+ await ws.send_json({"type": "SET_TITLE", "title": get_title(frame, event, arg)})
56
+ await ws.send_json(
57
+ {
58
+ "type": "SET_FRAMES",
59
+ "frames": list(serialize_frames(frame, tb)) if event != "shell" else [],
60
+ }
61
+ )
62
+
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
+
164
+ async def communication_loop(frame_, event_, arg_):
165
+ frame = frame_
166
+ event = event_
167
+ arg = arg_
168
+
169
+ ws, existing = await websocket_state()
170
+ try:
171
+ await ws.send_json({"type": "PAUSE"})
172
+ except ConnectionResetError:
173
+ log.info("Connection was reset")
174
+ stop_trace(frame)
175
+ return
176
+
177
+ if existing:
178
+ # If the socket is already opened we need to update client state
179
+ await init(ws, frame, event, arg)
180
+ # Otherwise if it's new, just wait for HELLO to answer current state
181
+
182
+ stop = False
183
+ async for msg in ws:
184
+ if msg.type == WSMsgType.TEXT:
185
+ data = json.loads(msg.data)
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)
190
+ response = {
191
+ "type": "SET_ANSWER",
192
+ "prompt": data["prompt"].strip(),
193
+ "key": data["key"],
194
+ "command": data.get("command"),
195
+ "frame": data.get("frame"),
196
+ "answer": [serialize_exception(*sys.exc_info(), "internal")],
197
+ }
198
+ log.debug(f"Got {data} answering with {response}")
199
+ response["local"] = True
200
+
201
+ if response.pop("recursive", False):
202
+ await ws.send_json({"type": "PAUSE", "recursive": True})
203
+ await init(ws, frame, event, arg)
204
+
205
+ stop = response.pop("stop", False)
206
+
207
+ try:
208
+ await ws.send_json(response)
209
+ except ConnectionResetError:
210
+ break
211
+
212
+ if stop:
213
+ break
214
+
215
+ elif msg.type == WSMsgType.ERROR:
216
+ log.error("WebSocket closed", exc_info=ws.exception())
217
+ break
218
+
219
+ # Browser exited, stopping debug if we are not stepping
220
+ if not stop:
221
+ stop_trace(frame)
@@ -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,
@@ -21,9 +21,10 @@ from pprint import pformat
21
21
 
22
22
  from jedi import Interpreter
23
23
 
24
- from .utils import cutter_mock, discompile, universal_travel
24
+ from .errors import SetFrameError
25
+ from .utils import cutter_mock, dedent, discompile, universal_travel
25
26
  from .utils.io import capture_display, capture_exception, capture_std
26
- from .utils.iterators import iter_cause, iter_stack, iter_frame
27
+ from .utils.iterators import force_iterable, iter_cause, iter_frame, iter_stack
27
28
  from .utils.obj import (
28
29
  get_code,
29
30
  get_infos,
@@ -32,7 +33,6 @@ from .utils.obj import (
32
33
  safe_repr,
33
34
  sync_locals,
34
35
  )
35
- from .errors import SetFrameError
36
36
 
37
37
  try:
38
38
  from cutter import cut
@@ -66,7 +66,7 @@ def get_frame(frame, key):
66
66
  for f in iter_frame(frame):
67
67
  if id(f) == key:
68
68
  return f
69
- log.warn(f"Frame {key} not found")
69
+ log.warning(f"Frame {key} not found")
70
70
  return frame
71
71
 
72
72
 
@@ -89,7 +89,11 @@ def serialize_frames(current_frame, current_tb):
89
89
  line = linecache.getline(filename, lno, frame.f_globals)
90
90
  line = line and line.strip()
91
91
  startlnos = dis.findlinestarts(code)
92
- lastlineno = list(startlnos)[-1][1]
92
+ lnos = list(startlnos)
93
+ lastlineno = None
94
+ if lnos:
95
+ lastlineno = lnos[-1][1]
96
+
93
97
  yield {
94
98
  "key": id(frame),
95
99
  "absoluteFilename": str(fn),
@@ -103,8 +107,42 @@ def serialize_frames(current_frame, current_tb):
103
107
  }
104
108
 
105
109
 
106
- def serialize_answer(prompt, frame):
107
- prompt = prompt.strip()
110
+ def serialize_answer_recursive(prompt, frame):
111
+ return serialize_answer(prompt, frame, True)
112
+
113
+
114
+ def _rec_exec(code, globals, locals):
115
+ from .stepping import start_trace, steppings, stop_trace
116
+ from .utils import current_origin
117
+
118
+ frame = sys._getframe()
119
+ stop_trace(frame)
120
+
121
+ origin = current_origin()
122
+ steppings[origin] = {
123
+ "type": "stepInto",
124
+ "frame": frame,
125
+ "lno": frame.f_lineno,
126
+ "skip_frames": 1, # Skip exec frame
127
+ "_parent": steppings.get(origin),
128
+ }
129
+
130
+ start_trace(frame)
131
+ try:
132
+ exec(code, globals, locals)
133
+ finally:
134
+ parent = steppings[origin].get("_parent")
135
+ if parent:
136
+ steppings[origin] = parent
137
+ else:
138
+ stop_trace(frame)
139
+
140
+
141
+ def rec_exec(code, globals, locals):
142
+ sys.call_tracing(_rec_exec, (code, globals, locals))
143
+
144
+
145
+ def serialize_answer(prompt, frame, recursive=False):
108
146
  duration = 0
109
147
  answer = []
110
148
  f_globals = dict(frame.f_globals)
@@ -117,16 +155,16 @@ def serialize_answer(prompt, frame):
117
155
  last_key = "_" if "_" not in f_locals else "__"
118
156
  f_locals[last_key] = frame.f_globals["__kalong_last_value__"]
119
157
 
120
- with capture_exception(answer), capture_display(
121
- answer
122
- ) as out, capture_std(answer):
158
+ with capture_exception(answer), capture_display(answer) as out, capture_std(answer):
123
159
  compiled_code = None
124
160
  try:
125
- compiled_code = compile(prompt, "<stdin>", "single")
161
+ compiled_code = compile(prompt.strip(), "<stdin>", "single")
162
+ prompt = prompt.strip()
126
163
  except SetFrameError:
127
164
  raise
128
165
  except Exception:
129
166
  try:
167
+ prompt = dedent(prompt)
130
168
  compiled_code = compile(prompt, "<stdin>", "exec")
131
169
  except SetFrameError:
132
170
  raise
@@ -137,29 +175,36 @@ def serialize_answer(prompt, frame):
137
175
  start = time.time()
138
176
  if compiled_code is not None:
139
177
  try:
140
- exec(compiled_code, f_globals, f_locals)
178
+ (rec_exec if recursive else exec)(compiled_code, f_globals, f_locals)
141
179
  except SetFrameError:
142
180
  raise
143
181
  except Exception:
144
182
  # handle ex
145
183
  sys.excepthook(*sys.exc_info())
146
- del f_locals["__kalong_current_frame__"]
147
- del f_locals["cut"]
148
- if last_key:
184
+ if "__kalong_current_frame__" in f_locals:
185
+ del f_locals["__kalong_current_frame__"]
186
+ if "cut" in f_locals:
187
+ del f_locals["cut"]
188
+ if last_key and last_key in f_locals:
149
189
  del f_locals[last_key]
150
190
  sync_locals(frame, f_locals)
151
- if out.obj:
191
+ if out.obj is not None:
152
192
  frame.f_globals["__kalong_last_value__"] = out.obj
153
193
  duration = int((time.time() - start) * 1000 * 1000 * 1000)
154
194
 
155
- return {"prompt": prompt, "answer": answer, "duration": duration}
195
+ return {
196
+ "prompt": prompt,
197
+ "answer": answer,
198
+ "duration": duration,
199
+ "recursive": recursive,
200
+ }
156
201
 
157
202
 
158
- def serialize_exception(type_, value, tb):
203
+ def serialize_exception(type_, value, tb, subtype="root"):
159
204
  return {
160
205
  "type": "exception",
161
206
  "id": obj_cache.register(value),
162
- "subtype": "root",
207
+ "subtype": subtype,
163
208
  "name": type_.__name__,
164
209
  "description": str(value),
165
210
  "traceback": list(serialize_frames(None, tb)),
@@ -358,8 +403,12 @@ def serialize_suggestion(prompt, from_, to, cursor, frame):
358
403
 
359
404
 
360
405
  def serialize_table(prompt, frame):
361
- valueStr, columns = (s.strip() for s in prompt.split(table_separator))
362
- columns = [c.strip() for c in columns.split(",")]
406
+ if table_separator in prompt:
407
+ valueStr, columns = (s.strip() for s in prompt.split(table_separator))
408
+ columns = [c.strip() for c in columns.split(",")]
409
+ else:
410
+ valueStr = prompt.strip()
411
+ columns = None
363
412
 
364
413
  try:
365
414
  valueKey = get_id_from_expression(valueStr, frame)
@@ -370,6 +419,19 @@ def serialize_table(prompt, frame):
370
419
  }
371
420
 
372
421
  value = obj_cache.get(valueKey)
422
+ values = list(force_iterable(value, True))
423
+ if not columns:
424
+ if all(isinstance(value, dict) for value in values):
425
+ columns = list({column for value in values for column in value.keys()})
426
+ else:
427
+ columns = list(
428
+ {
429
+ column
430
+ for value in values
431
+ for column in dir(value)
432
+ if not column.startswith("__")
433
+ }
434
+ )
373
435
 
374
436
  answer = [
375
437
  {
@@ -386,7 +448,7 @@ def serialize_table(prompt, frame):
386
448
  for column in columns
387
449
  for value in [universal_travel(row, column)]
388
450
  }
389
- for row in value
451
+ for row in values
390
452
  ],
391
453
  }
392
454
  ]
@@ -15,9 +15,7 @@ def forkserver():
15
15
  kalong_dir = Path(__file__).parent.parent
16
16
  env = dict(os.environ)
17
17
  env["PYTHONPATH"] = (
18
- f'{kalong_dir}:{env["PYTHONPATH"]}'
19
- if env.get("PYTHONPATH")
20
- else kalong_dir
18
+ f'{kalong_dir}:{env["PYTHONPATH"]}' if env.get("PYTHONPATH") else kalong_dir
21
19
  )
22
20
 
23
21
  popen_args = (
@@ -22,13 +22,13 @@ def get_loop():
22
22
  origin = current_origin()
23
23
  try:
24
24
  loop = asyncio.get_running_loop()
25
- if origin not in loops or loops[origin] != loop:
25
+ if origin not in loops or loop.is_running():
26
26
  if nest_asyncio:
27
27
  nest_asyncio.apply(loop)
28
28
  else:
29
29
  raise ImportError(
30
30
  "If you want to use kalong in an asyncio environment "
31
- "please install nest_asyncio"
31
+ "or use recursive debugging please install nest_asyncio"
32
32
  )
33
33
  except RuntimeError:
34
34
  pass
@@ -11,7 +11,7 @@ from aiohttp.web_runner import GracefulExit
11
11
 
12
12
  from . import config
13
13
  from .errors import NoClientFoundError
14
- from .utils import basicConfig, USER_SIGNAL, human_readable_side, parse_origin
14
+ from .utils import USER_SIGNAL, basicConfig, human_readable_side, parse_origin
15
15
  from .websockets import websocket_options
16
16
 
17
17
  log = logging.getLogger(__name__)
@@ -32,10 +32,7 @@ def maybe_bail(app):
32
32
  async def shutdown(app):
33
33
  log.info("App shutdown, closing remaining websockets.")
34
34
  await asyncio.gather(
35
- *[
36
- ws.close()
37
- for ws in chain(app["front"].values(), app["back"].values())
38
- ]
35
+ *[ws.close() for ws in chain(app["front"].values(), app["back"].values())]
39
36
  )
40
37
  app["front"].clear()
41
38
  app["back"].clear()
@@ -63,9 +60,7 @@ async def websocket(request):
63
60
  state = ws.can_prepare(request)
64
61
  if not state.ok:
65
62
  log.debug(f"Sending {side} app for {origin}")
66
- return web.FileResponse(
67
- Path(__file__).parent / "static" / "index.html"
68
- )
63
+ return web.FileResponse(Path(__file__).parent / "static" / "index.html")
69
64
 
70
65
  await ws.prepare(request)
71
66
 
@@ -88,9 +83,7 @@ async def websocket(request):
88
83
  _, pid, _ = parse_origin(origin)
89
84
  os.kill(
90
85
  pid,
91
- signal.SIGTERM
92
- if data["command"] == "kill"
93
- else USER_SIGNAL,
86
+ signal.SIGTERM if data["command"] == "kill" else USER_SIGNAL,
94
87
  )
95
88
  continue
96
89
 
@@ -122,7 +115,5 @@ def serve():
122
115
  app["back"] = {}
123
116
  app.on_shutdown.append(shutdown)
124
117
  app.router.add_get(r"/{side:(front|back)}/{origin}", websocket)
125
- app.router.add_static(
126
- "/assets/", Path(__file__).parent / "static" / "assets"
127
- )
118
+ app.router.add_static("/assets/", Path(__file__).parent / "static" / "assets")
128
119
  web.run_app(app, host=config.host, port=config.port, print=False)