Files
python-editor/README.md
Jimmy ca0ca6fe7e Add local-mode workspace, ZIP import/export, and richer pin/ADC/serial sims
Boot:
- Editor now picks local vs server mode based on URL flag, sign-in
  state, and a stale local-mode flag. Signed-in users are no longer
  bounced to IndexedDB if they had previously clicked "Use locally".

Local mode:
- New LocalWorkspaceClient (src/static/local-workspace.js) with
  pluggable IndexedDB and File System Access backends. Picked folder
  handles persist across reloads with a Reconnect button when the
  permission lapses.
- Static-only host: scripts/serve_static_editor.py serves src/static/
  with COOP/COEP so SharedArrayBuffer-backed sims keep working.
- Bundled MicroPython stubs ship under src/static/bundled-lib/ for
  static hosting; FastAPI also exposes them at /api/public/lib-bundle.

Workspace import / export:
- Zero-dep ZIP encoder + reader (STORE + DEFLATE via
  DecompressionStream). Export/Import buttons in the workspace badge
  work in both local and server modes; imports are confined to code/.

Pin / ADC / Serial simulation:
- machine.py grows ADC, UART, expanded Pin, and PWM mocks, all driven
  by SharedArrayBuffer when cross-origin isolated and falling back to
  postMessage + [pin-out] stdout markers otherwise — pins, ADC slider,
  and serial input now keep working over plain HTTP / LAN-IP origins.
- NeoPixel pins are claimed via a [pin-claim] marker and dropped from
  the Pins panel so the data line doesn't flicker per write().
- New demos: adc_slider_demo.py, pin_demo.py, serial_demo.py.

Lib layout:
- Single source of truth at repo lib/; workspace/lib/ caching layer
  removed and the directory deleted. Filesystem service reads stubs
  directly from PROJECT_ROOT/lib.

UI:
- Home page slimmed to "Sign in" + "Use locally" with optional editor
  / manage-users links. Admin user/invite UI moved to /users.
- Workspace badge gains storage indicator, Folder…/Reconnect, Export,
  Import, and Exit controls.
- Mobile-friendly tweaks: safer-area padding, larger touch targets,
  iOS-zoom-proof serial input, file-tree highlight fix.

Tests:
- test_auth.py patches PROJECT_ROOT for the lib-shared test so the
  repo-root lib refactor stays green. test_api.py asserts the new
  "LED Editor" branding.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 06:16:02 +12:00

8.9 KiB
Raw Blame History

python-editor

Browser-based Python editing: FastAPI serves static assets, stores workspace files, and optional API key auth. Pyodide runs your scripts and Jedi (inside Pyodide) powers completions and syntax diagnostics — no server-side Python execution or LSP process.

Run

cp .env.example .env   # optional: set WORKSPACE_ROOT, EDITOR_API_KEY, etc.
pipenv install
pipenv run dev

Configuration is read from .env at the repo root (see .env.example). Values there are applied when the app loads unless the variable is already set in your shell. Pipenv also loads .env for pipenv run commands.

Tests (includes pytest and selenium in dev dependencies):

pipenv run test
pipenv run test-integration   # Playwright; optional

Selenium

