Skip to main content

judb

CI

A browser-based visual Python debugger for datascience - debug any Python module or function, with the full power of jupyter notebooks.

judb uses pudb-style stepping with a notebook-style rich console that executes cells in the currently-paused stack frame. Plot and inspect a paused frame's real objects — DataFrames, arrays, xarray Datasets, matplotlib figures — the way you would in a notebook.

Status

Early prototype.

Try it out

Requires Python ≥ 3.13.

pip install judb          # or, in a checkout: uv sync

Drop judb.set_trace() where you want to pause:

import judb

def analyze(df):
    judb.set_trace()      # opens a browser tab, paused on the next line
    return df.describe()

Run your script normally. A browser tab opens at the paused frame with four panes — Source, a notebook console, Variables, and the Call stack. Step with the toolbar (Continue / Next / Step / Return); in the console, type Python that runs in the paused frame:

df                        # rich HTML table
df["x"].rolling(5).mean() # any expression, evaluated in-frame
import matplotlib.pyplot as plt; plt.plot(df["x"])   # inline figure

The console is a real notebook: cells are editable and re-runnable, and can be added, deleted, and reordered. You can also wire judb up as the breakpoint() hook, no code change needed:

PYTHONBREAKPOINT=judb.set_trace python your_script.py

Or run a whole script or module under judb without touching it (stops at the first line):

python -m judb your_script.py [args...]
python -m judb -m your.pipeline.module [args...]

Debugging a failing test

Point pytest's post-mortem debugger at judb, and a failing test drops you into the browser UI paused at the failure, with the console live in that frame:

pytest --pdb --pdbcls=judb:Debugger

Inspect the assertion's operands, plot the offending array, poke at locals — all in the frame where the test blew up. Hit Continue to move on.

With --pdbcls=judb:Debugger set, pytest's other entry points reach judb too — --trace breaks at the first line of every test, and a breakpoint() inside a test opens the UI there:

pytest --trace --pdbcls=judb:Debugger      # break at the start of each test
pytest --pdbcls=judb:Debugger              # honour breakpoint() in a test

Prefer a ready-made demo? From a checkout:

uv run python scripts/demo_p2.py     # a small paused frame with an array to plot

uv sync --extra example              # heavier viz libs (xarray/plotly/bokeh/altair/polars)
uv run python scripts/demo_rich.py   # a spread of rich objects to inspect

Interactive plots (zoom & pan)

By default a plot renders as a static inline PNG. For interactive figures — zoom, pan, the full matplotlib toolbar, live while paused — run this once in the console, then plot as usual:

%matplotlib judb                     # judb's interactive backend
import matplotlib.pyplot as plt
plt.plot(signal)                     # a live, zoomable/pannable canvas

Switch back to static images at any time with %matplotlib inline. Standard IPython magics work too (%timeit, %who, %%time, %matplotlib inline, …).

Under the hood this is matplotlib's own WebAgg engine (the same one behind %matplotlib notebook) driven over judb's connection — no Jupyter kernel required. Interactivity is live whenever the debuggee is paused and freezes on Continue, since the figure lives in the paused frame.

Threads and processes

judb debugs one paused frame at a time. Where that frame lives changes what works:

Where set_trace() runs Status
The main thread (a normal script, python -m judb, pytest) Fully supported
A single worker thread Supported, with two caveats below
Two threads pausing at the same time Not supported — see below
A child process (multiprocessing, fork, spawn) Supported — each process gets its own UI

A single worker thread pauses, shows its frame, and runs console cells in it exactly as the main thread does. Two things degrade, both because only the main thread can receive signals:

  • Interrupt falls back from a real SIGINT to Python's async-exception API, which lands at the next bytecode. It still stops a Python loop, but it cannot break out of a blocking call like time.sleep until that call returns.
  • Ctrl+C in the terminal will not end the program. The KeyboardInterrupt is delivered to the main thread, while the paused worker keeps waiting; if it is a non-daemon thread the process will not exit. Use the UI's Quit, or kill the process.

Two threads pausing concurrently is not supported yet (real multi-thread debugging is planned for a later phase). It does not crash, but: only the most recent pause is visible, the other paused thread is invisible while still blocked, and each Continue releases an arbitrary one of them. If your debuggee is multi-threaded, set a breakpoint that only one thread can reach.

Child processes each start their own server on their own port and print their own URL, so you get one browser tab per paused process. This holds for fork too: a forked child does not reuse its parent's server, since the threads running it do not survive the fork.

License

Copyright 2026 Mika Pflüger. Licensed under the Apache License 2.0 (see also NOTICE).

judb's debugger architecture and design borrow heavily from PuDB (MIT/X Consortium license). PuDB's license and attribution notice are reproduced in licenses/pudb-LICENSE.txt. Rich-output CSS is partly derived from Project Jupyter (BSD-3-Clause; see licenses/jupyter-LICENSE.txt). The interactive plotting backend reuses Matplotlib's WebAgg engine and serves its client JS and toolbar assets (BSD-compatible license; see licenses/matplotlib-LICENSE.txt).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

judb-0.1.0.tar.gz (371.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

judb-0.1.0-py3-none-any.whl (219.4 kB view details)

Uploaded Python 3

File details

Details for the file judb-0.1.0.tar.gz.

File metadata

  • Download URL: judb-0.1.0.tar.gz
  • Upload date:
  • Size: 371.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for judb-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0e62ad81e11f41adb18fc6f29fd4f07271b96771528340d3cf3029a97a60db44
MD5 5cfd7b549d6adc3ecf88a51ef2a4235d
BLAKE2b-256 f54da40495aa07b0d252b8d33bba71a5a54975966b49633d30b1a81657fa3053

See more details on using hashes here.

Provenance

The following attestation bundles were made for judb-0.1.0.tar.gz:

Publisher: release.yml on mikapfl/judb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file judb-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: judb-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 219.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for judb-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a73c555fa226a2fdc522036de13f31d3a3b4b346272be45d46b359fef403a474
MD5 a6456d2c30575e36d5fce4961e0632e5
BLAKE2b-256 bcfc9300dfa77b05346d175933d649a84832810ce7bf1d729402ce130021c534

See more details on using hashes here.

Provenance

The following attestation bundles were made for judb-0.1.0-py3-none-any.whl:

Publisher: release.yml on mikapfl/judb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.3.0

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page