Skip to content

Commit 0340dc7

Browse files
committed
fix: install dynamic library dependencies for scipy if present
This installs libopenblas.so if scipy is installed. It's a bit ad-hoc for now.
1 parent 1fb1565 commit 0340dc7

5 files changed

Lines changed: 202 additions & 6 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,6 @@ repos:
4444
exclude: (.*test.*)
4545
additional_dependencies:
4646
- click
47+
- types-requests
4748
ci:
4849
autoupdate_schedule: "quarterly"

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ dependencies = [
1919
"pyodide-cli",
2020
"pyjson5>=1.6.0",
2121
"pyodide-py",
22+
"requests",
23+
"types-requests>=2.32.4.20250913",
2224
]
2325

2426
[dependency-groups]

src/pywrangler/sync.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
import json
12
import logging
23
import os
34
import shutil
45
import tempfile
56
from collections.abc import Iterator
67
from contextlib import contextmanager
78
from pathlib import Path
9+
from typing import TypedDict, cast
810

911
import click
12+
import requests
1013

1114
from .utils import (
1215
check_uv_version,
1316
check_wrangler_config,
1417
find_pyproject_toml,
1518
get_project_root,
19+
get_pyodide_base_url,
1620
get_pyodide_index,
21+
get_pyodide_lock_url,
1722
get_python_version,
1823
get_uv_pyodide_interp_name,
1924
read_pyproject_toml,
@@ -31,8 +36,12 @@ def get_venv_workers_token_path() -> Path:
3136
return get_venv_workers_path() / ".synced"
3237

3338

39+
def get_vendor_path() -> Path:
40+
return get_project_root() / "python_modules"
41+
42+
3443
def get_vendor_token_path() -> Path:
35-
return get_project_root() / "python_modules/.synced"
44+
return get_vendor_path() / ".synced"
3645

3746

3847
def get_pyodide_venv_path() -> Path:
@@ -160,7 +169,7 @@ def temp_requirements_file(requirements: list[str]) -> Iterator[str]:
160169

161170

162171
def _install_requirements_to_vendor(requirements: list[str]) -> None:
163-
vendor_path = get_project_root() / "python_modules"
172+
vendor_path = get_vendor_path()
164173
logger.debug(f"Using vendor path: {vendor_path}")
165174

166175
if len(requirements) == 0:
@@ -239,9 +248,49 @@ def _install_requirements_to_venv(requirements: list[str]) -> None:
239248
)
240249

241250

251+
class PyodidePackageMetadata(TypedDict):
252+
depends: list[str]
253+
package_type: str
254+
file_name: str
255+
256+
257+
def _get_pyodide_lock() -> dict[str, PyodidePackageMetadata]:
258+
lock_path = get_venv_workers_path() / "pyodide-lock.json"
259+
if not lock_path.exists():
260+
url = get_pyodide_lock_url()
261+
logger.info(f"Fetching pyodide lock from {url}")
262+
req = requests.get(url)
263+
req.raise_for_status()
264+
lock_path.write_bytes(req.content)
265+
with lock_path.open() as f:
266+
res = json.load(f)
267+
return cast(dict[str, PyodidePackageMetadata], res["packages"])
268+
269+
270+
def _maybe_install_dylibs() -> None:
271+
vendor_path = get_vendor_path()
272+
if not (vendor_path / "scipy").exists():
273+
return
274+
libdir = vendor_path / "lib"
275+
libdir.mkdir(exist_ok=True)
276+
lock = _get_pyodide_lock()
277+
for depname in lock["scipy"]["depends"]:
278+
dep = lock[depname]
279+
if dep["package_type"] == "shared_library":
280+
file_name = dep["file_name"]
281+
url = get_pyodide_base_url() + file_name
282+
req = requests.get(url)
283+
req.raise_for_status()
284+
with tempfile.TemporaryDirectory() as d:
285+
p = Path(d) / file_name
286+
p.write_bytes(req.content)
287+
shutil.unpack_archive(filename=p, extract_dir=libdir)
288+
289+
242290
def install_requirements(requirements: list[str]) -> None:
243291
_install_requirements_to_vendor(requirements)
244292
_install_requirements_to_venv(requirements)
293+
_maybe_install_dylibs()
245294

246295

247296
def _is_out_of_date(token: Path, time: float) -> bool:

src/pywrangler/utils.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,23 @@ def get_uv_pyodide_interp_name() -> str:
314314
return f"cpython-{v}-emscripten-wasm32-musl"
315315

316316

317-
def get_pyodide_index() -> str:
317+
def get_pyodide_version() -> str:
318318
match get_python_version():
319319
case "3.12":
320-
v = "0.27.7"
320+
return "0.27.7"
321321
case "3.13":
322-
v = "0.28.3"
323-
return "https://index.pyodide.org/" + v
322+
return "0.28.3"
323+
324+
325+
def get_pyodide_index() -> str:
326+
return "https://index.pyodide.org/" + get_pyodide_version()
327+
328+
329+
def get_pyodide_base_url() -> str:
330+
ver = get_pyodide_version()
331+
return f"https://cdn.jsdelivr.net/pyodide/v{ver}/full/"
332+
333+
334+
def get_pyodide_lock_url() -> str:
335+
base = get_pyodide_base_url()
336+
return base + "pyodide-lock.json"

0 commit comments

Comments
 (0)