reactpy 2.0.0b3__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 CHANGED
@@ -2,7 +2,7 @@ from reactpy import config, logging, types, web, widgets
2
2
  from reactpy._html import html
3
3
  from reactpy.core import hooks
4
4
  from reactpy.core.component import component
5
- from reactpy.core.events import Event, event
5
+ from reactpy.core.events import event
6
6
  from reactpy.core.hooks import (
7
7
  create_context,
8
8
  use_async_effect,
@@ -18,17 +18,14 @@ from reactpy.core.hooks import (
18
18
  use_scope,
19
19
  use_state,
20
20
  )
21
- from reactpy.core.layout import Layout
22
21
  from reactpy.core.vdom import Vdom
23
22
  from reactpy.pyscript.components import pyscript_component
24
23
  from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy
25
24
 
26
25
  __author__ = "The Reactive Python Team"
27
- __version__ = "2.0.0b3"
26
+ __version__ = "2.0.0b5"
28
27
 
29
28
  __all__ = [
30
- "Event",
31
- "Layout",
32
29
  "Ref",
33
30
  "Vdom",
34
31
  "component",
@@ -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, Callable, Generic, TypeVar, cast
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=False,
88
+ default=True,
89
89
  mutable=True,
90
90
  validator=boolean,
91
91
  )
92
- """Whether to render components asynchronously. This is currently an experimental feature."""
92
+ """Whether to render components asynchronously."""
93
93
 
94
94
  REACTPY_RECONNECT_INTERVAL = Option(
95
95
  "REACTPY_RECONNECT_INTERVAL",
@@ -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, Callable, Protocol, TypeVar
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 ComponentType, Context, ContextProviderType
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: ComponentType
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], ContextProviderType[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: ContextProviderType[Any]) -> None:
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: ComponentType) -> None:
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
@@ -1,5 +1,6 @@
1
+ from collections.abc import Callable
1
2
  from threading import Thread, current_thread
2
- from typing import Callable, Generic, TypeVar
3
+ from typing import Generic, TypeVar
3
4
  from weakref import WeakKeyDictionary
4
5
 
5
6
  _StateType = TypeVar("_StateType")
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, Callable
6
+ from typing import Any
6
7
 
7
- from reactpy.types import ComponentType, VdomDict
8
+ from reactpy.types import Component, VdomDict
8
9
 
9
10
 