Selenium talks to a real browser against a running server (not the in-process TestClient).

  1. Install Google Chrome or Chromium on the machine (Selenium 4 uses Selenium Manager to resolve a matching driver).

  2. In one terminal, start the app (default http://127.0.0.1:8080):

    pipenv run dev
    
  3. In another terminal:

    pipenv run test-selenium
    

    If the app listens elsewhere, set SELENIUM_BASE_URL (e.g. http://127.0.0.1:9000) before running.

    Or run only Selenium-marked tests:

    cd src && PYTHONPATH=. pipenv run pytest ../tests -m selenium -v
    

If nothing is listening, the smoke test skips with a short message instead of failing.

Open http://localhost:8080.

Editor runtime controls

  • Run Python runs the active open .py tab.
  • Enable Run main.py to always run code/main.py instead.
  • Pressing Run Python while a script is running will stop and restart with the selected target.
  • LSP badge in the header shows in-browser Jedi syntax status (n/a, checking..., OK, or issue count).

Deploy with Docker

Build and run with Docker Compose:

cp .env.example .env
mkdir -p data
docker compose up --build

Then open http://localhost:8080.

Notes:

  • workspace/ is mounted to /app/workspace so your code persists locally.
  • data/ is mounted to /app/data for the SQLite auth DB.
  • In container mode, WORKSPACE_ROOT and AUTH_DATABASE_PATH are set by docker-compose.yml.

User accounts — Set AUTH_ENABLED=true in .env to require sign-in for workspace APIs. Users live in a SQLite file (AUTH_DATABASE_PATH, default ./data/editor.db). Use /register with an invite link (unless you opt into open signup) or BOOTSTRAP_ADMIN_USERNAME / BOOTSTRAP_ADMIN_PASSWORD for the first superuser. Superusers can GET /api/users, PATCH /api/users/{id} (username, password reset, admin flag — renames workspace folder when the username changes), or DELETE /api/users/{id} to manage accounts. New accounts are added only through invite links (POST /api/users/invites) plus self-service registration (/register?invite=…).

Email invite signup:

  • By default AUTH_INVITE_ONLY=true: registrations need a valid invite token. Set AUTH_INVITE_ONLY=false to allow open signup whenever AUTH_REGISTER_OPEN=true.
  • Superusers can create invites via POST /api/users/invites with { "email": "...", "expires_days": 7 }.
  • Response includes invite_url; if SMTP is configured the invite email is sent automatically.
  • Registration page accepts invite links like /register?invite=<token>.

When auth is enabled, file APIs use a per-user workspace under WORKSPACE_ROOT/users/<username-id>/ for isolated code/. The lib/ tree is shared and read-only for all users. When auth is disabled, the shared workspace root is used for everything.

Admins can open another user's workspace from the home page user management panel (links to /editor?workspace_user_id=<id>). Only superusers may use this override.

API key — If EDITOR_API_KEY is set, requests may use Authorization: Bearer … instead of a session (useful for automation). When AUTH_ENABLED=true, a valid session or API key is accepted.

The home page can store the API key in sessionStorage when you are not using cookie login, or use ?api_key= on /editor.

Local mode (no login) — Click Use locally on the home page (or open /editor?local=1) to run the editor without any FastAPI auth. The boot-time auth probe is skipped when local mode is active, so this works even on a host that has AUTH_ENABLED=true. Files default to the browser's IndexedDB; inside the editor the workspace badge has a Folder… button that opens window.showDirectoryPicker() so you can save straight to any folder on disk (Chromium-only — Firefox/Safari stay on IndexedDB). The picked directory handle is persisted across reloads in IndexedDB; if browser permission lapses a Reconnect button reappears in the badge. Nothing is sent to the server for file reads/writes. The MicroPython stubs are loaded from /static/bundled-lib/*.py (files under src/static/bundled-lib/ in the repo) so a plain static file server is enough; if those requests fail, the app falls back to GET /api/public/lib-bundle when FastAPI is available. For static-only hosting, run python scripts/serve_static_editor.py from the repo root — it serves src/static/ with the same /static/… URLs the HTML expects (it strips the /static prefix when resolving files), rewrites /editorindex.html, and sends the same COOP/COEP headers as the full app so ADC sliders, pin toggles, and serial I/O keep using SharedArrayBuffer on mobile Safari and Chrome where supported. An Exit button in the editor's workspace badge clears the local-mode flag (your IndexedDB files stay until you wipe browser storage).

Layout

  • src/ — FastAPI app and static UI (src/static/)
  • lib/ — bundled MicroPython stubs, served read-only as lib/ in the editor and merged into Pyodide at run time (single source of truth)
  • workspace/ — default WORKSPACE_ROOT: code/ samples and per-user folders (editable); the editor surfaces the repo lib/ alongside it without copying anything to disk

ESP32 / NeoPixel mock

The browser runtime ships MicroPython-style stubs in repo lib/ (they appear as lib/ in the editor and are read-only via the APIs):

  • machine.Pinvalue/on/off/toggle/high/low/init/__call__/irq plus a live "Pins" panel: OUT pins show an indicator, IN pins expose a clickable toggle button (its value is what Pin.value() returns), irq() fires on rising / falling edges as you click
  • machine.PWMfreq() / duty() / duty_u16() / duty_ns() with a duty-cycle bar in the Pins panel
  • machine.ADC — backed by a live slider in the editor UI (one slider per pin, read_u16() returns 0..65535)
  • machine.UART — opens a Serial Monitor pane; write() text appears there, what you type is delivered via read() / readline()
  • neopixel.NeoPixel
  • utimeticks_ms, ticks_diff, ticks_add, sleep_ms, sleep_us, sleep
  • micropython.const — no-op helper for ported constant declarations

Use them from scripts in workspace/code like typical ESP32 / MicroPython examples:

from machine import Pin
import neopixel

np = neopixel.NeoPixel(Pin(4), 8)
np[0] = (255, 0, 0)
np.write()

write() updates the NeoPixel simulator so you can verify behavior visually.

Simulator modes:

  • Default: in-app LED strip/panel section under the editor.
  • 16x16 panel checkbox: opens a dedicated popup with 16x16 serpentine mapping:
    • first LED at top-right
    • first row goes right -> left
    • rows zig-zag left/right.
  • The 16x16 popup closes automatically on Stop or when script execution finishes.

Tutorial files:

  • LED_TUTORIAL.md - step-by-step NeoPixel tutorial
  • workspace/code/led_tutorial.py - runnable guided LED example
  • workspace/code/led_patterns.py - shared pattern helpers (used by automated tests); each pattern_*_demo.py duplicates what it needs and uses only Python stdlib + machine / neopixel / time
  • workspace/code/pattern_rainbow_demo.py - rainbow animation (self-contained)
  • workspace/code/pattern_chase_demo.py - Knight Riderstyle bouncing scanner (self-contained)
  • workspace/code/pattern_twinkle_demo.py - twinkle animation (self-contained)
  • workspace/code/panel16_utils.py - helpers for 16x16 serpentine mapping
  • workspace/code/panel16_rainbow_wave.py - 16x16 rainbow wave
  • workspace/code/panel16_bounce.py - 16x16 bouncing pixel with trail
  • workspace/code/panel16_matrix_rain.py - 16x16 matrix rain effect

Dev auto-reload hook

Project hook files are included in .cursor/:

  • .cursor/hooks.json
  • .cursor/hooks/dev-reload-touch.sh

When files are edited through Cursor tools, the hook updates src/static/.reload-token. The editor (on localhost) polls that token and auto-reloads the browser when it changes.