Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/source/about/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Unreleased
- :pull:`1196` - Rewrite the ``event-to-object`` package to be more robust at handling properties on events.

**Deprecated**

-:pull:`1307` - ``reactpy.web.export`` is deprecated. Use ``reactpy.web.reactjs_component_from_*`` instead.
-:pull:`1307` - ``reactpy.web.module_from_file`` is deprecated. Use ``reactpy.web.reactjs_component_from_file`` instead.
-:pull:`1307` - ``reactpy.web.module_from_url`` is deprecated. Use ``reactpy.web.reactjs_component_from_url`` instead.
Expand All @@ -81,6 +82,10 @@ Unreleased
- :pull:`1281` - Removed ``reactpy.vdom``. Use ``reactpy.Vdom`` instead.
- :pull:`1281` - Removed ``reactpy.core.make_vdom_constructor``. Use ``reactpy.Vdom`` instead.
- :pull:`1281` - Removed ``reactpy.core.custom_vdom_constructor``. Use ``reactpy.Vdom`` instead.
- :pull:`1311` - Removed ``reactpy.core.serve.Stop`` type due to extended deprecation.
- :pull:`1311` - Removed ``reactpy.Layout`` top-level export. Use ``reactpy.core.layout.Layout`` instead.
- :pull:`1311` - Removed ``reactpy.widgets.hotswap`` due to extended deprecation.


**Fixed**

Expand Down Expand Up @@ -288,7 +293,7 @@ v0.43.0

**Deprecated**

- :pull:`870` - ``ComponentType.should_render()``. This method was implemented based on
- :pull:`870` - ``ComponentType.()``. This method was implemented based on
reading the React/Preact source code. As it turns out though it seems like it's mostly
a vestige from the fact that both these libraries still support class-based
components. The ability for components to not render also caused several bugs.
Expand Down
5 changes: 1 addition & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,7 @@ features = ["all"]
python = ["3.10", "3.11", "3.12", "3.13"]

[tool.pytest.ini_options]
addopts = """\
--strict-config
--strict-markers
"""
addopts = ["--strict-config", "--strict-markers"]
filterwarnings = """
ignore::DeprecationWarning:uvicorn.*
ignore::DeprecationWarning:websockets.*
Expand Down
1 change: 0 additions & 1 deletion src/build_scripts/copy_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# dependencies = []
# ///

# ruff: noqa: INP001
import logging
import shutil
import sys
Expand Down
2 changes: 1 addition & 1 deletion src/js/packages/@reactpy/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
"checkTypes": "tsc --noEmit"
},
"type": "module",
"version": "1.0.0"
"version": "1.0.1"
}
2 changes: 1 addition & 1 deletion src/js/packages/@reactpy/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class ReactPyClient
url: this.urls.componentUrl,
readyPromise: this.ready,
...props.reconnectOptions,
onMessage: (event) => this.handleIncoming(JSON.parse(event.data)),
onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
});
}

Expand Down
1 change: 1 addition & 0 deletions src/js/packages/@reactpy/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./vdom";
export * from "./websocket";
export { default as React } from "preact/compat";
export { default as ReactDOM } from "preact/compat";
export * as preact from "preact";
10 changes: 8 additions & 2 deletions src/js/packages/@reactpy/client/src/vdom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactPyClientInterface } from "./types";
import serializeEvent from "event-to-object";
import eventToObject from "event-to-object";
import type {
ReactPyVdom,
ReactPyVdomImportSource,
Expand Down Expand Up @@ -212,7 +212,13 @@ function createEventHandler(
if (stopPropagation) {
event.stopPropagation();
}
return serializeEvent(event);

// Convert JavaScript objects to plain JSON, if needed
if (typeof event === "object") {
return eventToObject(event);
} else {
return event;
}
});
client.sendMessage({ type: "layout-event", data, target });
};
Expand Down
2 changes: 1 addition & 1 deletion src/js/packages/event-to-object/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
"checkTypes": "tsc --noEmit"
},
"type": "module",
"version": "1.0.0"
"version": "1.0.1"
}
12 changes: 10 additions & 2 deletions src/js/packages/event-to-object/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ export default function convert(
classObject: { [key: string]: any },
maxDepth: number = 10,
): object {
const visited = new WeakSet<any>();
visited.add(classObject);
// Immediately return `classObject` if given an unexpected (non-object) input
if (!classObject || typeof classObject !== "object") {
console.warn(
"eventToObject: Expected an object input, received:",
classObject,
);
return classObject;
}

// Begin conversion
const visited = new WeakSet<any>();
visited.add(classObject);
const convertedObj: { [key: string]: any } = {};
for (const key in classObject) {
// Skip keys that cannot be converted
Expand Down
7 changes: 7 additions & 0 deletions src/js/packages/event-to-object/tests/event-to-object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,10 @@ test("handles recursive HTML node structures", () => {
expect(converted.children[0].parentNode).toBeUndefined();
}
});

test("pass-through on unexpected non-object inputs", () => {
expect(convert(null as any)).toEqual(null);
expect(convert(undefined as any)).toEqual(undefined);
expect(convert(42 as any)).toEqual(42);
expect(convert("test" as any)).toEqual("test");
});
7 changes: 2 additions & 5 deletions src/reactpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from reactpy._html import html
from reactpy.core import hooks
from reactpy.core.component import component
from reactpy.core.events import Event, event
from reactpy.core.events import event
from reactpy.core.hooks import (
create_context,
use_async_effect,
Expand All @@ -18,17 +18,14 @@
use_scope,
use_state,
)
from reactpy.core.layout import Layout
from reactpy.core.vdom import Vdom
from reactpy.pyscript.components import pyscript_component
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy

__author__ = "The Reactive Python Team"
__version__ = "2.0.0b3"
__version__ = "2.0.0b4"

__all__ = [
"Event",
"Layout",
"Ref",
"Vdom",
"component",
Expand Down
12 changes: 0 additions & 12 deletions src/reactpy/core/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,6 @@ def setup(function: Callable[..., Any]) -> EventHandler:
return setup(function) if function is not None else setup


class Event(dict):
def __getattr__(self, name: str) -> Any:
value = self.get(name)
return Event(value) if isinstance(value, dict) else value

def preventDefault(self) -> None:
"""Prevent the default action of the event."""

def stopPropagation(self) -> None:
"""Stop the event from propagating."""


class EventHandler:
"""Turn a function or coroutine into an event handler