10
11
  def component(
11
- function: Callable[..., ComponentType | VdomDict | str | None],
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
- from collections.abc import Sequence
6
- from typing import Any, Callable, Literal, cast, overload
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 EventHandlerFunc, EventHandlerType
12
+ from reactpy.types import BaseEventHandler, EventHandlerFunc
11
13
 
12
14
 
13
15
  @overload
@@ -73,19 +75,7 @@ def event(
73
75
  return setup(function) if function is not None else setup
74
76
 
75
77
 
76
- class Event(dict):
77
- def __getattr__(self, name: str) -> Any:
78
- value = self.get(name)
79
- return Event(value) if isinstance(value, dict) else value
80
-
81
- def preventDefault(self) -> None:
82
- """Prevent the default action of the event."""
83
-
84
- def stopPropagation(self) -> None:
85
- """Stop the event from propagating."""
86
-
87
-
88
- class EventHandler:
78
+ class EventHandler(BaseEventHandler):
89
79
  """Turn a function or coroutine into an event handler
90
80
 
91
81
  Parameters:
@@ -99,14 +89,6 @@ class EventHandler:
99
89
  A unique identifier for this event handler (auto-generated by default)
100
90
  """
101
91
 
102
- __slots__ = (
103
- "__weakref__",
104
- "function",
105
- "prevent_default",
106
- "stop_propagation",
107
- "target",
108
- )
109
-
110
92
  def __init__(
111
93
  self,
112
94
  function: EventHandlerFunc,
@@ -125,26 +107,14 @@ class EventHandler:
125
107
  while hasattr(func_to_inspect, "__wrapped__"):
126
108
  func_to_inspect = func_to_inspect.__wrapped__
127
109
 
128
- code = func_to_inspect.__code__
129
- if code.co_argcount > 0:
130
- event_arg_name = code.co_varnames[0]
131
- last_was_event = False
132
-
133
- for instr in dis.get_instructions(func_to_inspect):
134
- if instr.opname == "LOAD_FAST" and instr.argval == event_arg_name:
135
- last_was_event = True
136
- continue
137
-
138
- if last_was_event and instr.opname in (
139
- "LOAD_METHOD",
140
- "LOAD_ATTR",
141
- ):
142
- if instr.argval == "preventDefault":
143
- self.prevent_default = True
144
- elif instr.argval == "stopPropagation":
145
- self.stop_propagation = True
110
+ found_prevent_default, found_stop_propagation = _inspect_event_handler_code(
111
+ func_to_inspect.__code__
112
+ )
146
113
 
147
- last_was_event = False
114
+ if found_prevent_default:
115
+ self.prevent_default = True
116
+ if found_stop_propagation:
117
+ self.stop_propagation = True
148
118
 
149
119
  __hash__ = None # type: ignore
150
120
 
@@ -180,7 +150,7 @@ def to_event_handler_function(
180
150
  Whether to pass the event parameters a positional args or as a list.
181
151
  """
182
152
  if positional_args:
183
- if asyncio.iscoroutinefunction(function):
153
+ if inspect.iscoroutinefunction(function):
184
154
 
185
155
  async def wrapper(data: Sequence[Any]) -> None:
186
156
  await function(*data)
@@ -194,7 +164,7 @@ def to_event_handler_function(
194
164
 
195
165
  cast(Any, wrapper).__wrapped__ = function
196
166
  return wrapper
197
- elif not asyncio.iscoroutinefunction(function):
167
+ elif not inspect.iscoroutinefunction(function):
198
168
 
199
169
  async def wrapper(data: Sequence[Any]) -> None:
200
170
  function(data)
@@ -206,8 +176,8 @@ def to_event_handler_function(
206
176
 
207
177
 
208
178
  def merge_event_handlers(
209
- event_handlers: Sequence[EventHandlerType],
210
- ) -> EventHandlerType:
179
+ event_handlers: Sequence[BaseEventHandler],
180
+ ) -> BaseEventHandler:
211
181
  """Merge multiple event handlers into one
212
182
 
213
183
  Raises a ValueError if any handlers have conflicting
@@ -259,3 +229,46 @@ def merge_event_handler_funcs(
259
229
  group.start_soon(func, data)
260
230
 
261
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
- from collections.abc import Coroutine, Sequence
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,12 +145,19 @@ def use_effect(
146
145
  Returns:
147
146
  If not function is provided, a decorator. Otherwise ``None``.
148
147
  """
148
+
149
149
  hook = HOOK_STACK.current_hook()
150
150
  dependencies = _try_to_infer_closure_values(function, dependencies)
151
151
  memoize = use_memo(dependencies=dependencies)
152
152
  cleanup_func: Ref[_EffectCleanFunc | None] = use_ref(None)
153
153
 
154
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
+
155
161
  async def effect(stop: asyncio.Event) -> None:
156
162
  # Since the effect is asynchronous, we need to make sure we
157
163
  # always clean up the previous effect's resources
@@ -297,8 +303,8 @@ def create_context(default_value: _Type) -> Context[_Type]:
297
303
  *children: Any,
298
304
  value: _Type = default_value,
299
305
  key: Key | None = None,
300
- ) -> _ContextProvider[_Type]:
301
- return _ContextProvider(
306
+ ) -> ContextProvider[_Type]:
307
+ return ContextProvider(
302
308
  *children,
303
309
  value=value,
304
310
  key=key,
@@ -354,27 +360,6 @@ def use_location() -> Location:
354
360
  return use_connection().location
355
361
 
356
362
 
357
- class _ContextProvider(Generic[_Type]):
358
- def __init__(
359
- self,
360
- *children: Any,
361
- value: _Type,
362
- key: Key | None,
363
- type: Context[_Type],
364
- ) -> None:
365
- self.children = children
366
- self.key = key
367
- self.type = type
368
- self.value = value
369
-
370
- def render(self) -> VdomDict:
371
- HOOK_STACK.current_hook().set_context_provider(self)
372
- return VdomDict(tagName="", children=self.children)
373
-
374
- def __repr__(self) -> str:
375
- return f"ContextProvider({self.type})"
376
-
377
-
378
363
  _ActionType = TypeVar("_ActionType")
379
364
 
380
365
 
@@ -509,7 +494,7 @@ def use_memo(
509
494
  # if deps are same length check identity for each item
510
495
  or not all(
511
496
  strictly_equal(current, new)
512
- for current, new in zip(memo.deps, dependencies)
497
+ for current, new in zip(memo.deps, dependencies, strict=False)
513
498
  )
514
499
  ):
515
500
  memo.deps = dependencies
@@ -611,7 +596,18 @@ def strictly_equal(x: Any, y: Any) -> bool:
611
596
  getattr(x.__code__, attr) == getattr(y.__code__, attr)
612
597
  for attr in dir(x.__code__)
613
598
  if attr.startswith("co_")
614
- and attr not in {"co_positions", "co_linetable", "co_lines", "co_lnotab"}
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
+ }
615
611
  )
616
612
 
617
613
  # Check via the `==` operator if possible