Skip to content

Playground: add-file support (client-side, no storage)

Date: 2026-07-01 Status: Design approved, not yet implemented. Pick up here next session.

Problem

The files & exceptions Python chapter teaches reading files, but the playground had no way for a learner to bring a real file into the runtime. Concern was that this would need a server upload / database / size limits. It does not.

Key fact that makes this simple

The playground (docs/.vitepress/theme/components/PythonPlayground.vue) runs Pyodide — CPython on WebAssembly, entirely in the browser tab. Pyodide ships an in-memory virtual filesystem (MEMFS). So:

  • open(), read/write, with, try/except, FileNotFoundError already work today with zero changes — files live in browser RAM, never touch a server or DB.
  • "Upload" here means browser-memory only. Nothing leaves the tab. No storage, no privacy surface, no real need for a size limit beyond protecting RAM.
  • Pyodide's default cwd is /home/pyodide, which is what open('x.txt') resolves against.
  • _pyodide is module-level, so the FS already persists for the whole tab session (across runs and across closing/reopening the modal); only a hard reload clears it.

Decisions (locked)

  • What: let learners add their own file(s) from their computer into the FS.
  • Placement: global playground feature (an "Add file" button in PythonPlayground.vue near the run controls), available from every chapter.
  • File count: multiple files coexist, shown as a chip list with remove (×).
  • Size cap: 1 MB per file; oversize rejected with a friendly inline message.
  • Persistence: in-memory only. Nothing written to disk/IndexedDB (safest, matches "nothing is stored" principle; within-session persistence already comes free from the module-level Pyodide instance). Re-upload after a hard reload.

Architecture

Source of truth = a JS array, not the FS. Keep workspace files as a reactive array of { name, bytes } in PythonPlayground.vue. Right before the existing exec step in the run logic, loop the array and pyodide.FS.writeFile(name, bytes).

Why: a learner can add a file before Pyodide has loaded, the file survives a Pyodide re-init, and the run path stays simple and ordering-bug-free.

Changes

  • PythonPlayground.vue (only file touched):
    • hidden <input type="file" multiple> + an "Add file" button near run controls
    • reactive files array of { name, bytes }
    • chip list of current files, each with a remove (×) button
    • on pick: File.arrayBuffer()Uint8Array → push to files[]
    • on run (before exec): for each file, pyodide.FS.writeFile('/home/pyodide/'+name, bytes)
    • on remove: drop from array and pyodide.FS.unlink(path) if present
  • No new files. No markdown changes. Chapter content unchanged (examples can now say "add a file, then read it").

Guardrails

  • Filename sanitized to basename only (strip / and ..) — nothing escapes cwd.
  • Size cap 1 MB per file; oversize rejected, never written.
  • Bytes not text — write Uint8Array so open(f) (text) and open(f,'rb') (binary) both work.
  • Duplicate name replaces the existing entry (no silent double-write).

Data flow

pick file(s) → arrayBuffer()Uint8Array → push to files[] → chip appears → on Run each file written to FS → learner's open(...) reads it → output shown. On reload, files[] and the FS both reset.

Error handling

  • oversize / unreadable file → inline notice, file not added.
  • reading a file the learner did not add → normal Python FileNotFoundError (a teachable moment for the exceptions half of the chapter).

Test checklist

  • add a file, read it back
  • add two files, read both
  • remove one, then run
  • oversize file rejected with message
  • binary file read via 'rb'
  • hard reload clears everything

Next step

Turn this into an implementation plan (writing-plans skill), then implement in PythonPlayground.vue. Relevant run logic is around lines 255-300 of that file (the exec/stream-reset block).

© 2026 Scrimba