Developer workflow only — local servers, test harnesses, and IDE typings. For repo maintenance see scripts/README.md.
| Script | Purpose |
|---|---|
serve.py |
HTTP server with Cross-Origin-Isolation headers |
pyscript.py |
Universal PyScript runner (on PATH from pydevices/bin) — pyscript.py -m bouncing_balls |
jupyter.py |
Standalone Jupyter runner (on PATH from pydevices/bin) — jupyter.py paint.py |
From repo root:
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt # playwright, pytest (optional)
.venv/bin/playwright install chromium # headless PyScript matrix
pyscript.py -m bouncing_balls
jupyter.py lib/examples/paint.pySee pydevices/docs/pyscript.md and pydevices/docs/jupyter.md.
| Script | Purpose |
|---|---|
lvgl_input_probe.py |
LVGL keypad-mapping diagnostic + selftests |
micropython tools/lvgl_input_probe.py --selftest
cd lib && micropython ../tools/lvgl_input_probe.py # interactive; focus the windowThe core displaydev/appdev probe is owned by
pydevices/tools/input_probe.py.
Diagnostic probes (ps_debug.py and ps_shot.py) live in pydevices/tools/:
python ../pydevices/tools/ps_debug.py http://127.0.0.1:8000/pydevices-examples/pyscript/harness.html?modules=bouncing_balls
python ../pydevices/tools/ps_shot.py http://127.0.0.1:8000/pydevices-examples/pyscript/harness.html?modules=bouncing_balls 3Source of truth for the cross-interpreter example test system: this section
(workflow), example_interpreters.toml (interpreter command
templates), and example_test_manifest.toml
(per-example metadata). Platform is the product category (see
pydevices/docs/displaydev.md); interpreter is the
concrete launcher used in automation.
| Script | Purpose |
|---|---|
example_test_kit.py |
Cross-interpreter example matrix |
example_test_manifest.toml |
Per-example metadata |
example_interpreters.toml |
Interpreter command templates |
sibling_repos.py |
Discover sibling lib/ paths for matrix runs |
.venv/bin/python -m unittest discover -s testsFor thorough verification (timer/multimer/interpreter changes, or “run the full
matrix”), prefer example-by-example, all selected interpreters in parallel
per example (--jobs 0, default), --fail-fast, and both
PYDEVICES_TIMER_ASYNC modes as separate kit runs.
| Mode | Interpreters |
|---|---|
Sync (PYDEVICES_TIMER_ASYNC=0) |
5 desktop SDL: micropython, micropython.exe, circuitpython, cpython-venv, python.exe |
Async (PYDEVICES_TIMER_ASYNC=1) |
7 — the five above plus pyscript, jupyter |
| Android (opt-in) | android — pydevices/bin/android.py (or ~/bin/android.py on PATH) + emulator/device + org.pydevices.runner APK; not in the default 5/7 lists (--only-interpreter android) |
Default timing is already short (duration_s=2, timeout_s=15 in the
interpreters/manifest defaults). After each example’s parallel wave finishes, if
any cell failed, stop before the next example; fix the root cause, then resume.
# PyScript needs the static server (async mode)
python tools/serve.py # separate terminal; reuse if already on :8000
export SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy PYTHONUNBUFFERED=1
mkdir -p /tmp/pydevices-examples-matrix
SYNC_RT="micropython micropython.exe circuitpython cpython-venv python.exe"
ASYNC_RT="$SYNC_RT pyscript jupyter"
set -o pipefail # keep kit exit status through tee
# Sync — 5 interpreters concurrently per example
PYDEVICES_TIMER_ASYNC=0 stdbuf -oL -eL \
.venv/bin/python tools/example_test_kit.py --no-unit-tests --fail-fast \
--only-interpreter $SYNC_RT \
--results-json /tmp/pydevices-examples-matrix/sync.json \
2>&1 | stdbuf -oL -eL tee /tmp/pydevices-examples-matrix/sync.log
# After sync is clean — async, all 7
PYDEVICES_TIMER_ASYNC=1 stdbuf -oL -eL \
.venv/bin/python tools/example_test_kit.py --no-unit-tests --fail-fast \
--only-interpreter $ASYNC_RT \
--results-json /tmp/pydevices-examples-matrix/async.json \
2>&1 | stdbuf -oL -eL tee /tmp/pydevices-examples-matrix/async.logLive log lines: Running <example> @ N interpreter(s) in parallel..., then
start / done per interpreter. --fail-fast waits for the current example’s
workers, then exits if any cell failed. Resume with --only-example
(remaining ids) or by restarting that mode from the failed example. Use
--jobs 1 for fully serial interpreters when isolating races. See
Windows PE under WSL for PE window / quit notes.
--curated-only is a smoke shortcut, not a substitute for the preferred gate.
# Curated set across available interpreters (smoke)
.venv/bin/python tools/example_test_kit.py --curated-only
# Scope (space-separated ids on one flag; see note below)
.venv/bin/python tools/example_test_kit.py --only-example calculator --only-interpreter micropython
.venv/bin/python tools/example_test_kit.py --no-unit-tests --only-interpreter cpython-venv micropython
.venv/bin/python tools/example_test_kit.py --no-unit-tests \
--only-example calc_lvgl lv_test_timer --only-interpreter circuitpython
# Order: --order examples (default) / --order interpreters
# Broader: --all-except-harness--only-example and --only-interpreter use nargs="+": pass multiple ids
space-separated after one occurrence of the flag. Repeating the flag
silently keeps only the last list (--only-interpreter circuitpython --only-interpreter python.exe runs just python.exe). Same rule for lv_timer_test_kit.py
--only / --modes.
Headless desktop (dummy SDL — default for matrix/smoke):
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
.venv/bin/python tools/example_test_kit.py --no-unit-tests --only-interpreter cpython-venvUnix subprocesses see that shell export. Windows .exe behavior is different —
see Windows PE under WSL.
Async timers on desktop: the kit forwards PYDEVICES_TIMER_ASYNC as wrapper
--timer-async (uses env_set, works for Windows PE under WSL). Shell export
is the preferred way to select mode for a full kit run (see Preferred method
above). Semantics: App and board config — timer_async.
micropython.exe and python.exe are Windows PE binaries launched from WSL.
They cannot read Linux-exported environment variables. The kit therefore
forwards only values that must cross that boundary via wrapper argv +
displaydev.env_set (notably --timer-async / --multimer-backend).
Do not forward SDL_VIDEODRIVER / SDL_AUDIODRIVER to PE. Unix cells stay
headless from the shell SDL_*=dummy export; PE keeps a real Windows video
driver. During a matrix run you should see micropython.exe / python.exe
windows — that means the cell started and is usable. Forwarding dummy into
PE hides those windows.
summary: hang on PE is usually a quit failure, not a dead process. If the
Windows window stays up past duration_s / until the kit timeout_s, the
example is still running (you can interact with it); the harness timed out
waiting for cooperative quit / EXAMPLE_RESULT. PE child output is captured
via temp files so a timeout kill does not wipe stdout the way pipes often did.
Fix the quit path (wrapper deadline / pydevices_test_mode / inject) rather
than treating PE as “failed to launch.”
Scheduling: with --order examples and --jobs 0 (default), all
selected interpreters for an example — including both .exe launchers — run
concurrently.
Results: live Running <example> @ <interpreter>... lines on stderr; summary
table at end (or when --fail-fast stops). Full JSON defaults to the system
temp dir (example_test_results.json), not a path under the repo. Override
with --results-json PATH.
Real X display: DISPLAY=:1 (xfce) without dummy SDL opens a window titled
"<impl> on <platform>". Optional: xvfb-run -a … (no SDL_VIDEODRIVER=dummy)
for a real X11/SDL path without :1. Do not require Xvfb in the tools scripts;
wrap when useful. PyScript/Playwright does not need Xvfb.
Desktop matrices use repo .venv (cpython-venv) plus interpreters on
PATH / ~/bin (micropython, circuitpython). micropython.exe / python.exe are
Windows binaries and cannot run in a Linux cloud sandbox.
After usermod changes that affect these binaries or PyScript vendor wasm, run cmods/build_interpreters.sh.
micropython.exe matrix: no threading / _thread. The example wrapper
uses a appdev.App.poll deadline quit (not a multimer SDL quit timer). With
pydevices_test_mode.ENABLED, appdev.App skips auto-refresh wiring so examples
that call show() themselves avoid a competing SDL refresh timer. WSL PE
scheduling and SDL env rules:
Windows PE under WSL.
Examples that import palettes / pdwidgets / pygraphics / the ctypes
usdl2 fallback need those sibling lib/ dirs on path. The PyPI project
literally named palettes is unrelated — do not pip install palettes.
Prefer TestPyPI native builds for pygraphics and usdl2 when available.
Quick setup: bash scripts/setup_sibling_repos.sh (clones current main,
writes .pth files). The harness auto-discovers the same paths via
sibling_repos.py. pdwidgets also needs pydevices's lib on path
(the harness adds it).
tools/png_test.pyin pdwidgets (PNG probe) needsPDWIDGETS_PNG_DIR/ material-design-icons and a sibling pydevices-examples checkout.
Start or reuse python tools/serve.py, then re-run with --only-interpreter pyscript.
Headless needs Playwright (.venv/bin/pip install -r requirements-dev.txt and
.venv/bin/playwright install chromium). Without it, pyscript cells report
needs_playwright (not a hard failure). Troubleshooting hangs / CDP:
PyScript headless debug above.
| Script | Purpose |
|---|---|
run_desktop_lv_tests.py |
LVGL desktop matrix (sync/async, strict clicks) |
lv_timer_test_kit.py |
Full LVGL timer matrix (sync/async, all interpreters) |
run_test_timers.py |
Run the sibling core multimer timer probe across desktop interpreters |
multimer_backend_preload.py |
Force one multimer backend, then run a script |
Comparing multimer providers: lv_timer_test_kit.py --backend NAME (or
example_test_kit.py with MULTIMER_BACKEND set, which forwards
--multimer-backend to the wrapper). Both set MULTIMER_BACKEND inside the
child before importing multimer.auto, so they also work for the Windows
.exe interpreters, which cannot read WSL-exported env vars. Interpreters lacking that
provider report unavailable and do not fail the run. See the
multimer automatic-selection documentation.
TestPyPI package smoke tests are owned by the repositories that publish the
packages: core checks live in
pydevices/tools/test_testpypi_standalone.sh,
and pygraphics has its own standalone wheel check.
| Script | Purpose |
|---|---|
quit_inject.py |
Inject quit into running examples (used by the example harness) |
pydevices_test_mode.py |
Test-mode env for examples |
screenshot.py |
Run a desktop example and save its SDL2/pygame-ce window as PNG |
record.py |
Run a desktop example and record its SDL2/pygame-ce window with FFmpeg |
typings/ |
MicroPython stdlib stubs + core package .pyi (see below) |
python tools/screenshot.py hello.py
python tools/screenshot.py bouncing_balls 3
python tools/screenshot.py logo --delay 2 --resolution 320x240 --scale 1Without --output, screenshots are saved as
docs/screenshots/EXAMPLE_NAME.png.
python tools/record.py bouncing_balls
python tools/record.py bouncing_balls 10
python tools/record.py logo --duration 3 --fps 15 --resolution 320x240 --scale 1Without --output, recordings are saved as
docs/videos/EXAMPLE_NAME.mp4. Recording requires ffmpeg on PATH or
the binary-bundled imageio-ffmpeg Python package.
stubPath for Pylance / pyright (.vscode/settings.json, pyrightconfig.json):
| Content | Source |
|---|---|
| MicroPython stdlib stubs | committed under tools/typings/ |
displaydev / appdev / multimer / events / keys |
committed package trees / modules; regenerate with ../scripts/gen_package_pyi.sh |
lvgl |
committed tools/typings/lvgl.pyi (from ../lvgl-bindings/generated/lvgl.pyi) |
Confirm Python: Select Interpreter → .venv/bin/python. Cursor uses cursorpyright with stubPath / typeshedPaths → tools/typings (configured in a local .vscode/settings.json when present).