# pyodide-pack
[![PyPI Latest Release](https://img.shields.io/pypi/v/pyodide-pack.svg)](https://pypi.org/project/pyodide-pack/)
[![GHA CI](https://github.com/rth/pyodide-pack/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/rth/pyodide-pack/actions/workflows/main.yml)
Python package bundler for the web
THIS PACKAGE IS STILL VERY EXPERIMENTAL
Pyodide-pack detects used modules in a Python application running in the web with Pyodide, and creates a minimal bundle with them. This allows to significantly reduce the download size of Python applications, provided that the code to execute is known in advance.
## Install
Pyodide-pack requires Python 3.10+ as well as NodeJS,
```bash
pip install pyodide-pack
npm install pyodide@0.20.1-alpha.2
# A hack due to the npm package having issues
wget https://cdn.jsdelivr.net/pyodide/v0.20.0/full/packages.json -O node_modules/pyodide/packages.json
```
## Quickstart
1. Create file with the code of your Python application running in the web. As example we will take,
`examples/scikit-learn/app.py`
**app.py**
```py
import pandas as pd # noqa
pd.DataFrame(range(10))
```
This application can run with Pyodide, and will need to download around 10.5
MB of packages, including numpy and pandas in addition to
~7MB for CPython with stdlib.
2. Create the package bundle,
```bash
pyodide pack examples/pandas/app.py
```
which would produce the following output
```
Running pyodide-pack on examples/pandas/app.py
Note: unless otherwise specified all sizes are given for gzip compressed files to
be representative of CDN compression.
Loaded requirements from: examples/pandas/requirements.txt
Running the input code in Node.js to detect used modules..
[..]
Done input code execution in 11.1 s
Detected 8 dependencies with a total size of 10.54 MB (uncompressed: 40.99 MB)
In total 425 files and 54 dynamic libraries were accessed.
Packing..
┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ No ┃ Package ┃ All files ┃ .so libs ┃ Size (MB) ┃ Reduction ┃
┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ 1 │ distutils.tar │ 101 → 0 │ 0 → 0 │ 0.26 → 0.00 │ 100.0 % │
│ 2 │ numpy-1.22.3-cp310-cp310-emsc… │ 418 → 94 │ 19 → 13 │ 3.63 → 2.49 │ 31.4 % │
│ 3 │ pandas-1.4.2-cp310-cp310-emsc… │ 469 → 283 │ 42 → 41 │ 5.11 → 4.50 │ 12.0 % │
│ 4 │ pyparsing-3.0.7-py3-none-any.… │ 17 → 0 │ 0 → 0 │ 0.10 → 0.00 │ 100.0 % │
│ 5 │ python_dateutil-2.8.2-py2.py3… │ 25 → 15 │ 0 → 0 │ 0.24 → 0.22 │ 9.4 % │
│ 6 │ pytz-2022.1-py2.py3-none-any.… │ 612 → 5 │ 0 → 0 │ 0.43 → 0.02 │ 96.1 % │
│ 7 │ setuptools-62.0.0-py3-none-an… │ 213 → 0 │ 0 → 0 │ 0.76 → 0.00 │ 100.0 % │
│ 8 │ six-1.16.0-py2.py3-none-any.w… │ 6 → 1 │ 0 → 0 │ 0.01 → 0.01 │ 18.5 % │
└────┴────────────────────────────────┴───────────┴──────────┴─────────────┴───────────┘
Wrote pyodide-package-bundle.zip with 7.36 MB (30.2% reduction)
Running the input code in Node.js to validate bundle..
Validating and benchmarking the output bundle..
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Step ┃ Load time (s) ┃ Fraction of load time ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩
│ loadPyodide │ 2.59 │ 24.4 % │
│ fetch_unpack_archive │ 0.27 │ 2.5 % │
│ load_dynamic_libs │ 6.21 │ 58.5 % │
│ import_run_app │ 1.56 │ 14.7 % │
│ TOTAL │ 10.63 │ 100 % │
└──────────────────────┴───────────────┴───────────────────────┘
Bundle validation successful.
```
3. Load your Python web application with,
```js
let pyodide = await loadPyodide({fullStdLib: false});
await pyodide.runPythonAsync(`
from pyodide.http import pyfetch
response = await pyfetch("<your-server>/pyodide-package-bundle.zip")
await response.unpack_archive(extract_dir='/')
`)
await pyodide.pyimport('pyodide_pack_loader').setup();
```
## Implementation
This bundler runs your applications in a Node.js and intercepts,
- `FS.open` calls in read mode, which includes accessed files in the Emscripten's MEMFS file system opened from Python, C or Javascript.
- calls to load a dynamic library
Package wheels are then repacked into a single bundle with the accessed files and dynamic libraries.
## License
Pyodide-pack uses the [Mozilla Public License Version 2.0](https://choosealicense.com/licenses/mpl-2.0/).
Raw data
{
"_id": null,
"home_page": "https://github.com/rth/pyodide-bundler",
"name": "pyodide-pack",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": "",
"keywords": "",
"author": "Roman Yurchak",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/69/ab/512b98d585a333fee0b94fac6b191ab19d0f19527db4c27c25d27c0ecaa2/pyodide-pack-0.2.0.tar.gz",
"platform": null,
"description": "# pyodide-pack\n\n[![PyPI Latest Release](https://img.shields.io/pypi/v/pyodide-pack.svg)](https://pypi.org/project/pyodide-pack/)\n[![GHA CI](https://github.com/rth/pyodide-pack/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/rth/pyodide-pack/actions/workflows/main.yml)\n\nPython package bundler for the web\n\nTHIS PACKAGE IS STILL VERY EXPERIMENTAL\n\nPyodide-pack detects used modules in a Python application running in the web with Pyodide, and creates a minimal bundle with them. This allows to significantly reduce the download size of Python applications, provided that the code to execute is known in advance.\n\n## Install\n\nPyodide-pack requires Python 3.10+ as well as NodeJS,\n\n```bash\npip install pyodide-pack\nnpm install pyodide@0.20.1-alpha.2\n# A hack due to the npm package having issues\nwget https://cdn.jsdelivr.net/pyodide/v0.20.0/full/packages.json -O node_modules/pyodide/packages.json\n```\n\n## Quickstart\n\n1. Create file with the code of your Python application running in the web. As example we will take,\n `examples/scikit-learn/app.py`\n\n **app.py**\n\n ```py\n import pandas as pd # noqa\n\n pd.DataFrame(range(10))\n ```\n\n This application can run with Pyodide, and will need to download around 10.5\n MB of packages, including numpy and pandas in addition to\n ~7MB for CPython with stdlib.\n\n2. Create the package bundle,\n\n ```bash\n pyodide pack examples/pandas/app.py\n ```\n which would produce the following output\n\n ```\n Running pyodide-pack on examples/pandas/app.py\n\n Note: unless otherwise specified all sizes are given for gzip compressed files to\n be representative of CDN compression.\n\n Loaded requirements from: examples/pandas/requirements.txt\n Running the input code in Node.js to detect used modules..\n\n [..]\n\n Done input code execution in 11.1 s\n\n Detected 8 dependencies with a total size of 10.54 MB (uncompressed: 40.99 MB)\n In total 425 files and 54 dynamic libraries were accessed.\n\n Packing..\n \u250f\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n \u2503 No \u2503 Package \u2503 All files \u2503 .so libs \u2503 Size (MB) \u2503 Reduction \u2503\n \u2521\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n \u2502 1 \u2502 distutils.tar \u2502 101 \u2192 0 \u2502 0 \u2192 0 \u2502 0.26 \u2192 0.00 \u2502 100.0 % \u2502\n \u2502 2 \u2502 numpy-1.22.3-cp310-cp310-emsc\u2026 \u2502 418 \u2192 94 \u2502 19 \u2192 13 \u2502 3.63 \u2192 2.49 \u2502 31.4 % \u2502\n \u2502 3 \u2502 pandas-1.4.2-cp310-cp310-emsc\u2026 \u2502 469 \u2192 283 \u2502 42 \u2192 41 \u2502 5.11 \u2192 4.50 \u2502 12.0 % \u2502\n \u2502 4 \u2502 pyparsing-3.0.7-py3-none-any.\u2026 \u2502 17 \u2192 0 \u2502 0 \u2192 0 \u2502 0.10 \u2192 0.00 \u2502 100.0 % \u2502\n \u2502 5 \u2502 python_dateutil-2.8.2-py2.py3\u2026 \u2502 25 \u2192 15 \u2502 0 \u2192 0 \u2502 0.24 \u2192 0.22 \u2502 9.4 % \u2502\n \u2502 6 \u2502 pytz-2022.1-py2.py3-none-any.\u2026 \u2502 612 \u2192 5 \u2502 0 \u2192 0 \u2502 0.43 \u2192 0.02 \u2502 96.1 % \u2502\n \u2502 7 \u2502 setuptools-62.0.0-py3-none-an\u2026 \u2502 213 \u2192 0 \u2502 0 \u2192 0 \u2502 0.76 \u2192 0.00 \u2502 100.0 % \u2502\n \u2502 8 \u2502 six-1.16.0-py2.py3-none-any.w\u2026 \u2502 6 \u2192 1 \u2502 0 \u2192 0 \u2502 0.01 \u2192 0.01 \u2502 18.5 % \u2502\n \u2514\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Wrote pyodide-package-bundle.zip with 7.36 MB (30.2% reduction)\n\n Running the input code in Node.js to validate bundle..\n\n Validating and benchmarking the output bundle..\n \u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n \u2503 Step \u2503 Load time (s) \u2503 Fraction of load time \u2503\n \u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n \u2502 loadPyodide \u2502 2.59 \u2502 24.4 % \u2502\n \u2502 fetch_unpack_archive \u2502 0.27 \u2502 2.5 % \u2502\n \u2502 load_dynamic_libs \u2502 6.21 \u2502 58.5 % \u2502\n \u2502 import_run_app \u2502 1.56 \u2502 14.7 % \u2502\n \u2502 TOTAL \u2502 10.63 \u2502 100 % \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n Bundle validation successful.\n ```\n3. Load your Python web application with,\n ```js\n let pyodide = await loadPyodide({fullStdLib: false});\n\n await pyodide.runPythonAsync(`\n from pyodide.http import pyfetch\n\n response = await pyfetch(\"<your-server>/pyodide-package-bundle.zip\")\n await response.unpack_archive(extract_dir='/')\n `)\n\n await pyodide.pyimport('pyodide_pack_loader').setup();\n ```\n\n## Implementation\n\nThis bundler runs your applications in a Node.js and intercepts,\n - `FS.open` calls in read mode, which includes accessed files in the Emscripten's MEMFS file system opened from Python, C or Javascript.\n - calls to load a dynamic library\n\nPackage wheels are then repacked into a single bundle with the accessed files and dynamic libraries.\n\n## License\n\nPyodide-pack uses the [Mozilla Public License Version 2.0](https://choosealicense.com/licenses/mpl-2.0/).\n",
"bugtrack_url": null,
"license": "",
"summary": "A bundler for Python packages for the web",
"version": "0.2.0",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "bcfdb2fcde502fa2bf42b62247938068",
"sha256": "98f703abe72e7eda2fd0305726958889f13ecb58fe06d4a433832599459cb1f5"
},
"downloads": -1,
"filename": "pyodide_pack-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bcfdb2fcde502fa2bf42b62247938068",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 17420,
"upload_time": "2022-12-14T08:51:30",
"upload_time_iso_8601": "2022-12-14T08:51:30.159051Z",
"url": "https://files.pythonhosted.org/packages/60/47/27971449f919eeba2709d918723e06b24a0d7bf63d6764ec2650409ec8d0/pyodide_pack-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "1b1a3abc3bb480028cdca7d37a354d40",
"sha256": "7329291152c5f5b53cc0a94dcea7b0da549cc0aee25d08c1196039775dbe8538"
},
"downloads": -1,
"filename": "pyodide-pack-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "1b1a3abc3bb480028cdca7d37a354d40",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 21106,
"upload_time": "2022-12-14T08:51:31",
"upload_time_iso_8601": "2022-12-14T08:51:31.691218Z",
"url": "https://files.pythonhosted.org/packages/69/ab/512b98d585a333fee0b94fac6b191ab19d0f19527db4c27c25d27c0ecaa2/pyodide-pack-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2022-12-14 08:51:31",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "rth",
"github_project": "pyodide-bundler",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pyodide-pack"
}