Skip to content

Commit 36c6642

Browse files
committed
Use local ReactPy wheel for unpublished releases.
1 parent 2119fb1 commit 36c6642

File tree

2 files changed

+90
-4
lines changed

2 files changed

+90
-4
lines changed

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ urls.Source = "https://github.com/reactive-python/reactpy"
4141

4242
[project.optional-dependencies]
4343
all = ["reactpy[asgi,jinja,uvicorn,testing]"]
44-
standard = ["reactpy[asgi]"]
45-
asgi = ["asgiref", "asgi-tools", "servestatic", "orjson"]
44+
asgi = ["asgiref", "asgi-tools", "servestatic", "orjson", "pip"]
4645
jinja = ["jinja2-simple-tags", "jinja2 >=3"]
4746
uvicorn = ["uvicorn[standard]"]
4847
testing = ["playwright"]

src/reactpy/pyscript/utils.py

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
# ruff: noqa: S603, S607
12
from __future__ import annotations
23

34
import functools
45
import json
6+
import shutil
7+
import subprocess
58
import textwrap
9+
from glob import glob
10+
from logging import getLogger
611
from pathlib import Path
712
from typing import TYPE_CHECKING, Any
813
from uuid import uuid4
@@ -11,7 +16,7 @@
1116
import orjson
1217

1318
import 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
1520
from reactpy.types import VdomDict
1621
from reactpy.utils import vdom_to_html
1722

@@ -25,6 +30,7 @@
2530
PYSCRIPT_LAYOUT_HANDLER = (Path(__file__).parent / "layout_handler.py").read_text(
2631
encoding="utf-8"
2732
)
33+
_logger = getLogger(__name__)
2834

2935

3036
def 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
121208
def cached_file_read(file_path: str) -> str:
122209
return Path(file_path).read_text(encoding="utf-8").strip()

0 commit comments

Comments
 (0)