1+ # ruff: noqa: S603, S607
12from __future__ import annotations
23
34import functools
45import json
6+ import shutil
7+ import subprocess
58import textwrap
9+ from glob import glob
10+ from logging import getLogger
611from pathlib import Path
712from typing import TYPE_CHECKING , Any
813from uuid import uuid4
1116import orjson
1217
1318import reactpy
14- from reactpy .config import REACTPY_DEBUG , REACTPY_PATH_PREFIX
19+ from reactpy .config import REACTPY_DEBUG , REACTPY_PATH_PREFIX , REACTPY_WEB_MODULES_DIR
1520from reactpy .types import VdomDict
1621from reactpy .utils import vdom_to_html
1722
2530PYSCRIPT_LAYOUT_HANDLER = (Path (__file__ ).parent / "layout_handler.py" ).read_text (
2631 encoding = "utf-8"
2732)
33+ _logger = getLogger (__name__ )
2834
2935
3036def render_pyscript_executor (file_paths : tuple [str , ...], uuid : str , root : str ) -> str :
@@ -91,7 +97,7 @@ def extend_pyscript_config(
9197 # Extends ReactPy's default PyScript config with user provided values.
9298 pyscript_config : dict [str , Any ] = {
9399 "packages" : [
94- f"reactpy== { reactpy . __version__ } " ,
100+ reactpy_version_string () ,
95101 f"jsonpointer=={ jsonpointer .__version__ } " ,
96102 "ssl" ,
97103 ],
@@ -117,6 +123,87 @@ def extend_pyscript_config(
117123 return orjson .dumps (pyscript_config ).decode ("utf-8" )
118124
119125
126+ @functools .cache
127+ def reactpy_version_string () -> str : # pragma: no cover
128+ local_version = reactpy .__version__
129+
130+ # Get a list of all versions via `pip index versions`
131+ result = subprocess .run (
132+ ["pip" , "index" , "versions" , "reactpy" ],
133+ capture_output = True ,
134+ text = True ,
135+ check = False ,
136+ )
137+
138+ # Check if the command failed
139+ if result .returncode != 0 :
140+ _logger .warning (
141+ "Failed to verify what versions of ReactPy exist on PyPi. "
142+ "PyScript functionality may not work as expected." ,
143+ )
144+ return f"reactpy=={ local_version } "
145+
146+ # Have `pip` tell us what versions are available
147+ available_version_symbol = "Available versions: "
148+ latest_version_symbol = "LATEST: "
149+ known_versions : list [str ] = []
150+ latest_version : str = ""
151+ for line in result .stdout .splitlines ():
152+ if line .startswith (available_version_symbol ):
153+ known_versions .extend (line [len (available_version_symbol ) :].split (", " ))
154+ elif latest_version_symbol in line :
155+ symbol_postion = line .index (latest_version_symbol )
156+ latest_version = line [symbol_postion + len (latest_version_symbol ) :].strip ()
157+
158+ # Return early if local version of ReactPy is available on PyPi
159+ if local_version in known_versions :
160+ return f"reactpy=={ local_version } "
161+
162+ # Begin determining an alternative method of installing ReactPy
163+ _logger .warning (
164+ "'reactpy==%s' is not available on PyPi, "
165+ "Attempting to determine an alternative to use within PyScript..." ,
166+ local_version ,
167+ )
168+ if not latest_version :
169+ _logger .warning ("Failed to determine the latest version of ReactPy on PyPi. " )
170+
171+ # Build a local wheel for ReactPy, if needed
172+ dist_dir = Path (reactpy .__file__ ).parent .parent .parent / "dist"
173+ wheel_glob = glob (str (dist_dir / f"reactpy-{ local_version } -*.whl" ))
174+ if not wheel_glob :
175+ _logger .warning ("Attempting to build a local wheel for ReactPy..." )
176+ subprocess .run (
177+ ["hatch" , "build" , "-t" , "wheel" ],
178+ capture_output = True ,
179+ text = True ,
180+ check = False ,
181+ cwd = Path (reactpy .__file__ ).parent .parent .parent ,
182+ )
183+ wheel_glob = glob (str (dist_dir / f"reactpy-{ local_version } -*.whl" ))
184+
185+ # Building a local wheel failed, find an alternative installation method
186+ if not wheel_glob :
187+ if latest_version :
188+ _logger .warning (
189+ "Failed to build a local wheel for ReactPy, likely due to missing build dependencies. "
190+ "PyScript will default to using the latest ReactPy version on PyPi."
191+ )
192+ return f"reactpy=={ latest_version } "
193+ _logger .error (
194+ "Failed to build a local wheel for ReactPy and could not determine the latest version on PyPi. "
195+ "PyScript functionality may not work as expected." ,
196+ )
197+ return f"reactpy=={ local_version } "
198+
199+ # Move the wheel file to the web_modules directory, if needed
200+ wheel_file = Path (wheel_glob [0 ])
201+ new_path = REACTPY_WEB_MODULES_DIR .current / wheel_file .name
202+ if not new_path .exists ():
203+ shutil .copy (wheel_file , new_path )
204+ return f"{ REACTPY_PATH_PREFIX .current } modules/{ wheel_file .name } "
205+
206+
120207@functools .cache
121208def cached_file_read (file_path : str ) -> str :
122209 return Path (file_path ).read_text (encoding = "utf-8" ).strip ()
0 commit comments