Using PyScript's <py-env> Tag to Manage Browser-Based Python Dependencies
Learn how PyScript’s <py-env> tag declaratively installs Python packages via Pyodide, where the dependencies are cached in IndexedDB, and what to check when things go wrong.
24 Jul 2025, 07:38 UTC

Requirements
When building a web page that needs to run Python code in the browser, you must ensure that the required third‑party packages are available at runtime, that the page loads reproducibly across sessions, and that users are not left with a broken UI if a dependency cannot be fetched.
Smallest Suitable Design
The simplest way to satisfy those requirements with PyScript is to declare dependencies inside a <py-env> element. PyScript translates each entry into a micropip install call during page load, letting Pyodide fetch the appropriate WebAssembly wheels from its CDN and make them importable in subsequent <py-script> blocks.
Example declaration
<!-- index.html -->
<html>
<head>
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<head>
<body>
<py-env>
numpy==1.24
pandas
</py-env>
<py-script>
import numpy as np
import pandas as pd
print('NumPy version:', np.__version__)
print('Pandas version:', pd.__version__)
</py-script>
</body>
</html>
No additional build step is required; the browser handles fetching and caching.
Trust and Data Boundaries
- Network boundary: The
<py-env>triggers HTTPS requests to Pyodide’s package index (typicallyhttps://github.com/pyodide/pyodide-data/releases/download/...). Only packages that have pre‑built wheels for the target Pyodide version can be downloaded. - Storage boundary: After a successful install, Pyodide writes the package metadata and compiled wheels into the browser’s
IndexedDBunder themicropipstore. Subsequent page loads read from this cache instead of repeating the network fetch. - Execution boundary: The Python code runs inside a Pyodide WebAssembly sandbox; it cannot directly access the DOM unless you explicitly expose bindings via
jsorwindowobjects.
Operational Checks
- Open the page in a modern browser (Chrome, Firefox, Safari, Edge).
- Open DevTools → Console and verify that the version prints appear without tracebacks.
- Check DevTools → Application → IndexedDB →
micropipstore to see entries fornumpyandpandasafter the first load. - Reload the page and observe the Network tab: the package .whl files should show (from ServiceWorker) or (from disk cache) with minimal transferred bytes.
- To test error handling, temporarily change
numpy==1.24to a non‑existent name likenonexistentpkg, reload, and confirm that an error is logged in the console but the page still loads and any subsequent<py-script>that does not depend on the missing package runs.
Failure Modes
- Missing wheel: If a requested package lacks a Pyodide wheel for the current Pyodide version,
micropipfails and logs an error (e.g., "ERROR: Could not find a version that satisfies the requirement"). The page continues, but any import of that package will raiseModuleNotFoundError. - Network interruption: A failed fetch results in the same error path; the cached copy (if any) is used, otherwise the package is unavailable.
- Large initial payload: Each package brings its compiled wheel (often several megabytes). On slow connections this can delay page interactivity.
- Version lag: Pyodice’s wheel repository may trail upstream PyPI; a pinned version like
numpy==1.24might not be available, forcing you to either relax the version constraint or build a custom wheel.
When the Design Would Change
- Offline‑first scenarios: If the target environment may lack network access, you can pre‑bundle wheels using the
filesattribute of<py-env>or serve them from a same‑origin cache and pointmicropipto a local index. - Need for newer or custom builds: When a package requires a version or build not provided by Pyodide, you would build a wheel yourself (using
emscriptenor the Pyodide build tools) and host it, then reference it viafilesor a URL in<py-env>. - Size‑critical applications: To reduce initial load, you might split dependencies across multiple
<py-env>blocks and load them lazily via dynamic import of additional<py-script>elements, or use a CDN that serves compressed.bror.gzwheels. - Strict version locking: If exact reproducibility across Pyodide upgrades is required, you would lock the Pyodide version itself (by specifying a particular
pyscript.jsrelease) and vendor the wheel set alongside your application.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.