1+ import json
12import logging
23import os
34import shutil
45import tempfile
56from collections .abc import Iterator
67from contextlib import contextmanager
78from pathlib import Path
9+ from typing import TypedDict , cast
810
911import click
12+ import requests
1013
1114from .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+
3443def get_vendor_token_path () -> Path :
35- return get_project_root () / "python_modules/ .synced"
44+ return get_vendor_path () / ".synced"
3645
3746
3847def get_pyodide_venv_path () -> Path :
@@ -160,7 +169,7 @@ def temp_requirements_file(requirements: list[str]) -> Iterator[str]:
160169
161170
162171def _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+
242290def install_requirements (requirements : list [str ]) -> None :
243291 _install_requirements_to_vendor (requirements )
244292 _install_requirements_to_venv (requirements )
293+ _maybe_install_dylibs ()
245294
246295
247296def _is_out_of_date (token : Path , time : float ) -> bool :
0 commit comments