Skip to content
Merged
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
33 changes: 24 additions & 9 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,31 @@ The debugger backend is a two-threaded system:

- **Debugger thread**: Runs the WebPdb class (extends Python's `Pdb`), which executes user code and
handles debug commands.
- **Web server thread**: Runs a Bottle-based WSGI application that serves the web UI and communicates
with the debugger thread via WebSockets.
- **Web server thread**: Runs a pure-stdlib asyncio HTTP+WebSocket server that serves the web UI
and communicates with the debugger thread via a shared queue.

**Key modules:**

- `web_pdb/__init__.py`: Main `WebPdb` class extending `Pdb`, plus module-level functions
(`set_trace()`, `post_mortem()`, `catch_post_mortem()`). Handles command dispatch, variable
formatting, and the custom `inspect`/`i` command.
- `web_pdb/web_console.py`: File-like class that serves as stdin/stdout for the debugger thread.
Manages the WebSocket server, handles bidirectional communication between debugger and web UI.
- `web_pdb/wsgi_app.py`: Bottle application serving the web UI and API endpoints for debugger
control and frame data retrieval.
Delegates server management and WebSocket broadcasting to `ServerAdapter`. Maintains
`console_history` buffer and frame data, pinging clients on each write.
- `web_pdb/server_adapter.py`: Thin facade over `AsyncioServer`. `ServerAdapter` owns the
`input_queue` and `frame_data` buffer, starts the asyncio event loop in a daemon thread via
`serve_forever()`, and exposes `web_socket_broadcast()` and `web_socket_input_queue` to
`WebConsole`. Shutdown is coordinated via `SystemAdapter.abort()` and `AsyncioServer.stop()`.
- `web_pdb/asyncio_server.py`: Pure-stdlib asyncio HTTP/WebSocket server. `AsyncioServer` handles
all HTTP routing (index, `/frame-data`, `/static/`, `/ws`), WebSocket handshake and framing, and
gzip compression. `_WebSocketConnection` manages per-connection send/receive coroutines and feeds
incoming PDB commands into the shared `input_queue`. Active connections are tracked in
`AsyncioServer._connections` (a `set`).
- `web_pdb/system_adapter.py`: Abstraction layer for running in a standard Python environment vs.
a Kodi addon. Exposes `SystemAdapter` (alias to `_GeneralAdapter` or `_KodiAdapter` depending on
whether the Kodi runtime is detected). Both implement `is_abort_requested()`,
`on_server_started()`, `on_server_stopped()`, and `on_exception()`. The Kodi variant uses
`xbmc.Monitor` for abort detection and shows progress dialogs/notifications.
- `web_pdb/buffer.py`: Thread-safe buffer (`ThreadSafeBuffer`) used for passing data between
threads with dirty-flag semantics.

Expand Down Expand Up @@ -106,11 +119,13 @@ pip install .
## Threading Model

The debugger maintains one active instance (`WebPdb.active_instance`) that traces one thread at a
time. The WebConsole spawns a daemon thread to run the web server. Thread safety is achieved via:
time. `WebConsole` spawns a daemon thread that runs `ServerAdapter.serve_forever()`. Thread safety
is achieved via:

- `ThreadSafeBuffer` with RLock for console history and frame data.
- `queue.Queue` for PDB commands from the web UI (thread-safe by design).
- WebSocket deque for sending messages to clients (thread-safe for appending).
- `queue.Queue` (instance-level on `ServerAdapter`) for PDB commands from the web UI.
- `asyncio.Queue` per `_WebSocketConnection` for outbound WebSocket messages (asyncio-internal).
- `threading.Event` in `_BaseAdapter` (`SystemAdapter`) for coordinating server shutdown.

## Testing Notes

Expand All @@ -132,7 +147,7 @@ time. The WebConsole spawns a daemon thread to run the web server. Thread safety
## Requirements

- **Python**: 3.6+
- **Core dependencies**: `bottle>=0.12.25`, `asyncore-wsgi>=0.0.11`
- **Core dependencies**: pure Python stdlib (no third-party runtime dependencies)
- **Development dependencies**: `ruff==0.15.12`, `selenium==4.10.0`
- **Package manager**: `uv`

Expand Down
7 changes: 4 additions & 3 deletions Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Changelog
v.2.0.0
=======

* Fully reworked UI.
* Internal changes.
* Fully reworked web-UI: light and dark modes, drop outdated dependencies.
* Internal changes: a new async web-server, removed external runtime dependencies,
unified code with ``kodi.web-pdb`` project.

v.1.6.3
=======
Expand Down Expand Up @@ -36,7 +37,7 @@ v.1.5.6
v.1.5.3
=======

* Fixed the issue with closed debugger still being stored in ``active_instance``
* Fixed the issue with a closed debugger still being stored in ``active_instance``
class property that prevented starting a new debugger session (thanks to **maiamcc**).

v.1.5.2
Expand Down
2 changes: 0 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
uv
wheel
build>=1.0.0
bottle>=0.12.25
asyncore-wsgi>=0.0.11
selenium==4.10.0
ruff==0.15.12
3 changes: 0 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ license = MIT License
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Web Environment
Framework :: Bottle
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Expand All @@ -28,8 +27,6 @@ zip_safe = False
include_package_data = True
python_requires = >=3.8
install_requires =
bottle>=0.12.25
asyncore-wsgi>=0.0.11
test_suite = tests.tests
tests_require =
selenium==4.10.0
4 changes: 1 addition & 3 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# coding: utf-8
# Created on: 03.10.2016
# Author: Roman Miroshnychenko aka Roman V.M. (romanvm@yandex.ua)


# Author: Roman Miroshnychenko aka Roman V.M.
2 changes: 1 addition & 1 deletion tests/db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# coding: utf-8
# Created on: 13.09.2016
# Author: Roman Miroshnychenko aka Roman V.M. (romanvm@yandex.ua)
# Author: Roman Miroshnychenko aka Roman V.M.

import os
import sys
Expand Down
2 changes: 1 addition & 1 deletion tests/db_pm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# coding: utf-8
# Created on: 16.09.2016
# Author: Roman Miroshnychenko aka Roman V.M. (romanvm@yandex.ua)
# Author: Roman Miroshnychenko aka Roman V.M.


import os
Expand Down
17 changes: 9 additions & 8 deletions tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Created on: 13.09.2016
# Author: Roman Miroshnychenko aka Roman V.M. (romanvm@yandex.ua)
# Author: Roman Miroshnychenko aka Roman V.M.
#
# Copyright (c) 2016 Roman Miroshnychenko
#
Expand Down Expand Up @@ -64,13 +64,14 @@ def tearDownClass(cls):
cls.db_proc.kill()
cls.browser.quit()

def tearDown(self):
if hasattr(self, '_outcome'):
result = self._outcome.result
if result.failures:
failed_tests = [test for test, _ in result.failures]
if self in failed_tests:
self.browser.save_screenshot(f'screenshot-{self}.png')
def run(self, result=None):
outcome = super().run(result)
if result is not None:
failed = [t for t, _ in result.failures + result.errors]
if self in failed:
self.browser.save_screenshot(str(CWD.parent / f'screenshot-{self}.png'))
return outcome


class WebPdbTestCase(SeleniumTestCase):
"""
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web_pdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from pdb import Pdb
from pprint import pformat

from .adapter import SystemAdapter
from .system_adapter import SystemAdapter
from .web_console import WebConsole

__all__ = ['WebPdb', 'set_trace', 'post_mortem', 'catch_post_mortem']
Expand Down
Loading
Loading