dbgr-server 1.1.0.dev1__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.
Files changed (86) hide show
  1. dbgr_server/__init__.py +1 -0
  2. dbgr_server/__main__.py +61 -0
  3. dbgr_server/app.py +139 -0
  4. dbgr_server/constants.py +10 -0
  5. dbgr_server/routes.py +22 -0
  6. dbgr_server/static/fonts/MapleMono-NF-Bold.ttf +0 -0
  7. dbgr_server/static/fonts/MapleMono-NF-BoldItalic.ttf +0 -0
  8. dbgr_server/static/fonts/MapleMono-NF-Italic.ttf +0 -0
  9. dbgr_server/static/fonts/MapleMono-NF-Regular.ttf +0 -0
  10. dbgr_server/static/images/favicon.ico +0 -0
  11. dbgr_server/static/javascripts/dist/0d8bb5b3ee5f5dac9e44.woff2 +0 -0
  12. dbgr_server/static/javascripts/dist/16b1558858fe1064b5af.woff +0 -0
  13. dbgr_server/static/javascripts/dist/176f8f5bd5f02b3abfcf.woff2 +0 -0
  14. dbgr_server/static/javascripts/dist/18d00f739ff1e1c52db1.woff +0 -0
  15. dbgr_server/static/javascripts/dist/1b3b4f49d5b3b3fb2c20.jpg +0 -0
  16. dbgr_server/static/javascripts/dist/1ce08cfde36c0d81af5b.woff +0 -0
  17. dbgr_server/static/javascripts/dist/213eedde97d22010287c.woff +0 -0
  18. dbgr_server/static/javascripts/dist/2267169ee7270a22a963.woff +0 -0
  19. dbgr_server/static/javascripts/dist/24a9d3bbf9b715214bf5.woff +0 -0
  20. dbgr_server/static/javascripts/dist/2900d24e7347deaf65cd.woff +0 -0
  21. dbgr_server/static/javascripts/dist/2ed4651232a34d737777.woff +0 -0
  22. dbgr_server/static/javascripts/dist/37a7069dc30fc663c878.woff2 +0 -0
  23. dbgr_server/static/javascripts/dist/451d4e559d6f57cdf6a1.woff +0 -0
  24. dbgr_server/static/javascripts/dist/4674f8ded773cb03e824.eot +0 -0
  25. dbgr_server/static/javascripts/dist/498042b7fe9cd07b4fd1.woff +0 -0
  26. dbgr_server/static/javascripts/dist/49ae34d4cc6b98c00c69.woff +0 -0
  27. dbgr_server/static/javascripts/dist/51aa168dd4ba03bd83f0.woff2 +0 -0
  28. dbgr_server/static/javascripts/dist/535bc89d4af715503b01.woff2 +0 -0
  29. dbgr_server/static/javascripts/dist/53b43867955fbecc811a.woff2 +0 -0
  30. dbgr_server/static/javascripts/dist/5e7382c63da0098d634a.ttf +0 -0
  31. dbgr_server/static/javascripts/dist/640f8692dc49a19f1541.woff +0 -0
  32. dbgr_server/static/javascripts/dist/79f21d796f266ce0ea70.woff +0 -0
  33. dbgr_server/static/javascripts/dist/7d8125ff7f707231fd89.woff2 +0 -0
  34. dbgr_server/static/javascripts/dist/7f7e9cd596438fea4b02.woff2 +0 -0
  35. dbgr_server/static/javascripts/dist/7f839a8652da29745ce4.woff2 +0 -0
  36. dbgr_server/static/javascripts/dist/83bebaf37c09c7e1c3ee.woff +0 -0
  37. dbgr_server/static/javascripts/dist/865f928cbabcc9f8f2b5.woff +0 -0
  38. dbgr_server/static/javascripts/dist/870c8c1486f76054301a.woff2 +0 -0
  39. dbgr_server/static/javascripts/dist/9216be4e13fe7dbe70ef.woff2 +0 -0
  40. dbgr_server/static/javascripts/dist/9360531f9bb817f917f0.woff +0 -0
  41. dbgr_server/static/javascripts/dist/969a44e1b6fc26105bd6.woff2 +0 -0
  42. dbgr_server/static/javascripts/dist/a45108d3b34af91f9113.woff +0 -0
  43. dbgr_server/static/javascripts/dist/b1d9d9904bfca8802a63.woff +0 -0
  44. dbgr_server/static/javascripts/dist/b39a11f9a6107e57a21f.woff2 +0 -0
  45. dbgr_server/static/javascripts/dist/bac8362e7a6ea60b6983.woff +0 -0
  46. dbgr_server/static/javascripts/dist/bbbf6951525a78fff340.woff +0 -0
  47. dbgr_server/static/javascripts/dist/bc628a95b5026af0df2c.woff2 +0 -0
  48. dbgr_server/static/javascripts/dist/bd5b7a13f2c52b531a2a.woff +0 -0
  49. dbgr_server/static/javascripts/dist/bf4b4e73015a3066da91.woff +0 -0
  50. dbgr_server/static/javascripts/dist/bfe6eaec56c141a0bd72.woff2 +0 -0
  51. dbgr_server/static/javascripts/dist/c18ee39fb002ad58b6dc.woff2 +0 -0
  52. dbgr_server/static/javascripts/dist/c1f17364fee75703a7ee.woff2 +0 -0
  53. dbgr_server/static/javascripts/dist/c20d916c1a1b094c1cec.woff +0 -0
  54. dbgr_server/static/javascripts/dist/c2aa4ab115bf9c6057cb.woff2 +0 -0
  55. dbgr_server/static/javascripts/dist/c5c3959c04004102ea46.woff2 +0 -0
  56. dbgr_server/static/javascripts/dist/c64e7e354c88e613c77c.woff2 +0 -0
  57. dbgr_server/static/javascripts/dist/ca100b2be6274105bb16.woff +0 -0
  58. dbgr_server/static/javascripts/dist/cb5ad999740e9d8a8bd1.woff2 +0 -0
  59. dbgr_server/static/javascripts/dist/cc9e42999888377183f8.woff2 +0 -0
  60. dbgr_server/static/javascripts/dist/cea99d3e3e13a3a599a0.woff +0 -0
  61. dbgr_server/static/javascripts/dist/cff684e59ffb052d72cb.woff2 +0 -0
  62. dbgr_server/static/javascripts/dist/d022bc70dc1bf7b3425d.woff2 +0 -0
  63. dbgr_server/static/javascripts/dist/d97850bb044fb5584361.woff2 +0 -0
  64. dbgr_server/static/javascripts/dist/da65dcd09cdc4863ece2.woff +0 -0
  65. dbgr_server/static/javascripts/dist/dbgr.js +1463 -0
  66. dbgr_server/static/javascripts/dist/eba647ae67ba7dc20f05.woff +0 -0
  67. dbgr_server/static/javascripts/dist/f5b74d7ffcdf85b9dd60.woff2 +0 -0
  68. dbgr_server/static/javascripts/dist/ff2c5c919a01e9ddda85.woff2 +0 -0
  69. dbgr_server/static/javascripts/dist/home.js +1037 -0
  70. dbgr_server/static/libs/material-design-lite/material.indigo-red.min.css +8 -0
  71. dbgr_server/templates/_layout.html +17 -0
  72. dbgr_server/templates/dbgr.html +138 -0
  73. dbgr_server/templates/home.html +115 -0
  74. dbgr_server/utils/__init__.py +0 -0
  75. dbgr_server/utils/state.py +249 -0
  76. dbgr_server/utils/streams.py +151 -0
  77. dbgr_server/utils/technical.py +132 -0
  78. dbgr_server/views.py +298 -0
  79. dbgr_server-1.1.0.dev1.data/scripts/dbgr.server.py +105 -0
  80. dbgr_server-1.1.0.dev1.dist-info/METADATA +534 -0
  81. dbgr_server-1.1.0.dev1.dist-info/RECORD +86 -0
  82. dbgr_server-1.1.0.dev1.dist-info/WHEEL +5 -0
  83. dbgr_server-1.1.0.dev1.dist-info/licenses/LICENSE +674 -0
  84. dbgr_server-1.1.0.dev1.dist-info/top_level.txt +2 -0
  85. tests/__init__.py +0 -0
  86. tests/test_app.py +84 -0
