Skip to content

Commit a460583

Browse files
committed
[feat] Update scripts and README
1 parent 141de7c commit a460583

File tree

2 files changed

+206
-53
lines changed

2 files changed

+206
-53
lines changed

README.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,90 @@ Please file [bug reports](https://github.com/streamlit/streamlit/issues/new?temp
1717
## 📦 Installation
1818

1919
```bash
20-
pip install streamlit-bokeh
20+
uv pip install streamlit-bokeh
2121
```
2222

2323
Ensure you have **Streamlit** and **Bokeh** installed as well:
2424

2525
```bash
26-
pip install streamlit bokeh
26+
uv pip install streamlit bokeh
27+
```
28+
29+
---
30+
31+
## 🛠️ Development
32+
33+
### Prerequisites
34+
35+
- **Python** 3.10–3.13
36+
- **Node.js** 24.x.y (see `.nvmrc`)
37+
- **uv** (fast Python package manager)
38+
39+
### 1) Create and activate a virtual environment
40+
41+
```bash
42+
uv venv .venv
43+
source .venv/bin/activate
44+
```
45+
46+
### 2) Install Python dependencies from `pyproject.toml`
47+
48+
```bash
49+
# Minimal runtime install (editable)
50+
uv pip install -e .
51+
52+
# Recommended for development (includes tests/tools)
53+
uv pip install -e ".[devel]"
54+
```
55+
56+
### 3) Install and build the frontend
57+
58+
```bash
59+
cd streamlit_bokeh/frontend
60+
corepack enable
61+
yarn install
62+
yarn build # one-time build to produce frontend/build assets
63+
64+
# Optional: frontend dev server
65+
# Use `yarn dev:v2` to utilize the Custom Component v2 frontend (recommended).
66+
# Use `yarn dev:v1` to utilize the Custom Component v1 frontend (legacy).
67+
yarn dev:v2
68+
```
69+
70+
### 4) Run a local demo
71+
72+
```bash
73+
streamlit run ./e2e_playwright/bokeh_chart_basics.py
74+
```
75+
76+
### 5) Run tests
77+
78+
Python end-to-end tests (Playwright):
79+
80+
```bash
81+
# Build the package
82+
uv build
83+
# Install the test dependencies
84+
uv pip install -r e2e_playwright/test-requirements.txt
85+
# Install browsers (first time only)
86+
python -m playwright install --with-deps
87+
# Run tests
88+
pytest e2e_playwright -n auto
89+
```
90+
91+
Frontend tests and type checks:
92+
93+
```bash
94+
cd streamlit_bokeh/frontend
95+
yarn test
96+
yarn typecheck
97+
```
98+
99+
### 6) Build the Python package (optional)
100+
101+
```bash
102+
uv build
103+
ls dist/
27104
```
28105

29106
---

scripts/update_bokeh_version.py

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414

1515
import os
1616
import re
17+
import subprocess
18+
1719
import requests
1820
import semver
19-
import subprocess
21+
import toml
2022

21-
SETUP_PY_PATH = "setup.py"
23+
PYPROJECT_TOML_PATH = "pyproject.toml"
24+
PACKAGE_PYPROJECT_TOML_PATH = "streamlit_bokeh/pyproject.toml"
2225

2326

2427
def get_latest_bokeh_version():
@@ -31,29 +34,31 @@ def get_latest_bokeh_version():
3134

3235

3336
def get_component_version():
34-
with open(SETUP_PY_PATH, "r") as f:
35-
setup_content = f.read()
37+
with open(PYPROJECT_TOML_PATH, "r") as f:
38+
pyproject_data = toml.load(f)
3639

37-
# Extract version from setup.py
38-
match = re.search(r"version\s*=\s*['\"]([\d\.]+)(['\"])", setup_content)
40+
# Extract version from pyproject.toml
41+
version = pyproject_data.get("project", {}).get("version")
3942

40-
if match:
41-
return match.group(1)
43+
if version:
44+
return version
4245
else:
43-
raise ValueError("Bokeh version not found in the file")
46+
raise ValueError("Component version not found in pyproject.toml")
4447

4548

4649
def get_dependency_bokeh_version():
47-
with open(SETUP_PY_PATH, "r") as f:
48-
setup_content = f.read()
50+
with open(PYPROJECT_TOML_PATH, "r") as f:
51+
pyproject_data = toml.load(f)
4952

50-
# Extract Bokeh version from dependency line (e.g., bokeh==2.4.3)
51-
match = re.search(r"bokeh\s*==\s*([\d\.]+)", setup_content)
53+
# Extract Bokeh version from dependencies list
54+
dependencies = pyproject_data.get("project", {}).get("dependencies", [])
5255

53-
if match:
54-
return match.group(1)
55-
else:
56-
raise ValueError("Bokeh version not found in the file")
56+
for dep in dependencies:
57+
match = re.search(r"bokeh\s*==\s*([\d\.]+)", dep)
58+
if match:
59+
return match.group(1)
60+
61+
raise ValueError("Bokeh version not found in dependencies")
5762

5863

5964
def download_files(new_version, destination):
@@ -67,6 +72,9 @@ def download_files(new_version, destination):
6772
f"https://raw.githubusercontent.com/bokeh/bokeh/refs/tags/{new_version}/LICENSE.txt",
6873
]
6974

