Architecting PyScript Apps: Dependency Loading, Data Boundaries, and Runtime Trade‑offs
Design a PyScript web app by managing package fetching, respecting Same‑Origin limits, and choosing the right WASM runtime. This guide covers architecture, diagnostics, failure modes, and when to shift to a server‑side solution.
04 Dec 2025, 18:34 UTC

When you move Python logic into the browser with PyScript, the entire execution model shifts from a server‑controlled interpreter to a sandboxed WebAssembly runtime. The core architectural decisions revolve around three constraints: dependency loading time, browser‑enforced data boundaries, and runtime performance overhead. This article outlines a minimal yet robust architecture that satisfies those constraints, shows how to verify each component, and explains the conditions that would force a redesign.
1. Requirements Snapshot
- Execute pure Python code in the browser without exposing a server API.
- Import third‑party packages (NumPy, Pandas, etc.) via a declarative config.
- Respect the Same‑Origin Policy (SOP) and browser file‑access restrictions.
- Detect and recover from network or memory failures during runtime.
- Keep source code and any secrets out of the client bundle.
2. Smallest Suitable Design
The leanest architecture uses Pyodide for full CPython support, a single py-config block for dependency declaration, and a minimal py-script that performs data fetching, processing, and a one‑time DOM update. The design pattern follows:
py-configdeclares packages and pre‑loads data into the virtual file system.- PyScript automatically downloads the runtime (≈ 30 MB for Pyodide) and the listed packages.
- Python code runs inside the WASM heap, processes data, and pushes a final result to the DOM via the JS proxy.
Because the runtime and all packages live in memory, the user sees a single network request per package, and the runtime is cached in the browser’s HTTP cache for subsequent visits.
Concrete Example
<script type="text/py-config">
[packages]
pandas
numpy
[fetch]
# Download dataset into the virtual file system
url = "https://example.com/data.csv"
</script>
<script type="text/python">
import pandas as pd
import numpy as np
from js import console
# The file is now available at /data.csv in the Pyodide FS
df = pd.read_csv("/data.csv")
summary = df.describe().to_json()
# Push result to a DOM element
from js import document
document.getElementById("output").textContent = summary
console.log("PyScript finished")
</script>
3. Trust and Data Boundaries
PyScript inherits the browser’s security model:
- SOP Enforcement: Python code cannot reach resources on another origin unless CORS allows it. Attempting to fetch
https://evil.com/apiwill trigger a CORS error in the network tab. - File System Access: The runtime has a virtual file system. It cannot read
C:\Users\…on the client. To access user files, the script must invoke theFile System Access APIand await a user‑selected file. - Code Visibility: All Python source is transmitted as plain text. Never embed API keys or business logic directly in the
py-scriptblock.
Diagnostic Check
- Open DevTools → Network. Look for
pyodide.jsand.whlfiles. If they are missing, the runtime failed to load. - Inspect
document.getElementById("output")content after script execution. It should contain the JSON summary. - Attempt a cross‑origin fetch in a separate
py-scriptand confirm the console shows a CORS error.
4. Operational Checks
Implement these runtime checks to surface failures early:
| Check | Implementation | Expected Result |
|---|---|---|
| Package Load Success | Catch ImportError and log to JS console. |
No exception; package available. |
| Memory Usage | Use pyodide.runPython('import psutil; psutil.Process().memory_info().rss') via a JS bridge. |
Value below 500 MB for typical workloads. |
| Network Latency | Record performance.now() before and after py-config fetch. |
Download < 2 s for the runtime on a 5 Mbps connection. |
5. Failure Modes & When to Re‑Architect
- Network‑latency bottleneck: If the runtime or packages take > 5 s to load, consider pre‑bundling a smaller set of packages or using a CDN with edge caching.
- Memory exhaustion: Large data sets (> 100 MB) can exceed the WASM heap (≈ 2 GB). Detect via the memory check table above. If exceeded, offload heavy processing to a Web Worker or a server endpoint.
- High‑frequency DOM updates: The JS‑Python proxy serializes every call. For animations or real‑time charts, move the rendering loop to JavaScript and call Python only for data calculations.
- Secret exposure: If the application requires confidential keys, the design must shift to a server‑side API that the PyScript client calls via HTTPS.
Re‑Architecting Trigger
When any of the above conditions persistently degrade user experience, you should transition to a hybrid model:
- Keep lightweight logic (validation, UI glue) in PyScript.
- Move heavy data processing to a server‑side Python API.
- Use
fetchfrom PyScript to retrieve pre‑processed JSON.
6. Summary of Best Practices
- Declare only the packages you need in
py-config. - Validate package downloads via the Network tab.
- Monitor memory and latency; set thresholds.
- Never embed secrets; use server‑side auth.
- For high‑frequency UI, keep the heavy logic in JavaScript.
- Consider MicroPython for ultra‑light use cases where the full CPython stack is unnecessary.
By following this architecture, you can build secure, responsive PyScript applications that respect browser boundaries while leveraging the power of Python in the client. Regular diagnostics and a clear failure‑mode checklist will keep your app robust across varying network and device conditions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.