Implement invite-token registration with optional email delivery, add admin UI actions for creating invites and opening user workspaces, and support superuser workspace override while preserving per-user code isolation with shared read-only lib. Made-with: Cursor
5.8 KiB
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).
-
Install Google Chrome or Chromium on the machine (Selenium 4 uses Selenium Manager to resolve a matching driver).
-
In one terminal, start the app (default
http://127.0.0.1:8080):pipenv run dev -
In another terminal:
pipenv run test-seleniumIf 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 Pythonruns the active open.pytab.- Enable
Run main.pyto always runcode/main.pyinstead. - Pressing
Run Pythonwhile a script is running will stop and restart with the selected target. LSPbadge 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/workspaceso your code persists locally.data/is mounted to/app/datafor the SQLite auth DB.- In container mode,
WORKSPACE_ROOTandAUTH_DATABASE_PATHare set bydocker-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 (if AUTH_REGISTER_OPEN=true) or BOOTSTRAP_ADMIN_USERNAME / BOOTSTRAP_ADMIN_PASSWORD for the first superuser. Superusers can GET/POST/DELETE /api/users to list, create, or remove accounts.
Email invite signup:
- Superusers can create invites via
POST /api/users/inviteswith{ "email": "...", "expires_days": 7 }. - Response includes
invite_url; if SMTP is configured the invite email is sent automatically. - Set
AUTH_INVITE_ONLY=trueto require invite tokens for all registrations. - 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.
Layout
src/— FastAPI app and static UI (src/static/)workspace/— default tree:code/(editable),lib/(read-only via API)
ESP32 / NeoPixel mock
The browser runtime now includes MicroPython-style mocks in workspace/lib:
machine.Pinneopixel.NeoPixel
Use them from scripts in workspace/code exactly like ESP32 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 panelcheckbox: 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 tutorialworkspace/code/led_tutorial.py- runnable guided LED exampleworkspace/code/led_patterns.py- reusable pattern helpers (rainbow_frame,chase_frame,twinkle_frame)workspace/code/pattern_rainbow_demo.py- rainbow animation demoworkspace/code/pattern_chase_demo.py- chase animation demoworkspace/code/pattern_twinkle_demo.py- twinkle animation demoworkspace/code/panel16_utils.py- helpers for 16x16 serpentine mappingworkspace/code/panel16_rainbow_wave.py- 16x16 rainbow waveworkspace/code/panel16_bounce.py- 16x16 bouncing pixel with trailworkspace/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.