1313import sys
1414import zipfile
1515from pathlib import Path
16+ from typing import Literal
1617
1718from tool_utils import hexdigest , run
1819
1920PYPROJECT_TEMPLATE = """[project]
2021name = "vendor-test"
2122version = "0.1.0"
2223requires-python = ">=3.12"
23- dependencies = [
24- "{package_name}"
25- ]
24+ dependencies = {dependencies}
2625
2726[dependency-groups]
28- dev = ["workers-py"]
27+ dev = ["workers-py>=1.6 "]
2928"""
3029
30+ WRANGLER_TOML = """
31+ name = "hello-python-bindings"
32+ main = "src/entry.py"
33+ compatibility_flags = {compat_flags}
34+ compatibility_date = "2025-08-14"
35+ """
36+
37+ type PyVer = Literal ["3.12" , "3.13" ]
3138
32- def create_pyproject_toml (package_name : str , target_dir : Path ) -> Path :
39+
40+ def create_pyproject_toml (package_names : list [str ], target_dir : Path ) -> Path :
3341 """Create a pyproject.toml file with the specified package as a dependency."""
34- pyproject_content = PYPROJECT_TEMPLATE .format (package_name = package_name )
42+ pyproject_content = PYPROJECT_TEMPLATE .format (dependencies = repr ( package_names ) )
3543 pyproject_path = target_dir / "pyproject.toml"
3644 pyproject_path .write_text (pyproject_content )
37- # Also create a wrangler file as otherwise pywrangler won't run
38- wrangler_path = target_dir / "wrangler.toml"
39- wrangler_path .write_text ("\n " )
4045 return pyproject_path
4146
4247
43- def run_pywrangler_sync (work_dir : Path , python : str | None ) -> Path :
48+ def create_wrangler_toml (target_dir : Path , python : PyVer ):
49+ compat_flags = ["python_workers" ]
50+ match python :
51+ case "3.13" :
52+ compat_flags .append ("python_workers_20250116" )
53+
54+ (target_dir / "wrangler.toml" ).write_text (
55+ WRANGLER_TOML .format (compat_flags = repr (compat_flags ))
56+ )
57+
58+
59+ def run_pywrangler_sync (work_dir : Path ) -> Path :
4460 """Run `uv run pywrangler sync` in the specified directory."""
4561 env = os .environ .copy ()
4662 env ["_PYODIDE_EXTRA_MOUNTS" ] = str (work_dir )
47- if python :
48- env ["_PYWRANGLER_PYTHON_VERSION" ] = python
4963 # TODO: Make pywrangler understand how to use Python 3.13 correctly and
5064 # remove these extra commands
51- run (["uv" , "venv" ], cwd = work_dir , env = env )
5265 run (["uv" , "run" , "pywrangler" , "sync" ], cwd = work_dir , env = env )
5366 python_modules_dir = work_dir / "python_modules"
5467 if not python_modules_dir .exists ():
@@ -57,7 +70,7 @@ def run_pywrangler_sync(work_dir: Path, python: str | None) -> Path:
5770 return python_modules_dir
5871
5972
60- def create_zip_archive (source_dir : Path , package_name : str , output_dir : Path ) -> bool :
73+ def create_zip_archive (source_dir : Path , output_dir : Path ) -> bool :
6174 """Create a zip archive of the python_modules directory.
6275
6376 Return value indicates whether the archive includes any binary modules (.so
@@ -78,10 +91,11 @@ def create_zip_archive(source_dir: Path, package_name: str, output_dir: Path) ->
7891 return native
7992
8093
81- def vendor_package (package_name : str , python : str ) -> tuple [Path , bool ]:
94+ def vendor_package (package_names : list [ str ] , python : PyVer ) -> tuple [Path , bool ]:
8295 """Main function to vendor a Python package."""
8396 tmp_dir = Path ("/tmp" )
84- work_dir = tmp_dir / f"vendor-{ package_name } "
97+ vendor_name = "-" .join (package_names )
98+ work_dir = tmp_dir / f"vendor-{ vendor_name } "
8599
86100 # Clean up any existing work directory
87101 if work_dir .exists ():
@@ -91,21 +105,22 @@ def vendor_package(package_name: str, python: str) -> tuple[Path, bool]:
91105 try :
92106 # Create pyproject.toml
93107 print (f"Creating pyproject.toml in { work_dir } " )
94- create_pyproject_toml (package_name , work_dir )
108+ create_pyproject_toml (package_names , work_dir )
109+ create_wrangler_toml (work_dir , python )
95110
96111 # Run pywrangler sync
97112 print ("Running uv run pywrangler sync..." )
98- python_modules_dir = run_pywrangler_sync (work_dir , python )
113+ python_modules_dir = run_pywrangler_sync (work_dir )
99114
100115 # Create zip archive
101116 print ("Creating zip archive..." )
102- native = create_zip_archive (python_modules_dir , package_name , tmp_dir )
117+ native = create_zip_archive (python_modules_dir , tmp_dir )
103118 py = f"-{ python } " if native else ""
104- name = f"{ package_name } { py } -vendored-for-ew-testing.zip"
105- zip_path = tmp_dir / name
119+ zip_name = f"{ vendor_name } { py } -vendored-for-ew-testing.zip"
120+ zip_path = tmp_dir / zip_name
106121 shutil .move (tmp_dir / "tmp.zip" , zip_path )
107122 except Exception as e :
108- print (f"Error vendoring package { package_name } : { e } " )
123+ print (f"Error vendoring packages { package_names !r } : { e } " )
109124 sys .exit (1 )
110125 else :
111126 print (f"Successfully created: { zip_path } " )
@@ -125,7 +140,9 @@ def main() -> int:
125140 parser = argparse .ArgumentParser (
126141 description = "Create a zip file of a vendored Python package's source files for vendored_py_wd_test."
127142 )
128- parser .add_argument ("package_name" , help = "Name of the Python package to vendor" )
143+ parser .add_argument (
144+ "package_name" , help = "Name of the Python package to vendor" , nargs = "*"
145+ )
129146 parser .add_argument ("-p" , "--python" , help = "Name of the Python version to use" )
130147
131148 args = parser .parse_args ()
@@ -143,7 +160,7 @@ def main() -> int:
143160 i1 = " " * 12
144161 i2 = " " * 16
145162 print (i1 + "{" )
146- print (i2 + f'"name": "{ args .package_name } ",' )
163+ print (i2 + f'"name": "{ "-" . join ( args .package_name ) } ",' )
147164 print (i2 + f'"abi": "{ abi } ",' )
148165 print (i2 + f'"sha256": "{ hexdigest (zip_path )} ",' )
149166 print (i1 + "}," )
0 commit comments