reactpy 2.0.0b4__py3-none-any.whl → 2.0.0b5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reactpy/__init__.py +1 -1
- reactpy/_console/rewrite_props.py +2 -2
- reactpy/_option.py +2 -1
- reactpy/config.py +2 -2
- reactpy/core/_life_cycle_hook.py +8 -9
- reactpy/core/_thread_local.py +2 -1
- reactpy/core/component.py +4 -38
- reactpy/core/events.py +61 -36
- reactpy/core/hooks.py +25 -35
- reactpy/core/layout.py +174 -187
- reactpy/core/serve.py +6 -6
- reactpy/core/vdom.py +6 -7
- reactpy/executors/asgi/__init__.py +9 -4
- reactpy/executors/asgi/middleware.py +1 -2
- reactpy/executors/asgi/pyscript.py +3 -7
- reactpy/executors/asgi/standalone.py +4 -6
- reactpy/executors/asgi/types.py +2 -2
- reactpy/pyscript/components.py +3 -3
- reactpy/pyscript/utils.py +49 -46
- reactpy/static/index.js +2 -2
- reactpy/static/index.js.map +4 -4
- reactpy/testing/backend.py +2 -1
- reactpy/testing/common.py +3 -5
- reactpy/types.py +100 -47
- reactpy/utils.py +7 -7
- reactpy/web/module.py +13 -10
- reactpy/widgets.py +2 -2
- {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b5.dist-info}/METADATA +4 -7
- {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b5.dist-info}/RECORD +32 -32
- {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b5.dist-info}/WHEEL +0 -0
- {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b5.dist-info}/entry_points.txt +0 -0
- {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b5.dist-info}/licenses/LICENSE +0 -0
reactpy/__init__.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from copy import copy
|
|
5
6
|
from keyword import kwlist
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Callable
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
@@ -102,7 +102,7 @@ def _rewrite_props(
|
|
|
102
102
|
keys: list[ast.expr | None] = []
|
|
103
103
|
values: list[ast.expr] = []
|
|
104
104
|
# Iterate over the keys and values in the dictionary
|
|
105
|
-
for k, v in zip(props_node.keys, props_node.values):
|
|
105
|
+
for k, v in zip(props_node.keys, props_node.values, strict=False):
|
|
106
106
|
if isinstance(k, ast.Constant) and isinstance(k.value, str):
|
|
107
107
|
# Construct the new key and value
|
|
108
108
|
k_value, new_v = constructor(k.value, v)
|
reactpy/_option.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from logging import getLogger
|
|
5
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Generic, TypeVar, cast
|
|
6
7
|
|
|
7
8
|
from reactpy._warnings import warn
|
|
8
9
|
|
reactpy/config.py
CHANGED
|
@@ -85,11 +85,11 @@ REACTPY_TESTS_DEFAULT_TIMEOUT = Option(
|
|
|
85
85
|
|
|
86
86
|
REACTPY_ASYNC_RENDERING = Option(
|
|
87
87
|
"REACTPY_ASYNC_RENDERING",
|
|
88
|
-
default=
|
|
88
|
+
default=True,
|
|
89
89
|
mutable=True,
|
|
90
90
|
validator=boolean,
|
|
91
91
|
)
|
|
92
|
-
"""Whether to render components asynchronously.
|
|
92
|
+
"""Whether to render components asynchronously."""
|
|
93
93
|
|
|
94
94
|
REACTPY_RECONNECT_INTERVAL = Option(
|
|
95
95
|
"REACTPY_RECONNECT_INTERVAL",
|
reactpy/core/_life_cycle_hook.py
CHANGED
|
@@ -3,13 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
5
|
from asyncio import Event, Task, create_task, gather
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from contextvars import ContextVar, Token
|
|
7
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Protocol, TypeVar
|
|
8
9
|
|
|
9
10
|
from anyio import Semaphore
|
|
10
11
|
|
|
11
12
|
from reactpy.core._thread_local import ThreadLocal
|
|
12
|
-
from reactpy.types import
|
|
13
|
+
from reactpy.types import Component, Context, ContextProvider
|
|
13
14
|
from reactpy.utils import Singleton
|
|
14
15
|
|
|
15
16
|
T = TypeVar("T")
|
|
@@ -145,13 +146,13 @@ class LifeCycleHook:
|
|
|
145
146
|
"component",
|
|
146
147
|
)
|
|
147
148
|
|
|
148
|
-
component:
|
|
149
|
+
component: Component
|
|
149
150
|
|
|
150
151
|
def __init__(
|
|
151
152
|
self,
|
|
152
153
|
schedule_render: Callable[[], None],
|
|
153
154
|
) -> None:
|
|
154
|
-
self._context_providers: dict[Context[Any],
|
|
155
|
+
self._context_providers: dict[Context[Any], ContextProvider[Any]] = {}
|
|
155
156
|
self._schedule_render_callback = schedule_render
|
|
156
157
|
self._scheduled_render = False
|
|
157
158
|
self._rendered_atleast_once = False
|
|
@@ -200,7 +201,7 @@ class LifeCycleHook:
|
|
|
200
201
|
"""
|
|
201
202
|
self._effect_funcs.append(effect_func)
|
|
202
203
|
|
|
203
|
-
def set_context_provider(self, provider:
|
|
204
|
+
def set_context_provider(self, provider: ContextProvider[Any]) -> None:
|
|
204
205
|
"""Set a context provider for this hook
|
|
205
206
|
|
|
206
207
|
The context provider will be used to provide state to any child components
|
|
@@ -208,9 +209,7 @@ class LifeCycleHook:
|
|
|
208
209
|
"""
|
|
209
210
|
self._context_providers[provider.type] = provider
|
|
210
211
|
|
|
211
|
-
def get_context_provider(
|
|
212
|
-
self, context: Context[T]
|
|
213
|
-
) -> ContextProviderType[T] | None:
|
|
212
|
+
def get_context_provider(self, context: Context[T]) -> ContextProvider[T] | None:
|
|
214
213
|
"""Get a context provider for this hook of the given type
|
|
215
214
|
|
|
216
215
|
The context provider will have been set by a parent component. If no provider
|
|
@@ -218,7 +217,7 @@ class LifeCycleHook:
|
|
|
218
217
|
"""
|
|
219
218
|
return self._context_providers.get(context)
|
|
220
219
|
|
|
221
|
-
async def affect_component_will_render(self, component:
|
|
220
|
+
async def affect_component_will_render(self, component: Component) -> None:
|
|
222
221
|
"""The component is about to render"""
|
|
223
222
|
await self._render_access.acquire()
|
|
224
223
|
self._scheduled_render = False
|
reactpy/core/_thread_local.py
CHANGED
reactpy/core/component.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from functools import wraps
|
|
5
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
|
-
from reactpy.types import
|
|
8
|
+
from reactpy.types import Component, VdomDict
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def component(
|
|
11
|
-
function: Callable[...,
|
|
12
|
+
function: Callable[..., Component | VdomDict | str | None],
|
|
12
13
|
) -> Callable[..., Component]:
|
|
13
14
|
"""A decorator for defining a new component.
|
|
14
15
|
|
|
@@ -29,38 +30,3 @@ def component(
|
|
|
29
30
|
return Component(function, key, args, kwargs, sig)
|
|
30
31
|
|
|
31
32
|
return constructor
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class Component:
|
|
35
|
-
"""An object for rending component models."""
|
|
36
|
-
|
|
37
|
-
__slots__ = "__weakref__", "_args", "_func", "_kwargs", "_sig", "key", "type"
|
|
38
|
-
|
|
39
|
-
def __init__(
|
|
40
|
-
self,
|
|
41
|
-
function: Callable[..., ComponentType | VdomDict | str | None],
|
|
42
|
-
key: Any | None,
|
|
43
|
-
args: tuple[Any, ...],
|
|
44
|
-
kwargs: dict[str, Any],
|
|
45
|
-
sig: inspect.Signature,
|
|
46
|
-
) -> None:
|
|
47
|
-
self.key = key
|
|
48
|
-
self.type = function
|
|
49
|
-
self._args = args
|
|
50
|
-
self._kwargs = kwargs
|
|
51
|
-
self._sig = sig
|
|
52
|
-
|
|
53
|
-
def render(self) -> ComponentType | VdomDict | str | None:
|
|
54
|
-
return self.type(*self._args, **self._kwargs)
|
|
55
|
-
|
|
56
|
-
def __repr__(self) -> str:
|
|
57
|
-
try:
|
|
58
|
-
args = self._sig.bind(*self._args, **self._kwargs).arguments
|
|
59
|
-
except TypeError:
|
|
60
|
-
return f"{self.type.__name__}(...)"
|
|
61
|
-
else:
|
|
62
|
-
items = ", ".join(f"{k}={v!r}" for k, v in args.items())
|
|
63
|
-
if items:
|
|
64
|
-
return f"{self.type.__name__}({id(self):02x}, {items})"
|
|
65
|
-
else:
|
|
66
|
-
return f"{self.type.__name__}({id(self):02x})"
|
reactpy/core/events.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import dis
|
|
5
|
-
|
|
6
|
-
from
|
|
4
|
+
import inspect
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from types import CodeType
|
|
8
|
+
from typing import Any, Literal, cast, overload
|
|
7
9
|
|
|
8
10
|
from anyio import create_task_group
|
|
9
11
|
|
|
10
|
-
from reactpy.types import
|
|
12
|
+
from reactpy.types import BaseEventHandler, EventHandlerFunc
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@overload
|
|
@@ -73,7 +75,7 @@ def event(
|
|
|
73
75
|
return setup(function) if function is not None else setup
|
|
74
76
|
|
|
75
77
|
|
|
76
|
-
class EventHandler:
|
|
78
|
+
class EventHandler(BaseEventHandler):
|
|
77
79
|
"""Turn a function or coroutine into an event handler
|
|
78
80
|
|
|
79
81
|
Parameters:
|
|
@@ -87,14 +89,6 @@ class EventHandler:
|
|
|
87
89
|
A unique identifier for this event handler (auto-generated by default)
|
|
88
90
|
"""
|
|
89
91
|
|
|
90
|
-
__slots__ = (
|
|
91
|
-
"__weakref__",
|
|
92
|
-
"function",
|
|
93
|
-
"prevent_default",
|
|
94
|
-
"stop_propagation",
|
|
95
|
-
"target",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
92
|
def __init__(
|
|
99
93
|
self,
|
|
100
94
|
function: EventHandlerFunc,
|
|
@@ -113,26 +107,14 @@ class EventHandler:
|
|
|
113
107
|
while hasattr(func_to_inspect, "__wrapped__"):
|
|
114
108
|
func_to_inspect = func_to_inspect.__wrapped__
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
last_was_event = False
|
|
120
|
-
|
|
121
|
-
for instr in dis.get_instructions(func_to_inspect):
|
|
122
|
-
if instr.opname == "LOAD_FAST" and instr.argval == event_arg_name:
|
|
123
|
-
last_was_event = True
|
|
124
|
-
continue
|
|
125
|
-
|
|
126
|
-
if last_was_event and instr.opname in (
|
|
127
|
-
"LOAD_METHOD",
|
|
128
|
-
"LOAD_ATTR",
|
|
129
|
-
):
|
|
130
|
-
if instr.argval == "preventDefault":
|
|
131
|
-
self.prevent_default = True
|
|
132
|
-
elif instr.argval == "stopPropagation":
|
|
133
|
-
self.stop_propagation = True
|
|
110
|
+
found_prevent_default, found_stop_propagation = _inspect_event_handler_code(
|
|
111
|
+
func_to_inspect.__code__
|
|
112
|
+
)
|
|
134
113
|
|
|
135
|
-
|
|
114
|
+
if found_prevent_default:
|
|
115
|
+
self.prevent_default = True
|
|
116
|
+
if found_stop_propagation:
|
|
117
|
+
self.stop_propagation = True
|
|
136
118
|
|
|
137
119
|
__hash__ = None # type: ignore
|
|
138
120
|
|
|
@@ -168,7 +150,7 @@ def to_event_handler_function(
|
|
|
168
150
|
Whether to pass the event parameters a positional args or as a list.
|
|
169
151
|
"""
|
|
170
152
|
if positional_args:
|
|
171
|
-
if
|
|
153
|
+
if inspect.iscoroutinefunction(function):
|
|
172
154
|
|
|
173
155
|
async def wrapper(data: Sequence[Any]) -> None:
|
|
174
156
|
await function(*data)
|
|
@@ -182,7 +164,7 @@ def to_event_handler_function(
|
|
|
182
164
|
|
|
183
165
|
cast(Any, wrapper).__wrapped__ = function
|
|
184
166
|
return wrapper
|
|
185
|
-
elif not
|
|
167
|
+
elif not inspect.iscoroutinefunction(function):
|
|
186
168
|
|
|
187
169
|
async def wrapper(data: Sequence[Any]) -> None:
|
|
188
170
|
function(data)
|
|
@@ -194,8 +176,8 @@ def to_event_handler_function(
|
|
|
194
176
|
|
|
195
177
|
|
|
196
178
|
def merge_event_handlers(
|
|
197
|
-
event_handlers: Sequence[
|
|
198
|
-
) ->
|
|
179
|
+
event_handlers: Sequence[BaseEventHandler],
|
|
180
|
+
) -> BaseEventHandler:
|
|
199
181
|
"""Merge multiple event handlers into one
|
|
200
182
|
|
|
201
183
|
Raises a ValueError if any handlers have conflicting
|
|
@@ -247,3 +229,46 @@ def merge_event_handler_funcs(
|
|
|
247
229
|
group.start_soon(func, data)
|
|
248
230
|
|
|
249
231
|
return await_all_event_handlers
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@lru_cache(maxsize=4096)
|
|
235
|
+
def _inspect_event_handler_code(code: CodeType) -> tuple[bool, bool]:
|
|
236
|
+
prevent_default = False
|
|
237
|
+
stop_propagation = False
|
|
238
|
+
|
|
239
|
+
if code.co_argcount > 0:
|
|
240
|
+
names = code.co_names
|
|
241
|
+
check_prevent_default = "preventDefault" in names
|
|
242
|
+
check_stop_propagation = "stopPropagation" in names
|
|
243
|
+
|
|
244
|
+
if not (check_prevent_default or check_stop_propagation):
|
|
245
|
+
return False, False
|
|
246
|
+
|
|
247
|
+
event_arg_name = code.co_varnames[0]
|
|
248
|
+
last_was_event = False
|
|
249
|
+
|
|
250
|
+
for instr in dis.get_instructions(code):
|
|
251
|
+
if (
|
|
252
|
+
instr.opname in ("LOAD_FAST", "LOAD_FAST_BORROW")
|
|
253
|
+
and instr.argval == event_arg_name
|
|
254
|
+
):
|
|
255
|
+
last_was_event = True
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
if last_was_event and instr.opname in (
|
|
259
|
+
"LOAD_METHOD",
|
|
260
|
+
"LOAD_ATTR",
|
|
261
|
+
):
|
|
262
|
+
if check_prevent_default and instr.argval == "preventDefault":
|
|
263
|
+
prevent_default = True
|
|
264
|
+
check_prevent_default = False
|
|
265
|
+
elif check_stop_propagation and instr.argval == "stopPropagation":
|
|
266
|
+
stop_propagation = True
|
|
267
|
+
check_stop_propagation = False
|
|
268
|
+
|
|
269
|
+
if not (check_prevent_default or check_stop_propagation):
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
last_was_event = False
|
|
273
|
+
|
|
274
|
+
return prevent_default, stop_propagation
|
reactpy/core/hooks.py
CHANGED
|
@@ -2,31 +2,30 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
-
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Callable, Coroutine, Sequence
|
|
6
7
|
from logging import getLogger
|
|
7
8
|
from types import FunctionType
|
|
8
9
|
from typing import (
|
|
9
10
|
TYPE_CHECKING,
|
|
10
11
|
Any,
|
|
11
|
-
Callable,
|
|
12
12
|
Generic,
|
|
13
13
|
Protocol,
|
|
14
|
+
TypeAlias,
|
|
14
15
|
TypeVar,
|
|
15
16
|
cast,
|
|
16
17
|
overload,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
|
-
from typing_extensions import TypeAlias
|
|
20
|
-
|
|
21
20
|
from reactpy.config import REACTPY_DEBUG
|
|
22
21
|
from reactpy.core._life_cycle_hook import HOOK_STACK
|
|
23
22
|
from reactpy.types import (
|
|
24
23
|
Connection,
|
|
25
24
|
Context,
|
|
25
|
+
ContextProvider,
|
|
26
26
|
Key,
|
|
27
27
|
Location,
|
|
28
28
|
State,
|
|
29
|
-
VdomDict,
|
|
30
29
|
)
|
|
31
30
|
from reactpy.utils import Ref
|
|
32
31
|
|
|
@@ -146,11 +145,6 @@ def use_effect(
|
|
|
146
145
|
Returns:
|
|
147
146
|
If not function is provided, a decorator. Otherwise ``None``.
|
|
148
147
|
"""
|
|
149
|
-
if asyncio.iscoroutinefunction(function):
|
|
150
|
-
raise TypeError(
|
|
151
|
-
"`use_effect` does not support async functions. "
|
|
152
|
-
"Use `use_async_effect` instead."
|
|
153
|
-
)
|
|
154
148
|
|
|
155
149
|
hook = HOOK_STACK.current_hook()
|
|
156
150
|
dependencies = _try_to_infer_closure_values(function, dependencies)
|
|
@@ -158,6 +152,12 @@ def use_effect(
|
|
|
158
152
|
cleanup_func: Ref[_EffectCleanFunc | None] = use_ref(None)
|
|
159
153
|
|
|
160
154
|
def decorator(func: _SyncEffectFunc) -> None:
|
|
155
|
+
if inspect.iscoroutinefunction(func):
|
|
156
|
+
raise TypeError(
|
|
157
|
+
"`use_effect` does not support async functions. "
|
|
158
|
+
"Use `use_async_effect` instead."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
161
|
async def effect(stop: asyncio.Event) -> None:
|
|
162
162
|
# Since the effect is asynchronous, we need to make sure we
|
|
163
163
|
# always clean up the previous effect's resources
|
|
@@ -303,8 +303,8 @@ def create_context(default_value: _Type) -> Context[_Type]:
|
|
|
303
303
|
*children: Any,
|
|
304
304
|
value: _Type = default_value,
|
|
305
305
|
key: Key | None = None,
|
|
306
|
-
) ->
|
|
307
|
-
return
|
|
306
|
+
) -> ContextProvider[_Type]:
|
|
307
|
+
return ContextProvider(
|
|
308
308
|
*children,
|
|
309
309
|
value=value,
|
|
310
310
|
key=key,
|
|
@@ -360,27 +360,6 @@ def use_location() -> Location:
|
|
|
360
360
|
return use_connection().location
|
|
361
361
|
|
|
362
362
|
|
|
363
|
-
class _ContextProvider(Generic[_Type]):
|
|
364
|
-
def __init__(
|
|
365
|
-
self,
|
|
366
|
-
*children: Any,
|
|
367
|
-
value: _Type,
|
|
368
|
-
key: Key | None,
|
|
369
|
-
type: Context[_Type],
|
|
370
|
-
) -> None:
|
|
371
|
-
self.children = children
|
|
372
|
-
self.key = key
|
|
373
|
-
self.type = type
|
|
374
|
-
self.value = value
|
|
375
|
-
|
|
376
|
-
def render(self) -> VdomDict:
|
|
377
|
-
HOOK_STACK.current_hook().set_context_provider(self)
|
|
378
|
-
return VdomDict(tagName="", children=self.children)
|
|
379
|
-
|
|
380
|
-
def __repr__(self) -> str:
|
|
381
|
-
return f"ContextProvider({self.type})"
|
|
382
|
-
|
|
383
|
-
|
|
384
363
|
_ActionType = TypeVar("_ActionType")
|
|
385
364
|
|
|
386
365
|
|
|
@@ -515,7 +494,7 @@ def use_memo(
|
|
|
515
494
|
# if deps are same length check identity for each item
|
|
516
495
|
or not all(
|
|
517
496
|
strictly_equal(current, new)
|
|
518
|
-
for current, new in zip(memo.deps, dependencies)
|
|
497
|
+
for current, new in zip(memo.deps, dependencies, strict=False)
|
|
519
498
|
)
|
|
520
499
|
):
|
|
521
500
|
memo.deps = dependencies
|
|
@@ -617,7 +596,18 @@ def strictly_equal(x: Any, y: Any) -> bool:
|
|
|
617
596
|
getattr(x.__code__, attr) == getattr(y.__code__, attr)
|
|
618
597
|
for attr in dir(x.__code__)
|
|
619
598
|
if attr.startswith("co_")
|
|
620
|
-
and attr
|
|
599
|
+
and attr
|
|
600
|
+
not in {
|
|
601
|
+
"co_positions",
|
|
602
|
+
"co_linetable",
|
|
603
|
+
"co_lines",
|
|
604
|
+
"co_lnotab",
|
|
605
|
+
"co_branches",
|
|
606
|
+
"co_firstlineno",
|
|
607
|
+
"co_end_lineno",
|
|
608
|
+
"co_col_offset",
|
|
609
|
+
"co_end_col_offset",
|
|
610
|
+
}
|
|
621
611
|
)
|
|
622
612
|
|
|
623
613
|
# Check via the `==` operator if possible
|