streamlit2stlite 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit2stlite-0.1.0/PKG-INFO +109 -0
- streamlit2stlite-0.1.0/README.md +95 -0
- streamlit2stlite-0.1.0/pyproject.toml +29 -0
- streamlit2stlite-0.1.0/setup.cfg +4 -0
- streamlit2stlite-0.1.0/streamlit2stlite/__init__.py +4 -0
- streamlit2stlite-0.1.0/streamlit2stlite/cli.py +152 -0
- streamlit2stlite-0.1.0/streamlit2stlite/core.py +231 -0
- streamlit2stlite-0.1.0/streamlit2stlite.egg-info/PKG-INFO +109 -0
- streamlit2stlite-0.1.0/streamlit2stlite.egg-info/SOURCES.txt +12 -0
- streamlit2stlite-0.1.0/streamlit2stlite.egg-info/dependency_links.txt +1 -0
- streamlit2stlite-0.1.0/streamlit2stlite.egg-info/entry_points.txt +2 -0
- streamlit2stlite-0.1.0/streamlit2stlite.egg-info/requires.txt +3 -0
- streamlit2stlite-0.1.0/streamlit2stlite.egg-info/top_level.txt +1 -0
- streamlit2stlite-0.1.0/tests/test_core.py +109 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamlit2stlite
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert Streamlit Python apps to stlite HTML apps
|
|
5
|
+
Project-URL: Homepage, https://github.com/caggionim/streamlit2stlite
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/caggionim/streamlit2stlite/issues
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# 🔦 streamlit2stlite
|
|
16
|
+
|
|
17
|
+
**Turn your Streamlit apps into standalone HTML files in seconds.**
|
|
18
|
+
|
|
19
|
+
`streamlit2stlite` is a simple tool that bundles your Streamlit application and all its dependencies into a single HTML file. This file can be opened in any modern browser, running entirely purely on the client side using WebAssembly (via [stlite](https://github.com/whitphx/stlite)). **No server, no hosting costs, no deployment headaches.**
|
|
20
|
+
|
|
21
|
+
> 💡 Perfect for sharing data dashboards, prototypes, and tools with colleagues or clients who don't have Python installed.
|
|
22
|
+
|
|
23
|
+
## 🚀 Quick Start
|
|
24
|
+
|
|
25
|
+
1. **Install** the tool:
|
|
26
|
+
```bash
|
|
27
|
+
pip install streamlit2stlite
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. **Convert** your app:
|
|
31
|
+
```bash
|
|
32
|
+
streamlit2stlite my_app.py
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. **Open** `my_app.html` in your browser. That's it!
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## ✨ Features
|
|
40
|
+
|
|
41
|
+
* **📦 Auto-Magic Dependency Detection**: Automatically scans your imports to determine which packages to install in the browser (e.g., proper handling of `pandas`, `numpy`, `scipy` for `lmfit`, etc.).
|
|
42
|
+
* **� LaTeX Support**: Correctly handles backslashes in your math equations so they render perfectly.
|
|
43
|
+
* **�️ Smart Titles**: Automatically detects your app's title from `st.set_page_config()` or `st.title()`.
|
|
44
|
+
* **🛠️ Full Control**: Override requirements, titles, or the stlite version via CLI flags if you need to.
|
|
45
|
+
|
|
46
|
+
## � Usage Guide
|
|
47
|
+
|
|
48
|
+
### Basic Conversion
|
|
49
|
+
The simplest way to use it. Defaults to creating an HTML file with the same name as your script.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
streamlit2stlite dashboard.py
|
|
53
|
+
# -> Creates dashboard.html
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Custom Output Name
|
|
57
|
+
Specify exactly where you want the file to go.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
streamlit2stlite script.py -o ./dist/awesome_dashboard.html
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Managing Dependencies
|
|
64
|
+
We try to guess your dependencies, but sometimes you need to be specific.
|
|
65
|
+
|
|
66
|
+
**Add extra packages:**
|
|
67
|
+
```bash
|
|
68
|
+
streamlit2stlite app.py --add-requirements "scikit-learn,purple-air"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Override completely:**
|
|
72
|
+
```bash
|
|
73
|
+
streamlit2stlite app.py --requirements "streamlit,pandas,numpy"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Full CLI Options
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
usage: streamlit2stlite [-h] [-o OUTPUT] [-r REQUIREMENTS] [-t TITLE]
|
|
80
|
+
[--stlite-version STLITE_VERSION]
|
|
81
|
+
[--add-requirements ADD_REQUIREMENTS] [-v]
|
|
82
|
+
input
|
|
83
|
+
|
|
84
|
+
positional arguments:
|
|
85
|
+
input Path to the input Streamlit Python file
|
|
86
|
+
|
|
87
|
+
options:
|
|
88
|
+
-h, --help show this help message
|
|
89
|
+
-o, --output Path to the output HTML file
|
|
90
|
+
-r, --requirements Comma-separated list of packages to install
|
|
91
|
+
-t, --title Title for the HTML page
|
|
92
|
+
--add-requirements Additional packages to add to auto-detected ones
|
|
93
|
+
-v, --verbose Print verbose output
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## ❓ FAQ
|
|
97
|
+
|
|
98
|
+
**How does this work?**
|
|
99
|
+
It embeds your Python code into a template that loads `stlite` (a port of Streamlit to WebAssembly). When you open the HTML file, your browser downloads a mini Python environment (Pyodide) and runs your code locally.
|
|
100
|
+
|
|
101
|
+
**Can I read local files?**
|
|
102
|
+
Because this runs in the browser, it cannot read files from your hard drive directly (sandbox security). You should use `st.file_uploader` to let users provide files, or embed data directly into your script.
|
|
103
|
+
|
|
104
|
+
**Does it support all Python packages?**
|
|
105
|
+
It supports packages available in [Pyodide](https://pyodide.org/en/stable/usage/packages-in-pyodide.html) (including numpy, pandas, scipy, matplotlib, scikit-learn) and pure Python packages from PyPI (micropip).
|
|
106
|
+
|
|
107
|
+
## 📄 License
|
|
108
|
+
|
|
109
|
+
MIT License. Feel free to use this for whatever you want!
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# 🔦 streamlit2stlite
|
|
2
|
+
|
|
3
|
+
**Turn your Streamlit apps into standalone HTML files in seconds.**
|
|
4
|
+
|
|
5
|
+
`streamlit2stlite` is a simple tool that bundles your Streamlit application and all its dependencies into a single HTML file. This file can be opened in any modern browser, running entirely purely on the client side using WebAssembly (via [stlite](https://github.com/whitphx/stlite)). **No server, no hosting costs, no deployment headaches.**
|
|
6
|
+
|
|
7
|
+
> 💡 Perfect for sharing data dashboards, prototypes, and tools with colleagues or clients who don't have Python installed.
|
|
8
|
+
|
|
9
|
+
## 🚀 Quick Start
|
|
10
|
+
|
|
11
|
+
1. **Install** the tool:
|
|
12
|
+
```bash
|
|
13
|
+
pip install streamlit2stlite
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. **Convert** your app:
|
|
17
|
+
```bash
|
|
18
|
+
streamlit2stlite my_app.py
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
3. **Open** `my_app.html` in your browser. That's it!
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ✨ Features
|
|
26
|
+
|
|
27
|
+
* **📦 Auto-Magic Dependency Detection**: Automatically scans your imports to determine which packages to install in the browser (e.g., proper handling of `pandas`, `numpy`, `scipy` for `lmfit`, etc.).
|
|
28
|
+
* **� LaTeX Support**: Correctly handles backslashes in your math equations so they render perfectly.
|
|
29
|
+
* **�️ Smart Titles**: Automatically detects your app's title from `st.set_page_config()` or `st.title()`.
|
|
30
|
+
* **🛠️ Full Control**: Override requirements, titles, or the stlite version via CLI flags if you need to.
|
|
31
|
+
|
|
32
|
+
## � Usage Guide
|
|
33
|
+
|
|
34
|
+
### Basic Conversion
|
|
35
|
+
The simplest way to use it. Defaults to creating an HTML file with the same name as your script.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
streamlit2stlite dashboard.py
|
|
39
|
+
# -> Creates dashboard.html
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Custom Output Name
|
|
43
|
+
Specify exactly where you want the file to go.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
streamlit2stlite script.py -o ./dist/awesome_dashboard.html
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Managing Dependencies
|
|
50
|
+
We try to guess your dependencies, but sometimes you need to be specific.
|
|
51
|
+
|
|
52
|
+
**Add extra packages:**
|
|
53
|
+
```bash
|
|
54
|
+
streamlit2stlite app.py --add-requirements "scikit-learn,purple-air"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Override completely:**
|
|
58
|
+
```bash
|
|
59
|
+
streamlit2stlite app.py --requirements "streamlit,pandas,numpy"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Full CLI Options
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
usage: streamlit2stlite [-h] [-o OUTPUT] [-r REQUIREMENTS] [-t TITLE]
|
|
66
|
+
[--stlite-version STLITE_VERSION]
|
|
67
|
+
[--add-requirements ADD_REQUIREMENTS] [-v]
|
|
68
|
+
input
|
|
69
|
+
|
|
70
|
+
positional arguments:
|
|
71
|
+
input Path to the input Streamlit Python file
|
|
72
|
+
|
|
73
|
+
options:
|
|
74
|
+
-h, --help show this help message
|
|
75
|
+
-o, --output Path to the output HTML file
|
|
76
|
+
-r, --requirements Comma-separated list of packages to install
|
|
77
|
+
-t, --title Title for the HTML page
|
|
78
|
+
--add-requirements Additional packages to add to auto-detected ones
|
|
79
|
+
-v, --verbose Print verbose output
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## ❓ FAQ
|
|
83
|
+
|
|
84
|
+
**How does this work?**
|
|
85
|
+
It embeds your Python code into a template that loads `stlite` (a port of Streamlit to WebAssembly). When you open the HTML file, your browser downloads a mini Python environment (Pyodide) and runs your code locally.
|
|
86
|
+
|
|
87
|
+
**Can I read local files?**
|
|
88
|
+
Because this runs in the browser, it cannot read files from your hard drive directly (sandbox security). You should use `st.file_uploader` to let users provide files, or embed data directly into your script.
|
|
89
|
+
|
|
90
|
+
**Does it support all Python packages?**
|
|
91
|
+
It supports packages available in [Pyodide](https://pyodide.org/en/stable/usage/packages-in-pyodide.html) (including numpy, pandas, scipy, matplotlib, scikit-learn) and pure Python packages from PyPI (micropip).
|
|
92
|
+
|
|
93
|
+
## 📄 License
|
|
94
|
+
|
|
95
|
+
MIT License. Feel free to use this for whatever you want!
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "streamlit2stlite"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Convert Streamlit Python apps to stlite HTML apps"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.7"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Programming Language :: Python :: 3",
|
|
9
|
+
"License :: OSI Approved :: MIT License",
|
|
10
|
+
"Operating System :: OS Independent",
|
|
11
|
+
]
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = ["pytest"]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
streamlit2stlite = "streamlit2stlite.cli:main"
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
"Homepage" = "https://github.com/caggionim/streamlit2stlite"
|
|
22
|
+
"Bug Tracker" = "https://github.com/caggionim/streamlit2stlite/issues"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools]
|
|
25
|
+
packages = ["streamlit2stlite"]
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["setuptools>=61.0"]
|
|
29
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .core import convert_streamlit_to_stlite, extract_imports, STLITE_VERSION
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
"""Main CLI entry point."""
|
|
8
|
+
parser = argparse.ArgumentParser(
|
|
9
|
+
description='Convert Streamlit Python apps to stlite HTML apps',
|
|
10
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
11
|
+
epilog='''
|
|
12
|
+
Examples:
|
|
13
|
+
streamlit2stlite my_app.py
|
|
14
|
+
Converts my_app.py to my_app.html with auto-detected requirements
|
|
15
|
+
|
|
16
|
+
streamlit2stlite my_app.py -o output.html
|
|
17
|
+
Converts my_app.py to output.html
|
|
18
|
+
|
|
19
|
+
streamlit2stlite my_app.py --requirements pandas,numpy,plotly
|
|
20
|
+
Specifies exact requirements to install
|
|
21
|
+
|
|
22
|
+
streamlit2stlite my_app.py --title "My Dashboard"
|
|
23
|
+
Sets a custom page title
|
|
24
|
+
|
|
25
|
+
streamlit2stlite my_app.py --stlite-version 0.80.0
|
|
26
|
+
Uses a specific stlite version
|
|
27
|
+
'''
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'input',
|
|
32
|
+
type=str,
|
|
33
|
+
help='Path to the input Streamlit Python file'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'-o', '--output',
|
|
38
|
+
type=str,
|
|
39
|
+
default=None,
|
|
40
|
+
help='Path to the output HTML file (default: same name as input with .html extension)'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
'-r', '--requirements',
|
|
45
|
+
type=str,
|
|
46
|
+
default=None,
|
|
47
|
+
help='Comma-separated list of pip packages to install (default: auto-detect from imports)'
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
'-t', '--title',
|
|
52
|
+
type=str,
|
|
53
|
+
default=None,
|
|
54
|
+
help='Title for the HTML page (default: auto-detect from code)'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
'--stlite-version',
|
|
59
|
+
type=str,
|
|
60
|
+
default=STLITE_VERSION,
|
|
61
|
+
help=f'Version of stlite to use (default: {STLITE_VERSION})'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
'--add-requirements',
|
|
66
|
+
type=str,
|
|
67
|
+
default=None,
|
|
68
|
+
help='Additional requirements to add to auto-detected ones (comma-separated)'
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
'-v', '--verbose',
|
|
73
|
+
action='store_true',
|
|
74
|
+
help='Print verbose output'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
args = parser.parse_args()
|
|
78
|
+
|
|
79
|
+
# Read input file
|
|
80
|
+
input_path = Path(args.input)
|
|
81
|
+
if not input_path.exists():
|
|
82
|
+
print(f"Error: Input file '{args.input}' not found", file=sys.stderr)
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
# Check extension merely as a warning
|
|
86
|
+
if not input_path.suffix.lower() == '.py':
|
|
87
|
+
# Just a warning, proceed anyway
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
python_code = input_path.read_text(encoding='utf-8')
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"Error reading input file: {e}", file=sys.stderr)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
# Determine output path
|
|
97
|
+
if args.output:
|
|
98
|
+
output_path = Path(args.output)
|
|
99
|
+
else:
|
|
100
|
+
output_path = input_path.with_suffix('.html')
|
|
101
|
+
|
|
102
|
+
# Parse requirements
|
|
103
|
+
requirements = None
|
|
104
|
+
if args.requirements:
|
|
105
|
+
requirements = [r.strip() for r in args.requirements.split(',') if r.strip()]
|
|
106
|
+
|
|
107
|
+
# Add additional requirements if specified
|
|
108
|
+
if args.add_requirements:
|
|
109
|
+
additional = [r.strip() for r in args.add_requirements.split(',') if r.strip()]
|
|
110
|
+
if requirements is None:
|
|
111
|
+
requirements = extract_imports(python_code)
|
|
112
|
+
requirements = list(set(requirements + additional))
|
|
113
|
+
|
|
114
|
+
# Verbose output
|
|
115
|
+
if args.verbose:
|
|
116
|
+
print(f"Input: {input_path}")
|
|
117
|
+
print(f"Output: {output_path}")
|
|
118
|
+
if requirements is None:
|
|
119
|
+
detected_reqs = extract_imports(python_code)
|
|
120
|
+
print(f"Auto-detected requirements: {detected_reqs}")
|
|
121
|
+
else:
|
|
122
|
+
print(f"Using requirements: {requirements}")
|
|
123
|
+
print(f"stlite version: {args.stlite_version}")
|
|
124
|
+
|
|
125
|
+
# Convert
|
|
126
|
+
try:
|
|
127
|
+
html_content = convert_streamlit_to_stlite(
|
|
128
|
+
python_code=python_code,
|
|
129
|
+
title=args.title,
|
|
130
|
+
requirements=requirements,
|
|
131
|
+
stlite_version=args.stlite_version
|
|
132
|
+
)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f"Error during conversion: {e}", file=sys.stderr)
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
# Write output
|
|
138
|
+
try:
|
|
139
|
+
output_path.write_text(html_content, encoding='utf-8')
|
|
140
|
+
print(f"Successfully converted '{input_path}' to '{output_path}'")
|
|
141
|
+
|
|
142
|
+
if args.verbose:
|
|
143
|
+
# Show final requirements used if we haven't already
|
|
144
|
+
if requirements is None:
|
|
145
|
+
print(f"Final requirements used: {extract_imports(python_code)}")
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
print(f"Error writing output file: {e}", file=sys.stderr)
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
|
|
151
|
+
if __name__ == '__main__':
|
|
152
|
+
main()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional, List, Set, Union
|
|
3
|
+
|
|
4
|
+
# Default stlite version
|
|
5
|
+
STLITE_VERSION = "1.0.0"
|
|
6
|
+
|
|
7
|
+
# HTML template header
|
|
8
|
+
HTML_HEADER = '''<!doctype html>
|
|
9
|
+
<html>
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="UTF-8" />
|
|
12
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
13
|
+
<meta
|
|
14
|
+
name="viewport"
|
|
15
|
+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
16
|
+
/>
|
|
17
|
+
<title>{title}</title>
|
|
18
|
+
<link
|
|
19
|
+
rel="stylesheet"
|
|
20
|
+
href="https://cdn.jsdelivr.net/npm/@stlite/browser@{stlite_version}/build/stlite.css"
|
|
21
|
+
/>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<div id="root"></div>
|
|
25
|
+
<script type="module">
|
|
26
|
+
import {{ mount }} from "https://cdn.jsdelivr.net/npm/@stlite/browser@{stlite_version}/build/stlite.js";
|
|
27
|
+
|
|
28
|
+
// The Streamlit application code is defined here
|
|
29
|
+
const streamlit_app_code = `'''
|
|
30
|
+
|
|
31
|
+
# HTML template footer
|
|
32
|
+
HTML_FOOTER = '''`;
|
|
33
|
+
// Mount the stlite app with the specified requirements and files
|
|
34
|
+
mount(
|
|
35
|
+
{{
|
|
36
|
+
requirements: [{requirements}], // Packages to install
|
|
37
|
+
entrypoint: "streamlit_app.py", // This field is required
|
|
38
|
+
files: {{
|
|
39
|
+
"streamlit_app.py": streamlit_app_code,
|
|
40
|
+
}},
|
|
41
|
+
}},
|
|
42
|
+
document.getElementById("root"),
|
|
43
|
+
);
|
|
44
|
+
</script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
'''
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def escape_for_js_template_literal(python_code: str) -> str:
|
|
51
|
+
r"""
|
|
52
|
+
Escape Python code for embedding in a JavaScript template literal.
|
|
53
|
+
|
|
54
|
+
Key transformations:
|
|
55
|
+
1. Escape backticks (`) as they delimit template literals
|
|
56
|
+
2. Escape ${} sequences as they are template literal interpolation
|
|
57
|
+
3. Escape backslashes properly for JavaScript template literals
|
|
58
|
+
|
|
59
|
+
In Python source code:
|
|
60
|
+
- `\sigma` in source = literal `\sigma` when read
|
|
61
|
+
- For JS template literal, we need `\\sigma` to represent `\sigma`
|
|
62
|
+
|
|
63
|
+
So when reading a Python file:
|
|
64
|
+
- Single backslash in the read content needs to become double backslash in JS
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
python_code: The raw Python code to escape
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Escaped Python code safe for JS template literal embedding
|
|
71
|
+
"""
|
|
72
|
+
# First, escape all backslashes for JavaScript template literal
|
|
73
|
+
# A single backslash in the content needs to be \\ in JS template literal
|
|
74
|
+
escaped = python_code.replace('\\', '\\\\')
|
|
75
|
+
|
|
76
|
+
# Escape backticks with backslash (they delimit template literals)
|
|
77
|
+
escaped = escaped.replace('`', '\\`')
|
|
78
|
+
|
|
79
|
+
# Escape template literal interpolation sequences
|
|
80
|
+
escaped = escaped.replace('${', '\\${')
|
|
81
|
+
|
|
82
|
+
return escaped
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_imports(python_code: str) -> List[str]:
|
|
86
|
+
"""
|
|
87
|
+
Extract imported packages from Python code to suggest requirements.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
python_code: The Python source code
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of package names that should be installed
|
|
94
|
+
"""
|
|
95
|
+
packages: Set[str] = set()
|
|
96
|
+
|
|
97
|
+
# Common import patterns - extract the top-level package name
|
|
98
|
+
# Handles: import pkg, import pkg.submodule, from pkg import ..., from pkg.sub import ...
|
|
99
|
+
import_patterns = [
|
|
100
|
+
r'^import\s+([\w]+)', # import package or import package.submodule
|
|
101
|
+
r'^from\s+([\w]+)(?:\.\w+)*\s+import', # from package.submodule import ...
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
for line in python_code.split('\n'):
|
|
105
|
+
line = line.strip()
|
|
106
|
+
for pattern in import_patterns:
|
|
107
|
+
match = re.match(pattern, line)
|
|
108
|
+
if match:
|
|
109
|
+
pkg = match.group(1)
|
|
110
|
+
# Map common module names to pip package names
|
|
111
|
+
pkg_mapping = {
|
|
112
|
+
'cv2': 'opencv-python',
|
|
113
|
+
'sklearn': 'scikit-learn',
|
|
114
|
+
'PIL': 'Pillow',
|
|
115
|
+
'yaml': 'pyyaml',
|
|
116
|
+
'bs4': 'beautifulsoup4',
|
|
117
|
+
}
|
|
118
|
+
packages.add(pkg_mapping.get(pkg, pkg))
|
|
119
|
+
|
|
120
|
+
# Remove standard library modules that don't need installation
|
|
121
|
+
stdlib = {
|
|
122
|
+
'os', 'sys', 're', 'json', 'datetime', 'time', 'math', 'random',
|
|
123
|
+
'collections', 'itertools', 'functools', 'operator', 'copy',
|
|
124
|
+
'io', 'base64', 'hashlib', 'pickle', 'pathlib', 'typing',
|
|
125
|
+
'abc', 'contextlib', 'dataclasses', 'enum', 'warnings',
|
|
126
|
+
'threading', 'multiprocessing', 'subprocess', 'socket',
|
|
127
|
+
'urllib', 'http', 'email', 'html', 'xml', 'csv', 'sqlite3',
|
|
128
|
+
'logging', 'unittest', 'doctest', 'pdb', 'traceback',
|
|
129
|
+
'gc', 'inspect', 'importlib', 'pkgutil', 'platform',
|
|
130
|
+
'struct', 'array', 'decimal', 'fractions', 'statistics',
|
|
131
|
+
'tempfile', 'shutil', 'glob', 'fnmatch', 'linecache',
|
|
132
|
+
'textwrap', 'difflib', 'string', 'secrets', 'uuid',
|
|
133
|
+
'argparse', 'getopt', 'configparser', 'fileinput',
|
|
134
|
+
'stat', 'filecmp', 'zipfile', 'tarfile', 'gzip', 'bz2', 'lzma',
|
|
135
|
+
'zlib', 'binascii', 'quopri', 'uu', 'codecs',
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Also remove streamlit since it's built into stlite
|
|
139
|
+
stdlib.add('streamlit')
|
|
140
|
+
stdlib.add('st')
|
|
141
|
+
|
|
142
|
+
packages = packages - stdlib
|
|
143
|
+
|
|
144
|
+
# Add common dependencies that aren't directly imported but are needed
|
|
145
|
+
# These are dependencies of commonly used packages
|
|
146
|
+
dependency_additions = {
|
|
147
|
+
'lmfit': ['scipy'], # lmfit needs scipy
|
|
148
|
+
'tadatakit': ['pydantic'], # tadatakit needs pydantic
|
|
149
|
+
'plotly': [],
|
|
150
|
+
'pandas': ['xlsxwriter'],
|
|
151
|
+
'numpy': [],
|
|
152
|
+
'matplotlib': [],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for pkg in list(packages):
|
|
156
|
+
if pkg in dependency_additions:
|
|
157
|
+
packages.update(dependency_additions[pkg])
|
|
158
|
+
|
|
159
|
+
return sorted(list(packages))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def detect_title_from_code(python_code: str) -> str:
|
|
163
|
+
"""
|
|
164
|
+
Try to detect the app title from st.set_page_config or st.title calls.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
python_code: The Python source code
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Detected title or default
|
|
171
|
+
"""
|
|
172
|
+
# Look for page_title in set_page_config
|
|
173
|
+
match = re.search(r'page_title\s*=\s*["\']([^"\']+)["\']', python_code)
|
|
174
|
+
if match:
|
|
175
|
+
return match.group(1)
|
|
176
|
+
|
|
177
|
+
# Look for st.title
|
|
178
|
+
match = re.search(r'st\.title\s*\(\s*["\']([^"\']+)["\']', python_code)
|
|
179
|
+
if match:
|
|
180
|
+
# Remove emoji prefixes if present
|
|
181
|
+
title = match.group(1)
|
|
182
|
+
# Clean up emoji at the start
|
|
183
|
+
title = re.sub(r'^[^\w\s]+\s*', '', title)
|
|
184
|
+
return title if title else "Streamlit App"
|
|
185
|
+
|
|
186
|
+
return "Streamlit App"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def convert_streamlit_to_stlite(
|
|
190
|
+
python_code: str,
|
|
191
|
+
title: Optional[str] = None,
|
|
192
|
+
requirements: Optional[List[str]] = None,
|
|
193
|
+
stlite_version: str = STLITE_VERSION
|
|
194
|
+
) -> str:
|
|
195
|
+
"""
|
|
196
|
+
Convert a Streamlit Python app to an stlite HTML app.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
python_code: The Streamlit Python source code
|
|
200
|
+
title: Optional title for the HTML page
|
|
201
|
+
requirements: Optional list of pip packages to install
|
|
202
|
+
stlite_version: Version of stlite to use
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Complete HTML document with embedded stlite app
|
|
206
|
+
"""
|
|
207
|
+
# Detect title if not provided
|
|
208
|
+
if title is None:
|
|
209
|
+
title = detect_title_from_code(python_code)
|
|
210
|
+
|
|
211
|
+
# Detect requirements if not provided
|
|
212
|
+
if requirements is None:
|
|
213
|
+
requirements = extract_imports(python_code)
|
|
214
|
+
|
|
215
|
+
# Escape the Python code for JavaScript template literal
|
|
216
|
+
escaped_code = escape_for_js_template_literal(python_code)
|
|
217
|
+
|
|
218
|
+
# Format requirements as JavaScript array elements
|
|
219
|
+
req_str = ', '.join(f'"{pkg}"' for pkg in requirements)
|
|
220
|
+
|
|
221
|
+
# Build the HTML document
|
|
222
|
+
header = HTML_HEADER.format(
|
|
223
|
+
title=title,
|
|
224
|
+
stlite_version=stlite_version
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
footer = HTML_FOOTER.format(
|
|
228
|
+
requirements=req_str
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return header + escaped_code + footer
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamlit2stlite
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert Streamlit Python apps to stlite HTML apps
|
|
5
|
+
Project-URL: Homepage, https://github.com/caggionim/streamlit2stlite
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/caggionim/streamlit2stlite/issues
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# 🔦 streamlit2stlite
|
|
16
|
+
|
|
17
|
+
**Turn your Streamlit apps into standalone HTML files in seconds.**
|
|
18
|
+
|
|
19
|
+
`streamlit2stlite` is a simple tool that bundles your Streamlit application and all its dependencies into a single HTML file. This file can be opened in any modern browser, running entirely purely on the client side using WebAssembly (via [stlite](https://github.com/whitphx/stlite)). **No server, no hosting costs, no deployment headaches.**
|
|
20
|
+
|
|
21
|
+
> 💡 Perfect for sharing data dashboards, prototypes, and tools with colleagues or clients who don't have Python installed.
|
|
22
|
+
|
|
23
|
+
## 🚀 Quick Start
|
|
24
|
+
|
|
25
|
+
1. **Install** the tool:
|
|
26
|
+
```bash
|
|
27
|
+
pip install streamlit2stlite
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. **Convert** your app:
|
|
31
|
+
```bash
|
|
32
|
+
streamlit2stlite my_app.py
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. **Open** `my_app.html` in your browser. That's it!
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## ✨ Features
|
|
40
|
+
|
|
41
|
+
* **📦 Auto-Magic Dependency Detection**: Automatically scans your imports to determine which packages to install in the browser (e.g., proper handling of `pandas`, `numpy`, `scipy` for `lmfit`, etc.).
|
|
42
|
+
* **� LaTeX Support**: Correctly handles backslashes in your math equations so they render perfectly.
|
|
43
|
+
* **�️ Smart Titles**: Automatically detects your app's title from `st.set_page_config()` or `st.title()`.
|
|
44
|
+
* **🛠️ Full Control**: Override requirements, titles, or the stlite version via CLI flags if you need to.
|
|
45
|
+
|
|
46
|
+
## � Usage Guide
|
|
47
|
+
|
|
48
|
+
### Basic Conversion
|
|
49
|
+
The simplest way to use it. Defaults to creating an HTML file with the same name as your script.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
streamlit2stlite dashboard.py
|
|
53
|
+
# -> Creates dashboard.html
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Custom Output Name
|
|
57
|
+
Specify exactly where you want the file to go.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
streamlit2stlite script.py -o ./dist/awesome_dashboard.html
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Managing Dependencies
|
|
64
|
+
We try to guess your dependencies, but sometimes you need to be specific.
|
|
65
|
+
|
|
66
|
+
**Add extra packages:**
|
|
67
|
+
```bash
|
|
68
|
+
streamlit2stlite app.py --add-requirements "scikit-learn,purple-air"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Override completely:**
|
|
72
|
+
```bash
|
|
73
|
+
streamlit2stlite app.py --requirements "streamlit,pandas,numpy"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Full CLI Options
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
usage: streamlit2stlite [-h] [-o OUTPUT] [-r REQUIREMENTS] [-t TITLE]
|
|
80
|
+
[--stlite-version STLITE_VERSION]
|
|
81
|
+
[--add-requirements ADD_REQUIREMENTS] [-v]
|
|
82
|
+
input
|
|
83
|
+
|
|
84
|
+
positional arguments:
|
|
85
|
+
input Path to the input Streamlit Python file
|
|
86
|
+
|
|
87
|
+
options:
|
|
88
|
+
-h, --help show this help message
|
|
89
|
+
-o, --output Path to the output HTML file
|
|
90
|
+
-r, --requirements Comma-separated list of packages to install
|
|
91
|
+
-t, --title Title for the HTML page
|
|
92
|
+
--add-requirements Additional packages to add to auto-detected ones
|
|
93
|
+
-v, --verbose Print verbose output
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## ❓ FAQ
|
|
97
|
+
|
|
98
|
+
**How does this work?**
|
|
99
|
+
It embeds your Python code into a template that loads `stlite` (a port of Streamlit to WebAssembly). When you open the HTML file, your browser downloads a mini Python environment (Pyodide) and runs your code locally.
|
|
100
|
+
|
|
101
|
+
**Can I read local files?**
|
|
102
|
+
Because this runs in the browser, it cannot read files from your hard drive directly (sandbox security). You should use `st.file_uploader` to let users provide files, or embed data directly into your script.
|
|
103
|
+
|
|
104
|
+
**Does it support all Python packages?**
|
|
105
|
+
It supports packages available in [Pyodide](https://pyodide.org/en/stable/usage/packages-in-pyodide.html) (including numpy, pandas, scipy, matplotlib, scikit-learn) and pure Python packages from PyPI (micropip).
|
|
106
|
+
|
|
107
|
+
## 📄 License
|
|
108
|
+
|
|
109
|
+
MIT License. Feel free to use this for whatever you want!
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
streamlit2stlite/__init__.py
|
|
4
|
+
streamlit2stlite/cli.py
|
|
5
|
+
streamlit2stlite/core.py
|
|
6
|
+
streamlit2stlite.egg-info/PKG-INFO
|
|
7
|
+
streamlit2stlite.egg-info/SOURCES.txt
|
|
8
|
+
streamlit2stlite.egg-info/dependency_links.txt
|
|
9
|
+
streamlit2stlite.egg-info/entry_points.txt
|
|
10
|
+
streamlit2stlite.egg-info/requires.txt
|
|
11
|
+
streamlit2stlite.egg-info/top_level.txt
|
|
12
|
+
tests/test_core.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
streamlit2stlite
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from streamlit2stlite.core import (
|
|
3
|
+
escape_for_js_template_literal,
|
|
4
|
+
extract_imports,
|
|
5
|
+
detect_title_from_code,
|
|
6
|
+
convert_streamlit_to_stlite
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
def test_escape_for_js_template_literal():
|
|
10
|
+
# Test backslash escaping
|
|
11
|
+
code = r"print('Hello \n World')"
|
|
12
|
+
escaped = escape_for_js_template_literal(code)
|
|
13
|
+
# The result should have double backslashes for the newline char representation in Python string
|
|
14
|
+
# But effectively passing \\ to JS.
|
|
15
|
+
# In python string literal r"\\" is actually \
|
|
16
|
+
assert r"\\" in escaped
|
|
17
|
+
|
|
18
|
+
# Test backtick escaping
|
|
19
|
+
code = "print(f`Hello`)"
|
|
20
|
+
escaped = escape_for_js_template_literal(code)
|
|
21
|
+
assert r"\`" in escaped
|
|
22
|
+
|
|
23
|
+
# Test template interpolation escaping
|
|
24
|
+
code = "const x = `${variable}`"
|
|
25
|
+
escaped = escape_for_js_template_literal(code)
|
|
26
|
+
assert r"\${" in escaped
|
|
27
|
+
|
|
28
|
+
# Combined test
|
|
29
|
+
code = r"path = 'c:\users\name'"
|
|
30
|
+
# Expected: c:\\users\\name in the JS string
|
|
31
|
+
escaped = escape_for_js_template_literal(code)
|
|
32
|
+
assert r"\\" in escaped
|
|
33
|
+
|
|
34
|
+
def test_extract_imports():
|
|
35
|
+
code = """
|
|
36
|
+
import pandas as pd
|
|
37
|
+
import numpy
|
|
38
|
+
from plotly import express
|
|
39
|
+
import cv2
|
|
40
|
+
import os
|
|
41
|
+
import sys
|
|
42
|
+
"""
|
|
43
|
+
reqs = extract_imports(code)
|
|
44
|
+
|
|
45
|
+
# Check that standard lib is ignored
|
|
46
|
+
assert "os" not in reqs
|
|
47
|
+
assert "sys" not in reqs
|
|
48
|
+
|
|
49
|
+
# Check simple imports
|
|
50
|
+
assert "pandas" in reqs
|
|
51
|
+
assert "numpy" in reqs
|
|
52
|
+
|
|
53
|
+
# Check from imports
|
|
54
|
+
assert "plotly" in reqs
|
|
55
|
+
|
|
56
|
+
# Check mapping
|
|
57
|
+
assert "opencv-python" in reqs
|
|
58
|
+
assert "cv2" not in reqs
|
|
59
|
+
|
|
60
|
+
def test_extract_imports_dependencies():
|
|
61
|
+
code = "import lmfit"
|
|
62
|
+
reqs = extract_imports(code)
|
|
63
|
+
assert "lmfit" in reqs
|
|
64
|
+
assert "scipy" in reqs # lmfit dependency
|
|
65
|
+
|
|
66
|
+
# Test pandas -> xlsxwriter
|
|
67
|
+
code_pandas = "import pandas"
|
|
68
|
+
reqs_pandas = extract_imports(code_pandas)
|
|
69
|
+
assert "pandas" in reqs_pandas
|
|
70
|
+
assert "xlsxwriter" in reqs_pandas
|
|
71
|
+
|
|
72
|
+
def test_detect_title_from_code():
|
|
73
|
+
# Test set_page_config
|
|
74
|
+
code1 = """
|
|
75
|
+
import streamlit as st
|
|
76
|
+
st.set_page_config(page_title="My Cool App")
|
|
77
|
+
"""
|
|
78
|
+
assert detect_title_from_code(code1) == "My Cool App"
|
|
79
|
+
|
|
80
|
+
# Test st.title
|
|
81
|
+
code2 = """
|
|
82
|
+
import streamlit as st
|
|
83
|
+
st.title("My Title")
|
|
84
|
+
"""
|
|
85
|
+
assert detect_title_from_code(code2) == "My Title"
|
|
86
|
+
|
|
87
|
+
# Test emoji cleanup
|
|
88
|
+
code3 = """
|
|
89
|
+
st.title("🚀 Blast Off")
|
|
90
|
+
"""
|
|
91
|
+
assert detect_title_from_code(code3) == "Blast Off"
|
|
92
|
+
|
|
93
|
+
# Test default
|
|
94
|
+
code4 = "print('hello')"
|
|
95
|
+
assert detect_title_from_code(code4) == "Streamlit App"
|
|
96
|
+
|
|
97
|
+
def test_convert_streamlit_to_stlite():
|
|
98
|
+
code = "import streamlit as st\nst.write('Hello')"
|
|
99
|
+
html = convert_streamlit_to_stlite(code, title="Test App")
|
|
100
|
+
|
|
101
|
+
assert "<!doctype html>" in html
|
|
102
|
+
assert "<title>Test App</title>" in html
|
|
103
|
+
assert "stlite.js" in html
|
|
104
|
+
assert "stlite.css" in html
|
|
105
|
+
assert "streamlit_app_code = `" in html
|
|
106
|
+
assert "mount(" in html
|
|
107
|
+
|
|
108
|
+
# Check that code is present
|
|
109
|
+
assert "st.write('Hello')" in html
|