Expand Down
6 changes: 6 additions & 0 deletions src/reactpy/core/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ def use_effect(
Returns:
If not function is provided, a decorator. Otherwise ``None``.
"""
if asyncio.iscoroutinefunction(function):
raise TypeError(
"`use_effect` does not support async functions. "
"Use `use_async_effect` instead."
)

hook = HOOK_STACK.current_hook()
dependencies = _try_to_infer_closure_values(function, dependencies)
memoize = use_memo(dependencies=dependencies)
Expand Down
2 changes: 1 addition & 1 deletion src/reactpy/core/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
REACTPY_DEBUG,
)
from reactpy.core._life_cycle_hook import LifeCycleHook
from reactpy.core.events import Event
from reactpy.core.vdom import validate_vdom_json
from reactpy.types import (
ComponentType,
Context,
Event,
EventHandlerDict,
Key,
LayoutEventMessage,
Expand Down
25 changes: 3 additions & 22 deletions src/reactpy/core/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from collections.abc import Awaitable
from logging import getLogger
from typing import Any, Callable
from warnings import warn

from anyio import create_task_group
from anyio.abc import TaskGroup
Expand All @@ -25,16 +24,6 @@
"""


class Stop(BaseException):
"""Deprecated

Stop serving changes and events

Raising this error will tell dispatchers to gracefully exit. Typically this is
called by code running inside a layout to tell it to stop rendering.
"""


async def serve_layout(
layout: LayoutType[
LayoutUpdateMessage | dict[str, Any], LayoutEventMessage | dict[str, Any]
Expand All @@ -44,17 +33,9 @@ async def serve_layout(
) -> None:
"""Run a dispatch loop for a single view instance"""
async with layout:
try:
async with create_task_group() as task_group:
task_group.start_soon(_single_outgoing_loop, layout, send)
task_group.start_soon(_single_incoming_loop, task_group, layout, recv)
except Stop: # nocov
warn(
"The Stop exception is deprecated and will be removed in a future version",
UserWarning,
stacklevel=1,
)
logger.info(f"Stopped serving {layout}")
async with create_task_group() as task_group:
task_group.start_soon(_single_outgoing_loop, layout, send)
task_group.start_soon(_single_incoming_loop, task_group, layout, recv)


async def _single_outgoing_loop(
Expand Down
8 changes: 1 addition & 7 deletions src/reactpy/pyscript/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
from typing import TYPE_CHECKING, Any
from uuid import uuid4

import jsonpointer

import reactpy
from reactpy.config import REACTPY_DEBUG, REACTPY_PATH_PREFIX, REACTPY_WEB_MODULES_DIR
from reactpy.types import VdomDict
Expand Down Expand Up @@ -116,11 +114,7 @@ def extend_pyscript_config(

# Extends ReactPy's default PyScript config with user provided values.
pyscript_config: dict[str, Any] = {
"packages": [
reactpy_version_string(),
f"jsonpointer=={jsonpointer.__version__}",
"ssl",
],
"packages": [reactpy_version_string(), "jsonpointer==3.*", "ssl"],
"js_modules": {
"main": {
f"{REACTPY_PATH_PREFIX.current}static/morphdom/morphdom-esm.js": "morphdom"
Expand Down
16 changes: 16 additions & 0 deletions src/reactpy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,3 +1071,19 @@ def __call__(
class EllipsisRepr:
def __repr__(self) -> str:
return "..."


class Event(dict):
"""
A light `dict` wrapper for event data passed to event handler functions.
"""

def __getattr__(self, name: str) -> Any:
value = self.get(name)
return Event(value) if isinstance(value, dict) else value

def preventDefault(self) -> None:
"""Prevent the default action of the event."""

def stopPropagation(self) -> None:
"""Stop the event from propagating."""
78 changes: 78 additions & 0 deletions src/reactpy/web/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@
_STRING_WEB_MODULE_CACHE: dict[str, WebModule] = {}


@overload
def reactjs_component_from_url(
url: str,
import_names: str,
fallback: Any | None = ...,
resolve_imports: bool | None = ...,
resolve_imports_depth: int = ...,
unmount_before_update: bool = ...,
allow_children: bool = ...,
) -> VdomConstructor: ...


@overload
def reactjs_component_from_url(
url: str,
import_names: list[str] | tuple[str, ...],
fallback: Any | None = ...,
resolve_imports: bool | None = ...,
resolve_imports_depth: int = ...,
unmount_before_update: bool = ...,
allow_children: bool = ...,
) -> list[VdomConstructor]: ...


def reactjs_component_from_url(
url: str,
import_names: str | list[str] | tuple[str, ...],
Expand Down Expand Up @@ -80,6 +104,34 @@ def reactjs_component_from_url(
return _vdom_from_web_module(module, import_names, fallback, allow_children)


@overload
def reactjs_component_from_file(
name: str,
file: str | Path,
import_names: str,
fallback: Any | None = ...,
resolve_imports: bool | None = ...,
resolve_imports_depth: int = ...,
unmount_before_update: bool = ...,
symlink: bool = ...,
allow_children: bool = ...,
) -> VdomConstructor: ...


@overload
def reactjs_component_from_file(
name: str,
file: str | Path,
import_names: list[str] | tuple[str, ...],
fallback: Any | None = ...,
resolve_imports: bool | None = ...,
resolve_imports_depth: int = ...,
unmount_before_update: bool = ...,
symlink: bool = ...,
allow_children: bool = ...,
) -> list[VdomConstructor]: ...


def reactjs_component_from_file(
name: str,
file: str | Path,
Expand Down Expand Up @@ -135,6 +187,32 @@ def reactjs_component_from_file(
return _vdom_from_web_module(module, import_names, fallback, allow_children)


@overload
def reactjs_component_from_string(
name: str,
content: str,
import_names: str,
fallback: Any | None = ...,
resolve_imports: bool | None = ...,
resolve_imports_depth: int = ...,
unmount_before_update: bool = ...,
allow_children: bool = ...,
) -> VdomConstructor: ...


@overload
def reactjs_component_from_string(
name: str,
content: str,
import_names: list[str] | tuple[str, ...],
fallback: Any | None = ...,
resolve_imports: bool | None = ...,
resolve_imports_depth: int = ...,
unmount_before_update: bool = ...,
allow_children: bool = ...,
) -> list[VdomConstructor]: ...


def reactjs_component_from_string(
name: str,
content: str,
Expand Down
Loading
Loading