75+
# Ensure destination/bokeh directory exists
76+
os.makedirs(os.path.join(destination, "bokeh"), exist_ok=True)
77+
7078
for url in files_to_download:
7179
filename = os.path.basename(url)
7280
print(f"Downloading {filename}")
@@ -77,28 +85,48 @@ def download_files(new_version, destination):
7785
f.write(chunk)
7886

7987

80-
def update_setup_py(new_version, old_bokeh_version, new_bokeh_version):
81-
with open(SETUP_PY_PATH, "r") as f:
82-
setup_content = f.read()
88+
def update_pyproject_toml(new_version, old_bokeh_version, new_bokeh_version):
89+
"""
90+
Update the project version and Bokeh dependency in the TOML files
91+
without disturbing comments, headers, or table ordering.
92+
93+
We intentionally operate on raw text instead of round-tripping through
94+
a TOML serializer (which would drop comments and reformat sections).
95+
"""
96+
for path in (PYPROJECT_TOML_PATH, PACKAGE_PYPROJECT_TOML_PATH):
97+
if not os.path.exists(path):
98+
raise FileNotFoundError(f"File {path} not found")
99+
100+
with open(path, "r", encoding="utf-8") as f:
101+
contents = f.read()
102+
103+
# 1) Update the [project] version line.
104+
#
105+
# All pyproject.toml files in this repo have a single `version = "..."`
106+
# entry under `[project]`. Anchoring at line-start ensures we don't
107+
# accidentally rewrite a value in another table or in comments.
108+
contents, replaced_version_count = re.subn(
109+
r'(?m)^(version\s*=\s*")([^"]+)(")',
110+
lambda m: f"{m.group(1)}{new_version}{m.group(3)}",
111+
contents,
112+
count=1,
113+
)
83114

84-
# Replace package version in `version='...'`
85-
# This pattern is naive; adapt as needed for your file structure.
86-
setup_content = re.sub(
87-
r"(version\s*=\s*['\"])([\d\.]+)(['\"])",
88-
rf"\g<1>{new_version}\g<3>",
89-
setup_content,
90-
)
115+
if replaced_version_count == 0:
116+
raise ValueError(f"Could not find project version line in {path}")
91117

92-
# Replace bokeh==old_version with bokeh==new_version
93-
if old_bokeh_version:
94-
setup_content = re.sub(
95-
rf"(bokeh\s*==\s*){old_bokeh_version}",
96-
rf"\g<1>{new_bokeh_version}",
97-
setup_content,
98-
)
118+
# 2) Update the Bokeh dependency version. This only has an effect in the
119+
# root pyproject, since the package-local pyproject does not currently
120+
# declare dependencies.
121+
if old_bokeh_version:
122+
contents = re.sub(
123+
rf"(bokeh\s*==\s*){re.escape(old_bokeh_version)}",
124+
rf"\g<1>{new_bokeh_version}",
125+
contents,
126+
)
99127

100-
with open(SETUP_PY_PATH, "w") as f:
101-
f.write(setup_content)
128+
with open(path, "w", encoding="utf-8") as f:
129+
f.write(contents)
102130

103131

