From e9f6d33bfb812cff15985cbdf491f77a2a673c78 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 14 Dec 2025 23:06:52 -0800
Subject: [PATCH 1/9] Remove unneeded caplog fixtures
---
tests/test_asgi/test_middleware.py | 2 +-
tests/test_core/test_hooks.py | 4 ++--
tests/test_core/test_layout.py | 4 ++--
tests/test_reactjs/test_utils.py | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/tests/test_asgi/test_middleware.py b/tests/test_asgi/test_middleware.py
index 81d7dab88..7cacddf0c 100644
--- a/tests/test_asgi/test_middleware.py
+++ b/tests/test_asgi/test_middleware.py
@@ -119,7 +119,7 @@ async def app(scope, receive, send): ...
assert response.status_code == 404
-async def test_templatetag_bad_kwargs(caplog, browser):
+async def test_templatetag_bad_kwargs(browser):
"""Override for the display fixture that uses ReactPyMiddleware."""
templates = Jinja2Templates(
env=JinjaEnvironment(
diff --git a/tests/test_core/test_hooks.py b/tests/test_core/test_hooks.py
index 93e64a692..804b055c5 100644
--- a/tests/test_core/test_hooks.py
+++ b/tests/test_core/test_hooks.py
@@ -518,7 +518,7 @@ async def effect():
await asyncio.wait_for(cleanup_ran.wait(), 1)
-async def test_use_async_effect_cancel(caplog):
+async def test_use_async_effect_cancel():
component_hook = HookCatcher()
effect_ran = asyncio.Event()
effect_was_cancelled = asyncio.Event()
@@ -557,7 +557,7 @@ async def effect():
event_that_never_occurs.set()
-async def test_error_in_effect_is_gracefully_handled(caplog):
+async def test_error_in_effect_is_gracefully_handled():
@reactpy.component
def ComponentWithEffect():
@reactpy.hooks.use_effect
diff --git a/tests/test_core/test_layout.py b/tests/test_core/test_layout.py
index 07ce0992d..cb017ea70 100644
--- a/tests/test_core/test_layout.py
+++ b/tests/test_core/test_layout.py
@@ -63,7 +63,7 @@ def test_layout_expects_abstract_component():
Layout(reactpy.html.div())
-async def test_layout_cannot_be_used_outside_context_manager(caplog):
+async def test_layout_cannot_be_used_outside_context_manager():
@reactpy.component
def Component(): ...
@@ -691,7 +691,7 @@ def HasNestedEventHandler():
assert last_event_handler() is None
-async def test_duplicate_sibling_keys_causes_error(caplog):
+async def test_duplicate_sibling_keys_causes_error():
hook = HookCatcher()
should_error = True
diff --git a/tests/test_reactjs/test_utils.py b/tests/test_reactjs/test_utils.py
index 95bac62e6..ecf746af0 100644
--- a/tests/test_reactjs/test_utils.py
+++ b/tests/test_reactjs/test_utils.py
@@ -36,7 +36,7 @@ def test_module_name_suffix(name, suffix):
@responses.activate
-def test_resolve_module_exports_from_file(caplog):
+def test_resolve_module_exports_from_file():
responses.add(
responses.GET,
"https://some.external.url",
From 696807a47f136a92d4387573148b711806d0b5bb Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 14 Dec 2025 23:07:33 -0800
Subject: [PATCH 2/9] Replace `print` with `logger.*` in `DisplayFixture`
---
src/reactpy/testing/display.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/reactpy/testing/display.py b/src/reactpy/testing/display.py
index 33a6e6295..4dc4c53cb 100644
--- a/src/reactpy/testing/display.py
+++ b/src/reactpy/testing/display.py
@@ -2,6 +2,7 @@
import os
from contextlib import AsyncExitStack
+from logging import getLogger
from types import TracebackType
from typing import TYPE_CHECKING, Any
@@ -14,6 +15,8 @@
if TYPE_CHECKING:
import pytest
+_logger = getLogger(__name__)
+
class DisplayFixture:
"""A fixture for running web-based tests using ``playwright``"""
@@ -77,12 +80,16 @@ async def configure_page(self) -> None:
self.page.set_default_timeout(self.timeout * 1000)
self.page.on(
"requestfailed",
- lambda x: print(f"BROWSER LOAD ERROR: {x.url}\n{x.failure}"), # noqa: T201
+ lambda x: _logger.error(f"BROWSER LOAD ERROR: {x.url}\n{x.failure}"),
+ )
+ self.page.on(
+ "console", lambda x: _logger.info(f"BROWSER CONSOLE: {x.text}")
)
- self.page.on("console", lambda x: print(f"BROWSER CONSOLE: {x.text}")) # noqa: T201
self.page.on(
"pageerror",
- lambda x: print(f"BROWSER ERROR: {x.name} - {x.message}\n{x.stack}"), # noqa: T201
+ lambda x: _logger.error(
+ f"BROWSER ERROR: {x.name} - {x.message}\n{x.stack}"
+ ),
)
async def __aexit__(
From dfb9612baaadc7656b0b75ee469c345099c41ff5 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 14 Dec 2025 23:15:21 -0800
Subject: [PATCH 3/9] automagically bind `react` and `react-dom` if missing
from page
---
src/js/packages/@reactpy/client/src/bind.tsx | 9 ++---
src/reactpy/executors/utils.py | 39 ++++++++++++++++----
src/reactpy/reactjs/module.py | 36 ++++++------------
tests/test_reactjs/test_modules_from_npm.py | 36 +++++++++++++++++-
4 files changed, 80 insertions(+), 40 deletions(-)
diff --git a/src/js/packages/@reactpy/client/src/bind.tsx b/src/js/packages/@reactpy/client/src/bind.tsx
index 8feb43c67..93d7fb2ae 100644
--- a/src/js/packages/@reactpy/client/src/bind.tsx
+++ b/src/js/packages/@reactpy/client/src/bind.tsx
@@ -6,14 +6,11 @@ export async function infer_bind_from_environment() {
const React = await import("react");
// @ts-ignore
const ReactDOM = await import("react-dom/client");
-
- console.log(
- "ReactPy detected 'ReactJS' to bind your JavaScript components.",
- );
return (node: HTMLElement) => reactjs_bind(node, React, ReactDOM);
} catch {
- console.debug(
- "ReactPy did not detect a component binding function or a suitable 'importmap'. Using ReactPy's internal framework (Preact) to bind your JavaScript components.",
+ console.error(
+ "Unknown error occurred: 'react' is missing within this ReactPy environment! \
+ Your JavaScript components may not work as expected!",
);
return (node: HTMLElement) => local_preact_bind(node);
}
diff --git a/src/reactpy/executors/utils.py b/src/reactpy/executors/utils.py
index b68d56627..291674a8a 100644
--- a/src/reactpy/executors/utils.py
+++ b/src/reactpy/executors/utils.py
@@ -64,16 +64,41 @@ def server_side_component_html(
) -> str:
return (
f'
'
+ ""
'"
)
+
+
+def default_import_map() -> str:
+ path_prefix = REACTPY_PATH_PREFIX.current.strip("/")
+ return f"""{{
+ "imports": {{
+ "react": "/{path_prefix}/static/preact.js",
+ "react-dom": "/{path_prefix}/static/preact-dom.js",
+ "react-dom/client": "/{path_prefix}/static/preact-dom.js",
+ "react/jsx-runtime": "/{path_prefix}/static/preact-jsx-runtime.js"
+ }}
+ }}""".replace("\n", "").replace(" ", "")
diff --git a/src/reactpy/reactjs/module.py b/src/reactpy/reactjs/module.py
index 89f0231f5..d9ce6758d 100644
--- a/src/reactpy/reactjs/module.py
+++ b/src/reactpy/reactjs/module.py
@@ -4,7 +4,6 @@
from pathlib import Path, PurePosixPath
from typing import Any, Literal
-from reactpy import config
from reactpy.config import REACTPY_DEBUG, REACTPY_WEB_MODULES_DIR
from reactpy.core.vdom import Vdom
from reactpy.reactjs.types import NAME_SOURCE, URL_SOURCE
@@ -15,7 +14,7 @@
resolve_names_from_file,
resolve_names_from_url,
)
-from reactpy.types import ImportSourceDict, JavaScriptModule, VdomConstructor
+from reactpy.types import ImportSourceDict, JavaScriptModule, VdomConstructor, VdomDict
logger = logging.getLogger(__name__)
@@ -183,7 +182,7 @@ def import_reactjs(
framework: Literal["preact", "react"] | None = None,
version: str | None = None,
use_local: bool = False,
-):
+) -> VdomDict:
"""
Return an import map script tag for ReactJS or Preact.
Parameters:
@@ -205,6 +204,7 @@ def import_reactjs(
A VDOM script tag containing the import map.
"""
from reactpy import html
+ from reactpy.executors.utils import default_import_map
if use_local and (framework or version): # nocov
raise ValueError("use_local cannot be used with framework or version")
@@ -215,19 +215,9 @@ def import_reactjs(
# Import map for ReactPy's local framework (re-exported/bundled/minified version of Preact)
if use_local:
- prefix = f"/{config.REACTPY_PATH_PREFIX.current.strip('/')}/static/{framework}"
return html.script(
- {"type": "importmap"},
- f"""
- {{
- "imports": {{
- "react": "{prefix}.js",
- "react-dom": "{prefix}-dom.js",
- "react-dom/client": "{prefix}-dom.js",
- "react/jsx-runtime": "{prefix}-jsx-runtime.js"
- }}
- }}
- """,
+ {"type": "importmap", "id": "reactpy-importmap"},
+ default_import_map(),
)
# Import map for ReactJS from esm.sh
@@ -235,17 +225,15 @@ def import_reactjs(
version = version or "19"
postfix = "?dev" if REACTPY_DEBUG.current else ""
return html.script(
- {"type": "importmap"},
- f"""
- {{
+ {"type": "importmap", "id": "reactpy-importmap"},
+ f"""{{
"imports": {{
"react": "https://esm.sh/react@{version}{postfix}",
"react-dom": "https://esm.sh/react-dom@{version}{postfix}",
"react-dom/client": "https://esm.sh/react-dom@{version}/client{postfix}",
"react/jsx-runtime": "https://esm.sh/react@{version}/jsx-runtime{postfix}"
}}
- }}
- """,
+ }}""".replace("\n", "").replace(" ", ""),
)
# Import map for Preact from esm.sh
@@ -253,17 +241,15 @@ def import_reactjs(
version = version or "10"
postfix = "?dev" if REACTPY_DEBUG.current else ""
return html.script(
- {"type": "importmap"},
- f"""
- {{
+ {"type": "importmap", "id": "reactpy-importmap"},
+ f"""{{
"imports": {{
"react": "https://esm.sh/preact@{version}/compat{postfix}",
"react-dom": "https://esm.sh/preact@{version}/compat{postfix}",
"react-dom/client": "https://esm.sh/preact@{version}/compat/client{postfix}",
"react/jsx-runtime": "https://esm.sh/preact@{version}/compat/jsx-runtime{postfix}"
}}
- }}
- """,
+ }}""".replace("\n", "").replace(" ", ""),
)
diff --git a/tests/test_reactjs/test_modules_from_npm.py b/tests/test_reactjs/test_modules_from_npm.py
index 6e2477518..fbe9be161 100644
--- a/tests/test_reactjs/test_modules_from_npm.py
+++ b/tests/test_reactjs/test_modules_from_npm.py
@@ -6,6 +6,8 @@
from reactpy.reactjs import component_from_npm, import_reactjs
from reactpy.testing import GITHUB_ACTIONS, BackendFixture, DisplayFixture
+MISSING_IMPORT_MAP_MSG = "ReactPy did not detect a suitable import map on your page."
+
@pytest.fixture(scope="module")
async def display(browser):
@@ -16,7 +18,7 @@ async def display(browser):
@pytest.mark.flaky(reruns=10 if GITHUB_ACTIONS else 1)
-async def test_component_from_npm_react_bootstrap(display: DisplayFixture):
+async def test_component_from_npm_react_bootstrap(display: DisplayFixture, caplog):
Button = component_from_npm("react-bootstrap", "Button", version="2")
@reactpy.component
@@ -33,9 +35,12 @@ def App():
await expect(button).to_contain_class("btn")
await expect(button).to_contain_class("btn-primary")
+ # Ensure missing import map was NOT logged
+ assert MISSING_IMPORT_MAP_MSG not in " ".join(x.msg for x in caplog.records)
+
@pytest.mark.flaky(reruns=10 if GITHUB_ACTIONS else 1)
-async def test_component_from_npm_react_bootstrap_with_local_framework(browser):
+async def test_component_from_npm_react_bootstrap_with_local_framework(browser, caplog):
Button = component_from_npm("react-bootstrap", "Button", version="2")
@reactpy.component
@@ -56,6 +61,33 @@ def App():
await expect(button).to_contain_class("btn")
await expect(button).to_contain_class("btn-primary")
+ # Ensure missing import map was NOT logged
+ assert MISSING_IMPORT_MAP_MSG not in " ".join(x.msg for x in caplog.records)
+
+
+@pytest.mark.flaky(reruns=10 if GITHUB_ACTIONS else 1)
+async def test_component_from_npm_without_explicit_reactjs_import(browser, caplog):
+ Button = component_from_npm("react-bootstrap", "Button", version="2")
+
+ @reactpy.component
+ def App():
+ return Button({"variant": "primary", "id": "test-button"}, "Click me")
+
+ async with BackendFixture() as backend:
+ async with DisplayFixture(backend=backend, browser=browser) as display:
+ await display.show(App)
+
+ button = display.page.locator("#test-button")
+ await expect(button).to_have_text("Click me")
+
+ # Check if it has the correct class for primary variant
+ # React Bootstrap buttons usually have 'btn' and 'btn-primary' classes
+ await expect(button).to_contain_class("btn")
+ await expect(button).to_contain_class("btn-primary")
+
+ # Check if missing import map was logged
+ assert MISSING_IMPORT_MAP_MSG in " ".join(x.msg for x in caplog.records)
+
@pytest.mark.flaky(reruns=10 if GITHUB_ACTIONS else 1)
async def test_component_from_npm_material_ui(display: DisplayFixture):
From 4f4d629718d19834e6dffdd5adec5243e14c7766 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 14 Dec 2025 23:16:22 -0800
Subject: [PATCH 4/9] Use ReactPyClient type hint instead of client interface
---
src/js/packages/@reactpy/client/src/components.tsx | 6 +++---
src/js/packages/@reactpy/client/src/vdom.tsx | 14 +++++++-------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/js/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx
index 2fb2debde..f6d06a381 100644
--- a/src/js/packages/@reactpy/client/src/components.tsx
+++ b/src/js/packages/@reactpy/client/src/components.tsx
@@ -6,13 +6,13 @@ import type {
ImportSourceBinding,
ReactPyComponent,
ReactPyVdom,
- ReactPyClientInterface,
} from "./types";
import { createAttributes, createChildren, loadImportSource } from "./vdom";
+import type { ReactPyClient } from "./client";
-const ClientContext = createContext(null as any);
+const ClientContext = createContext(null as any);
-export function Layout(props: { client: ReactPyClientInterface }): JSX.Element {
+export function Layout(props: { client: ReactPyClient }): JSX.Element {
const currentModel: ReactPyVdom = useState({ tagName: "" })[0];
const forceUpdate = useForceUpdate();
diff --git a/src/js/packages/@reactpy/client/src/vdom.tsx b/src/js/packages/@reactpy/client/src/vdom.tsx
index 736683ed6..e57ea4594 100644
--- a/src/js/packages/@reactpy/client/src/vdom.tsx
+++ b/src/js/packages/@reactpy/client/src/vdom.tsx
@@ -1,4 +1,3 @@
-import type { ReactPyClientInterface } from "./types";
import eventToObject from "event-to-object";
import type {
ReactPyVdom,
@@ -11,10 +10,11 @@ import type {
} from "./types";
import { infer_bind_from_environment } from "./bind";
import log from "./logger";
+import type { ReactPyClient } from "./client";
export async function loadImportSource(
vdomImportSource: ReactPyVdomImportSource,
- client: ReactPyClientInterface,
+ client: ReactPyClient,
): Promise {
let module: ReactPyModule;
if (vdomImportSource.sourceType === "URL") {
@@ -61,7 +61,7 @@ export async function loadImportSource(
}
function createImportSourceElement(props: {
- client: ReactPyClientInterface;
+ client: ReactPyClient;
module: ReactPyModule;
binding: ReactPyModuleBinding;
model: ReactPyVdom;
@@ -180,7 +180,7 @@ export function createChildren(
export function createAttributes(
model: ReactPyVdom,
- client: ReactPyClientInterface,
+ client: ReactPyClient,
): { [key: string]: any } {
return Object.fromEntries(
Object.entries({
@@ -203,7 +203,7 @@ export function createAttributes(
}
function createEventHandler(
- client: ReactPyClientInterface,
+ client: ReactPyClient,
name: string,
{ target, preventDefault, stopPropagation }: ReactPyVdomEventHandler,
): [string, () => void] {
@@ -262,7 +262,7 @@ function createInlineJavaScript(
class ReactPyChild extends HTMLElement {
mountPoint: HTMLDivElement;
binding: ImportSourceBinding | null = null;
- _client: ReactPyClientInterface | null = null;
+ _client: ReactPyClient | null = null;
_model: ReactPyVdom | null = null;
currentImportSource: ReactPyVdomImportSource | null = null;
@@ -276,7 +276,7 @@ class ReactPyChild extends HTMLElement {
this.appendChild(this.mountPoint);
}
- set client(value: ReactPyClientInterface) {
+ set client(value: ReactPyClient) {
this._client = value;
}
From 5c6cff563a6efe5f1897a43244cfaf6ec956bcbf Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 15 Dec 2025 02:09:51 -0800
Subject: [PATCH 5/9] specifically install `chromium` dependencies
---
src/build_scripts/install_playwright.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/build_scripts/install_playwright.py b/src/build_scripts/install_playwright.py
index eb78d2dec..d8d9cee70 100644
--- a/src/build_scripts/install_playwright.py
+++ b/src/build_scripts/install_playwright.py
@@ -14,4 +14,4 @@
# as *nix systems (such as WSL) return a failure code if there are *any* dependencies
# that could be cleaned up via `sudo apt autoremove`. This occurs even if we weren't
# the ones to install those dependencies in the first place.
-subprocess.run(["playwright", "install-deps"], check=False) # noqa: S607
+subprocess.run(["playwright", "install-deps", "chromium"], check=False) # noqa: S607
From 5aed0cc03e3f0167d0105ddb0a2f220274fafa8f Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 15 Dec 2025 02:13:24 -0800
Subject: [PATCH 6/9] fix broken tests
---
tests/test_asgi/test_middleware.py | 9 +++++----
tests/test_reactjs/test_modules_from_npm.py | 2 +-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/tests/test_asgi/test_middleware.py b/tests/test_asgi/test_middleware.py
index 7cacddf0c..2c0a5ec58 100644
--- a/tests/test_asgi/test_middleware.py
+++ b/tests/test_asgi/test_middleware.py
@@ -80,14 +80,15 @@ def Stub():
# Wait for the log record to be populated
for _ in range(10):
- if len(server.log_records) > 0:
+ if "Attempting to use an unregistered root component" in " ".join(
+ x.message for x in server.log_records
+ ):
break
await asyncio.sleep(0.25)
# Check that the log record was populated with the "unregistered component" message
- assert (
- "Attempting to use an unregistered root component"
- in server.log_records[-1].message
+ assert "Attempting to use an unregistered root component" in " ".join(
+ x.message for x in server.log_records
)
diff --git a/tests/test_reactjs/test_modules_from_npm.py b/tests/test_reactjs/test_modules_from_npm.py
index fbe9be161..2a7573955 100644
--- a/tests/test_reactjs/test_modules_from_npm.py
+++ b/tests/test_reactjs/test_modules_from_npm.py
@@ -6,7 +6,7 @@
from reactpy.reactjs import component_from_npm, import_reactjs
from reactpy.testing import GITHUB_ACTIONS, BackendFixture, DisplayFixture
-MISSING_IMPORT_MAP_MSG = "ReactPy did not detect a suitable import map on your page."
+MISSING_IMPORT_MAP_MSG = "ReactPy did not detect a suitable JavaScript import map"
@pytest.fixture(scope="module")
From 5677c6f27f893ea8e4ae498745748cc9e8972883 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 15 Dec 2025 02:21:57 -0800
Subject: [PATCH 7/9] bump version string
---
src/reactpy/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/reactpy/__init__.py b/src/reactpy/__init__.py
index 27b462d8c..c53c419c6 100644
--- a/src/reactpy/__init__.py
+++ b/src/reactpy/__init__.py
@@ -23,7 +23,7 @@
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy
__author__ = "The Reactive Python Team"
-__version__ = "2.0.0b6"
+__version__ = "2.0.0b7"
__all__ = [
"Ref",
From d53576b17a8ba11e24808277875775cec14ab970 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 15 Dec 2025 02:41:21 -0800
Subject: [PATCH 8/9] Move pyscript code into `reactpy.executors.pyscript`
---
src/reactpy/__init__.py | 2 +-
src/reactpy/core/_thread_local.py | 2 +-
src/reactpy/core/hooks.py | 30 +++++++------------
src/reactpy/executors/asgi/pyscript.py | 5 +++-
src/reactpy/executors/asgi/standalone.py | 2 +-
.../{ => executors}/pyscript/__init__.py | 0
.../pyscript/component_template.py | 2 +-
.../{ => executors}/pyscript/components.py | 2 +-
.../pyscript/layout_handler.py | 0
src/reactpy/{ => executors}/pyscript/utils.py | 0
src/reactpy/templatetags/jinja.py | 5 +++-
tests/test_pyscript/test_utils.py | 10 +++----
12 files changed, 29 insertions(+), 31 deletions(-)
rename src/reactpy/{ => executors}/pyscript/__init__.py (100%)
rename src/reactpy/{ => executors}/pyscript/component_template.py (90%)
rename src/reactpy/{ => executors}/pyscript/components.py (96%)
rename src/reactpy/{ => executors}/pyscript/layout_handler.py (100%)
rename src/reactpy/{ => executors}/pyscript/utils.py (100%)
diff --git a/src/reactpy/__init__.py b/src/reactpy/__init__.py
index c53c419c6..5d1c64d56 100644
--- a/src/reactpy/__init__.py
+++ b/src/reactpy/__init__.py
@@ -19,7 +19,7 @@
use_state,
)
from reactpy.core.vdom import Vdom
-from reactpy.pyscript.components import pyscript_component
+from reactpy.executors.pyscript.components import pyscript_component
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy
__author__ = "The Reactive Python Team"
diff --git a/src/reactpy/core/_thread_local.py b/src/reactpy/core/_thread_local.py
index 9d4bae99c..4d859b7a8 100644
--- a/src/reactpy/core/_thread_local.py
+++ b/src/reactpy/core/_thread_local.py
@@ -8,7 +8,7 @@
class ThreadLocal(Generic[_StateType]): # nocov
"""Utility for managing per-thread state information. This is only used in
- environments where ContextVars are not available, such as the `pyodide`
+ environments where ContextVars are not available, such as the `pyscript`
executor."""
def __init__(self, default: Callable[[], _StateType]):
diff --git a/src/reactpy/core/hooks.py b/src/reactpy/core/hooks.py
index 27fbc1ce4..3692a98b3 100644
--- a/src/reactpy/core/hooks.py
+++ b/src/reactpy/core/hooks.py
@@ -84,11 +84,7 @@ def __init__(
self,
initial_value: _Type | Callable[[], _Type],
) -> None:
- if callable(initial_value):
- self.value = initial_value()
- else:
- self.value = initial_value
-
+ self.value = initial_value() if callable(initial_value) else initial_value
hook = HOOK_STACK.current_hook()
def dispatch(new: _Type | Callable[[_Type], _Type]) -> None:
@@ -434,10 +430,7 @@ def use_callback(
def setup(function: _CallbackFunc) -> _CallbackFunc:
return memoize(lambda: function)
- if function is not None:
- return setup(function)
- else:
- return setup
+ return setup(function) if function is not None else setup
class _LambdaCaller(Protocol):
@@ -553,17 +546,16 @@ def _try_to_infer_closure_values(
func: Callable[..., Any] | None,
values: Sequence[Any] | ellipsis | None,
) -> Sequence[Any] | None:
- if values is ...:
- if isinstance(func, FunctionType):
- return (
- [cell.cell_contents for cell in func.__closure__]
- if func.__closure__
- else []
- )
- else:
- return None
- else:
+ if values is not ...:
return values
+ if isinstance(func, FunctionType):
+ return (
+ [cell.cell_contents for cell in func.__closure__]
+ if func.__closure__
+ else []
+ )
+ else:
+ return None
def strictly_equal(x: Any, y: Any) -> bool:
diff --git a/src/reactpy/executors/asgi/pyscript.py b/src/reactpy/executors/asgi/pyscript.py
index edae4386d..20a094cb7 100644
--- a/src/reactpy/executors/asgi/pyscript.py
+++ b/src/reactpy/executors/asgi/pyscript.py
@@ -13,8 +13,11 @@
from reactpy.executors.asgi.middleware import ReactPyMiddleware
from reactpy.executors.asgi.standalone import ReactPy, ReactPyApp
from reactpy.executors.asgi.types import AsgiWebsocketScope
+from reactpy.executors.pyscript.utils import (
+ pyscript_component_html,
+ pyscript_setup_html,
+)
from reactpy.executors.utils import vdom_head_to_html
-from reactpy.pyscript.utils import pyscript_component_html, pyscript_setup_html
from reactpy.types import ReactPyConfig, VdomDict
diff --git a/src/reactpy/executors/asgi/standalone.py b/src/reactpy/executors/asgi/standalone.py
index 35875f606..b16246995 100644
--- a/src/reactpy/executors/asgi/standalone.py
+++ b/src/reactpy/executors/asgi/standalone.py
@@ -23,8 +23,8 @@
AsgiV3WebsocketApp,
AsgiWebsocketScope,
)
+from reactpy.executors.pyscript.utils import pyscript_setup_html
from reactpy.executors.utils import server_side_component_html, vdom_head_to_html
-from reactpy.pyscript.utils import pyscript_setup_html
from reactpy.types import (
PyScriptOptions,
ReactPyConfig,
diff --git a/src/reactpy/pyscript/__init__.py b/src/reactpy/executors/pyscript/__init__.py
similarity index 100%
rename from src/reactpy/pyscript/__init__.py
rename to src/reactpy/executors/pyscript/__init__.py
diff --git a/src/reactpy/pyscript/component_template.py b/src/reactpy/executors/pyscript/component_template.py
similarity index 90%
rename from src/reactpy/pyscript/component_template.py
rename to src/reactpy/executors/pyscript/component_template.py
index 009ca56f7..71710a42e 100644
--- a/src/reactpy/pyscript/component_template.py
+++ b/src/reactpy/executors/pyscript/component_template.py
@@ -2,7 +2,7 @@
# type: ignore
import asyncio
-from reactpy.pyscript.layout_handler import ReactPyLayoutHandler
+from reactpy.executors.pyscript.layout_handler import ReactPyLayoutHandler
# User component is inserted below by regex replacement
diff --git a/src/reactpy/pyscript/components.py b/src/reactpy/executors/pyscript/components.py
similarity index 96%
rename from src/reactpy/pyscript/components.py
rename to src/reactpy/executors/pyscript/components.py
index 8fb769eca..54940ad48 100644
--- a/src/reactpy/pyscript/components.py
+++ b/src/reactpy/executors/pyscript/components.py
@@ -4,7 +4,7 @@
from typing import TYPE_CHECKING
from reactpy import component, hooks
-from reactpy.pyscript.utils import pyscript_component_html
+from reactpy.executors.pyscript.utils import pyscript_component_html
from reactpy.types import Component, Key
from reactpy.utils import string_to_reactpy
diff --git a/src/reactpy/pyscript/layout_handler.py b/src/reactpy/executors/pyscript/layout_handler.py
similarity index 100%
rename from src/reactpy/pyscript/layout_handler.py
rename to src/reactpy/executors/pyscript/layout_handler.py
diff --git a/src/reactpy/pyscript/utils.py b/src/reactpy/executors/pyscript/utils.py
similarity index 100%
rename from src/reactpy/pyscript/utils.py
rename to src/reactpy/executors/pyscript/utils.py
diff --git a/src/reactpy/templatetags/jinja.py b/src/reactpy/templatetags/jinja.py
index e2cf83489..e7f980dcb 100644
--- a/src/reactpy/templatetags/jinja.py
+++ b/src/reactpy/templatetags/jinja.py
@@ -3,8 +3,11 @@
from jinja2_simple_tags import StandaloneTag
+from reactpy.executors.pyscript.utils import (
+ pyscript_component_html,
+ pyscript_setup_html,
+)
from reactpy.executors.utils import server_side_component_html
-from reactpy.pyscript.utils import pyscript_component_html, pyscript_setup_html
class ReactPyJinja(StandaloneTag): # type: ignore
diff --git a/tests/test_pyscript/test_utils.py b/tests/test_pyscript/test_utils.py
index 4ae89d841..14a54ca96 100644
--- a/tests/test_pyscript/test_utils.py
+++ b/tests/test_pyscript/test_utils.py
@@ -6,7 +6,7 @@
import orjson
import pytest
-from reactpy.pyscript import utils
+from reactpy.executors.pyscript import utils
def test_bad_root_name():
@@ -69,8 +69,8 @@ def test_get_reactpy_versions_https_fail_http_success():
# Mock json.load to return data when called with mock_response
with (
- mock.patch("reactpy.pyscript.utils.request.urlopen") as mock_urlopen,
- mock.patch("reactpy.pyscript.utils.json.load") as mock_json_load,
+ mock.patch("reactpy.executors.pyscript.utils.request.urlopen") as mock_urlopen,
+ mock.patch("reactpy.executors.pyscript.utils.json.load") as mock_json_load,
):
def side_effect(url, timeout):
@@ -97,8 +97,8 @@ def test_get_reactpy_versions_all_fail():
utils.get_reactpy_versions.cache_clear()
with (
- mock.patch("reactpy.pyscript.utils.request.urlopen") as mock_urlopen,
- mock.patch("reactpy.pyscript.utils._logger") as mock_logger,
+ mock.patch("reactpy.executors.pyscript.utils.request.urlopen") as mock_urlopen,
+ mock.patch("reactpy.executors.pyscript.utils._logger") as mock_logger,
):
mock_urlopen.side_effect = URLError("Fail")
From ebb009a6060737996096b10d8b8eee72a97cb2c0 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:00:32 -0800
Subject: [PATCH 9/9] fix coverage
---
pyproject.toml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 38880aff1..c756663c4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -232,8 +232,8 @@ omit = [
"src/reactpy/__init__.py",
"src/reactpy/_console/*",
"src/reactpy/__main__.py",
- "src/reactpy/pyscript/layout_handler.py",
- "src/reactpy/pyscript/component_template.py",
+ "src/reactpy/executors/pyscript/layout_handler.py",
+ "src/reactpy/executors/pyscript/component_template.py",
]
[tool.coverage.report]