dbgr_server/views.py ADDED
@@ -0,0 +1,298 @@
1
+ # pylint: disable=missing-function-docstring
2
+ # Stdlib:
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ import multiprocessing
7
+ import os
8
+ import re
9
+ from typing import Optional
10
+ from uuid import uuid4
11
+
12
+ # Thirdparty:
13
+ from fastapi import Form, HTTPException, Request
14
+ from fastapi.responses import RedirectResponse
15
+ from fastapi.templating import Jinja2Templates
16
+ from starlette.websockets import WebSocket, WebSocketDisconnect
17
+
18
+ # Firstparty:
19
+ from dbgr_server.constants import UNKNOWN_UUID, UUID_REGEXP, DBGR_TYPES
20
+ from dbgr_server.utils.state import (
21
+ breakpoints,
22
+ settings,
23
+ sockets,
24
+ syncwebsockets,
25
+ websockets,
26
+ )
27
+ from dbgr_server.utils.technical import LibPythonWatcher, refresh_process
28
+
29
+ log = logging.getLogger("dbgr_server")
30
+
31
+
32
+ async def self_shell() -> None: # pragma: no cover
33
+ import dbgr # pylint: disable=import-outside-toplevel
34
+
35
+ dbgr.set_trace() # pylint: disable=no-member
36
+
37
+
38
+ async def run_shell() -> None: # pragma: no cover
39
+ from dbgr import Dbgr # pylint: disable=import-outside-toplevel
40
+
41
+ Dbgr.get().shell()
42
+
43
+
44
+ async def run_file(file_name: str) -> None: # pragma: no cover
45
+ from dbgr import Dbgr # pylint: disable=import-outside-toplevel
46
+
47
+ Dbgr.get().run_file(file_name)
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # HTTP handlers
52
+ # ---------------------------------------------------------------------------
53
+
54
+ def _get_templates(request: Request) -> Jinja2Templates:
55
+ return request.app.state.templates # type: ignore[no-any-return]
56
+
57
+
58
+ async def home_get(request: Request) -> "TemplateResponse": # type: ignore[name-defined]
59
+ templates = _get_templates(request)
60
+ return templates.TemplateResponse(
61
+ request,
62
+ "home.html",
63
+ context={
64
+ "settings": request.app.state.settings,
65
+ "theme": request.app.state.theme,
66
+ "type_": "home",
67
+ "handler_name": "HomeHandler",
68
+ },
69
+ )
70
+
71
+
72
+ async def main_get(request: Request, type_: str, uuid: str) -> "TemplateResponse": # type: ignore[name-defined]
73
+ if type_ not in DBGR_TYPES:
74
+ raise HTTPException(status_code=404, detail=f"type_ {type_} Not Found")
75
+ if not re.fullmatch(UUID_REGEXP, uuid):
76
+ raise HTTPException(status_code=404, detail="Not Found")
77
+ templates = _get_templates(request)
78
+ return templates.TemplateResponse(
79
+ request,
80
+ "dbgr.html",
81
+ context={
82
+ "uuid": uuid,
83
+ "new_version": request.app.state.new_version,
84
+ "type_": type_,
85
+ "settings": request.app.state.settings,
86
+ "theme": request.app.state.theme,
87
+ "handler_name": "MainHandler",
88
+ },
89
+ )
90
+
91
+
92
+ def _debug(fnc: str) -> RedirectResponse:
93
+ multiprocessing.Process(target=run_file, args=(fnc,)).start()
94
+ return RedirectResponse("/", status_code=302)
95
+
96
+
97
+ async def debug_get(fn: str) -> RedirectResponse:
98
+ return _debug(fn)
99
+
100
+
101
+ async def debug_post_form(
102
+ fn: str = "",
103
+ debug_file: Optional[str] = Form(None),
104
+ ) -> RedirectResponse:
105
+ target = debug_file or fn
106
+ if target:
107
+ return _debug(target)
108
+ raise HTTPException(status_code=400, detail="No file specified")
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # WebSocket handlers
113
+ # ---------------------------------------------------------------------------
114
+
115
+ class BaseWebSocketHandler:
116
+ uuid: str = UNKNOWN_UUID
117
+
118
+ def __init__(self, ws: WebSocket) -> None:
119
+ self.ws = ws
120
+
121
+ async def write_message(self, message: str) -> None:
122
+ await self.ws.send_text(message)
123
+
124
+ async def write(self, message: str) -> None:
125
+ raise NotImplementedError
126
+
127
+ async def on_open(self) -> None:
128
+ raise NotImplementedError
129
+
130
+ async def on_message(self, message: str) -> None:
131
+ raise NotImplementedError
132
+
133
+ async def close(self) -> None:
134
+ try:
135
+ await self.ws.close()
136
+ except Exception: # pylint: disable=broad-except
137
+ pass
138
+
139
+ async def on_close(self) -> None:
140
+ raise NotImplementedError
141
+
142
+ async def get(self) -> None:
143
+ await self.ws.accept()
144
+ await self.on_open()
145
+ try:
146
+ while True:
147
+ try:
148
+ data = await self.ws.receive_text()
149
+ await self.on_message(data)
150
+ except WebSocketDisconnect:
151
+ break
152
+ finally:
153
+ await self.on_close()
154
+
155
+
156
+ class SyncWebSocketHandler(BaseWebSocketHandler):
157
+ async def write(self, message: str) -> None:
158
+ log.debug("server -> syncsocket: %s", message)
159
+ await self.write_message(message)
160
+
161
+ async def on_open(self) -> None:
162
+ self.uuid = str(uuid4())
163
+ await syncwebsockets.add(self.uuid, self)
164
+ if not LibPythonWatcher:
165
+ await syncwebsockets.send(self.uuid, "StartLoop")
166
+
167
+ # pylint: disable=too-many-branches
168
+ async def on_message(self, message: str) -> None:
169
+ if "|" in message:
170
+ cmd, data = message.split("|", 1)
171
+ else:
172
+ cmd, data = message, ""
173
+
174
+ if cmd == "ListSockets":
175
+ for uuid in sockets.uuids:
176
+ await syncwebsockets.send(
177
+ self.uuid,
178
+ "AddSocket",
179
+ {
180
+ "uuid": uuid,
181
+ "filename": sockets.get_filename(uuid)
182
+ if settings.show_filename
183
+ else "",
184
+ },
185
+ )
186
+ elif cmd == "ListWebsockets":
187
+ for uuid in websockets.uuids:
188
+ await syncwebsockets.send(self.uuid, "AddWebSocket", uuid)
189
+ elif cmd == "ListBreaks":
190
+ for brk in breakpoints.get():
191
+ await syncwebsockets.send(self.uuid, "AddBreak", brk)
192
+ elif cmd == "RemoveBreak":
193
+ brk = json.loads(data)
194
+ await breakpoints.remove(brk)
195
+ brk["temporary"] = False
196
+ await sockets.broadcast("Unbreak", brk)
197
+ elif cmd == "RemoveUUID":
198
+ await sockets.close(data)
199
+ await sockets.remove(data)
200
+ await websockets.close(data)
201
+ await websockets.remove(data)
202
+ elif cmd == "ListProcesses":
203
+ await refresh_process(self.uuid)
204
+ elif cmd == "Pause":
205
+ if int(data) == os.getpid():
206
+ log.debug("Pausing self")
207
+ multiprocessing.Process(target=self_shell).start()
208
+ else:
209
+ log.debug("Pausing %s", data)
210
+ command = ["gdb", "-p", data, "-batch"] + [
211
+ f"-eval-command=call {hook}"
212
+ for hook in [
213
+ "PyGILState_Ensure()",
214
+ "PyRun_SimpleString("
215
+ '"import dbgr; dbgr.set_trace(skip=1)"'
216
+ ")",
217
+ "PyGILState_Release($1)",
218
+ ]
219
+ ]
220
+ await asyncio.create_subprocess_exec(
221
+ *command,
222
+ stdin=asyncio.subprocess.PIPE,
223
+ stderr=asyncio.subprocess.PIPE,
224
+ )
225
+ elif cmd == "RunFile":
226
+ multiprocessing.Process(target=run_file, args=(data,)).start()
227
+ elif cmd == "RunShell":
228
+ multiprocessing.Process(target=run_shell).start()
229
+
230
+ async def on_close(self) -> None:
231
+ await syncwebsockets.remove(self.uuid)
232
+
233
+
234
+ class WebSocketHandler(BaseWebSocketHandler):
235
+ async def write(self, message: str) -> None:
236
+ log.debug("socket -> websocket: %s", message)
237
+ if message.startswith("BreakSet|") or message.startswith("BreakUnset|"):
238
+ log.debug("Intercepted break")
239
+ cmd, _brk = message.split("|", 1)
240
+ brk = json.loads(_brk)
241
+ if not brk["temporary"]:
242
+ del brk["temporary"]
243
+ if cmd == "BreakSet":
244
+ await breakpoints.add(brk)
245
+ elif cmd == "BreakUnset":
246
+ await breakpoints.remove(brk)
247
+
248
+ await self.write_message(message)
249
+
250
+ async def on_open(self) -> None:
251
+ if self.uuid in websockets.uuids:
252
+ log.warning(
253
+ "Websocket already opened for %s. Closing previous one",
254
+ self.uuid,
255
+ )
256
+ await websockets.send(self.uuid, "Die")
257
+ await websockets.close(self.uuid)
258
+
259
+ if self.uuid not in sockets.uuids:
260
+ log.warning(
261
+ "Websocket opened for %s with no corresponding socket",
262
+ self.uuid,
263
+ )
264
+ await sockets.send(self.uuid, "Die")
265
+ await self.on_close()
266
+ return
267
+
268
+ log.info("Websocket opened for %s", self.uuid)
269
+ await websockets.add(self.uuid, self)
270
+
271
+ async def on_message(self, message: str) -> None:
272
+ log.debug("websocket -> socket: %s", message)
273
+ if message.startswith("Broadcast|"):
274
+ message = message.split("|", 1)[1]
275
+ await sockets.broadcast(message)
276
+ else:
277
+ await sockets.send(self.uuid, message)
278
+
279
+ async def on_close(self) -> None:
280
+ log.info("Websocket closed for %s", self.uuid)
281
+ if not settings.detached_session:
282
+ await sockets.send(self.uuid, "Close")
283
+ await sockets.close(self.uuid)
284
+
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # FastAPI WebSocket endpoints
288
+ # ---------------------------------------------------------------------------
289
+
290
+ async def status_ws(websocket: WebSocket) -> None:
291
+ handler = SyncWebSocketHandler(websocket)
292
+ await handler.get()
293
+
294
+
295
+ async def websocket_ws(websocket: WebSocket, uuid: str) -> None:
296
+ handler = WebSocketHandler(websocket)
297
+ handler.uuid = uuid
298
+ await handler.get()
@@ -0,0 +1,105 @@
1
+ #!python
2
+ # pylint: disable=missing-function-docstring
3
+
4
+ # Stdlib:
5
+ import asyncio
6
+ import argparse
7
+ from logging import DEBUG, INFO, WARNING, getLogger
8
+
9
+ # Thirdparty:
10
+ import uvicorn
11
+
12
+ # Firstparty:
13
+ from dbgr_server.app import init_app
14
+
15
+
16
+ def main() -> None:
17
+ parser = argparse.ArgumentParser()
18
+ parser.add_argument(
19
+ "--debug", action="store_true", help="Debug mode (default False)"
20
+ )
21
+ parser.add_argument(
22
+ "--extra-search-path",
23
+ action="store_true",
24
+ help=(
25
+ "Try harder to find the 'libpython*' shared library at the cost "
26
+ "of a slower server startup. (default False)"
27
+ ),
28
+ )
29
+ parser.add_argument(
30
+ "--more",
31
+ action="store_true",
32
+ help="Set the debug more verbose (default False)",
33
+ )
34
+ parser.add_argument(
35
+ "--detached_session",
36
+ action="store_true",
37
+ help="Whether to continue program on browser close (default False)",
38
+ )
39
+ parser.add_argument(
40
+ "--show-filename",
41
+ action="store_true",
42
+ help="Whether to show filename in session list (default False)",
43
+ )
44
+ parser.add_argument(
45
+ "--server-host",
46
+ type=str,
47
+ default="localhost",
48
+ help="Host used to serve debugging pages (default localhost)",
49
+ )
50
+ parser.add_argument(
51
+ "--server-port",
52
+ type=int,
53
+ default=1984,
54
+ help="Port used to serve debugging pages (default 1984)",
55
+ )
56
+ parser.add_argument(
57
+ "--socket-host",
58
+ type=str,
59
+ default="localhost",
60
+ help="Host used to communicate with dbgr instances (default localhost)",
61
+ )
62
+ parser.add_argument(
63
+ "--socket-port",
64
+ type=int,
65
+ default=19840,
66
+ help="Port used to communicate with dbgr instances (default 19840)",
67
+ )
68
+ args = parser.parse_args()
69
+
70
+ log = getLogger("dbgr_server")
71
+ if args.debug:
72
+ log.setLevel(INFO)
73
+ if args.more:
74
+ log.setLevel(DEBUG)
75
+ else:
76
+ log.setLevel(WARNING)
77
+
78
+ try:
79
+ import uvloop # pylint: disable=import-outside-toplevel
80
+ uvloop.install()
81
+ except ImportError: # pragma: no cover
82
+ pass
83
+
84
+ settings = {
85
+ "debug": args.debug,
86
+ "extra_search_path": args.extra_search_path,
87
+ "more": args.more,
88
+ "detached_session": args.detached_session,
89
+ "show_filename": args.show_filename,
90
+ "socket_host": args.socket_host,
91
+ "socket_port": args.socket_port,
92
+ }
93
+
94
+ app = asyncio.run(init_app(settings))
95
+
96
+ uvicorn.run(
97
+ app,
98
+ host=args.server_host,
99
+ port=args.server_port,
100
+ loop="none",
101
+ )
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()