104132
def update_test_requirements(
@@ -160,16 +188,56 @@ def update_init_py(old_bokeh_version, new_bokeh_version):
160188
f.write(init_py_contents)
161189

162190

163-
def update_index_html(public_dir, old_version, new_version):
164-
index_html_path = os.path.join(public_dir, "index.html")
165-
if os.path.exists(index_html_path):
166-
with open(index_html_path, "r", encoding="utf-8") as f:
191+
def update_loader_imports(old_bokeh_version, new_bokeh_version):
192+
"""
193+
Update versioned Bokeh asset import paths in the TypeScript loader to the new version.
194+
195+
This replaces occurrences like `bokeh-3.8.0.min.js` with `bokeh-<new>.min.js`
196+
in `streamlit_bokeh/frontend/src/v2/loaders.ts`.
197+
"""
198+
loader_path = "streamlit_bokeh/frontend/src/v2/loaders.ts"
199+
with open(loader_path, "r", encoding="utf-8") as f:
200+
contents = f.read()
201+
202+
suffixes = ["mathjax", "gl", "api", "tables", "widgets", ""]
203+
for suffix in suffixes:
204+
old_str = (
205+
f"bokeh-{suffix}-{old_bokeh_version}.min.js"
206+
if suffix
207+
else f"bokeh-{old_bokeh_version}.min.js"
208+
)
209+
new_str = (
210+
f"bokeh-{suffix}-{new_bokeh_version}.min.js"
211+
if suffix
212+
else f"bokeh-{new_bokeh_version}.min.js"
213+
)
214+
contents = contents.replace(old_str, new_str)
215+
216+
with open(loader_path, "w", encoding="utf-8") as f:
217+
f.write(contents)
218+
219+
220+
def update_index_html(frontend_dir, old_version, new_version):
221+
"""
222+
Update the index.html files to reference the new Bokeh asset version.
223+
Only the source Vite entry point (`frontend/index.html`) needs to be updated,
224+
since build artifacts are regenerated.
225+
"""
226+
227+
index_html_paths = [os.path.join(frontend_dir, "index.html")]
228+
229+
cdn_suffixes = ["mathjax", "gl", "api", "tables", "widgets", ""]
230+
found_index = False
231+
232+
for path in index_html_paths:
233+
if not os.path.exists(path):
234+
continue
235+
236+
found_index = True
237+
with open(path, "r", encoding="utf-8") as f:
167238
html_content = f.read()
168239

169-
# If old_version is known, do a direct replacement
170240
if old_version:
171-
# Replace each script reference with the new version
172-
cdn_suffixes = ["mathjax", "gl", "api", "tables", "widgets", ""]
173241
for suffix in cdn_suffixes:
174242
old_str = (
175243
f"bokeh-{suffix}-{old_version}.min.js"
@@ -183,10 +251,13 @@ def update_index_html(public_dir, old_version, new_version):
183251
)
184252
html_content = html_content.replace(old_str, new_str)
185253

186-
with open(index_html_path, "w", encoding="utf-8") as f:
254+
with open(path, "w", encoding="utf-8") as f:
187255
f.write(html_content)
188-
else:
189-
print("No index.html found in frontend/public. Skipping HTML update.")
256+
257+
if not found_index:
258+
print(
259+
"No index.html found under streamlit_bokeh/frontend. Skipping HTML update."
260+
)
190261

191262

192263
def check_remote_branch_exists(remote: str, new_version: str) -> bool:
@@ -243,20 +314,25 @@ def check_remote_branch_exists(remote: str, new_version: str) -> bool:
243314

244315
print("New version available!")
245316
public_dir = "streamlit_bokeh/frontend/public"
317+
frontend_dir = "streamlit_bokeh/frontend"
246318

247-
# Remove original files
319+
# Remove original files from the public bokeh directory
248320
bokeh_dir = os.path.join(public_dir, "bokeh")
321+
os.makedirs(bokeh_dir, exist_ok=True)
249322
for filename in os.listdir(bokeh_dir):
250323
if "bokeh" in filename and filename.endswith(".js"):
251324
os.remove(os.path.join(bokeh_dir, filename))
252325

326+
# Download new Bokeh assets into the public directory so they are served
327+
# from `/bokeh` at runtime.
253328
download_files(new_bokeh_version, public_dir)
254-
# Update the bokeh dependency version in index.html and __init__.py
255-
update_index_html(public_dir, old_bokeh_version, new_bokeh_version)
329+
# Update the bokeh dependency version in index.html, TS loader and __init__.py
330+
update_index_html(frontend_dir, old_bokeh_version, new_bokeh_version)
331+
update_loader_imports(old_bokeh_version, new_bokeh_version)
256332
update_init_py(old_bokeh_version, new_bokeh_version)
257333

258-
# Update the bokeh dependency version and component version in setup.py and test-requirements.txt
259-
update_setup_py(new_version, old_bokeh_version, new_bokeh_version)
334+
# Update the bokeh dependency version in pyproject.toml and test-requirements.txt
335+
update_pyproject_toml(new_version, old_bokeh_version, new_bokeh_version)
260336
update_test_requirements(
261337
old_bokeh_version, new_bokeh_version, old_version, new_version
262338
)

0 commit comments

Comments
 (0)