pyreact-framework 1.0.0__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.
- pyreact/__init__.py +144 -0
- pyreact/cli/__init__.py +3 -0
- pyreact/cli/main.py +512 -0
- pyreact/core/__init__.py +80 -0
- pyreact/core/component.py +372 -0
- pyreact/core/context.py +173 -0
- pyreact/core/element.py +208 -0
- pyreact/core/error_boundary.py +145 -0
- pyreact/core/hooks.py +550 -0
- pyreact/core/memo.py +221 -0
- pyreact/core/portal.py +159 -0
- pyreact/core/reconciler.py +399 -0
- pyreact/core/refs.py +213 -0
- pyreact/core/renderer.py +112 -0
- pyreact/core/scheduler.py +304 -0
- pyreact/devtools/__init__.py +18 -0
- pyreact/devtools/debugger.py +314 -0
- pyreact/devtools/profiler.py +288 -0
- pyreact/dom/__init__.py +64 -0
- pyreact/dom/attributes.py +317 -0
- pyreact/dom/dom_operations.py +333 -0
- pyreact/dom/events.py +349 -0
- pyreact/server/__init__.py +34 -0
- pyreact/server/hydration.py +216 -0
- pyreact/server/ssr.py +344 -0
- pyreact/styles/__init__.py +19 -0
- pyreact/styles/css_module.py +231 -0
- pyreact/styles/styled.py +303 -0
- pyreact/testing/__init__.py +71 -0
- pyreact/testing/fire_event.py +355 -0
- pyreact/testing/screen.py +267 -0
- pyreact/testing/test_renderer.py +232 -0
- pyreact/utils/__init__.py +17 -0
- pyreact/utils/diff.py +182 -0
- pyreact/utils/object_pool.py +216 -0
- pyreact_framework-1.0.0.dist-info/METADATA +363 -0
- pyreact_framework-1.0.0.dist-info/RECORD +41 -0
- pyreact_framework-1.0.0.dist-info/WHEEL +5 -0
- pyreact_framework-1.0.0.dist-info/entry_points.txt +2 -0
- pyreact_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyreact_framework-1.0.0.dist-info/top_level.txt +1 -0
pyreact/core/renderer.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""DOM Renderer Module
|
|
2
|
+
|
|
3
|
+
This module handles rendering VNodes to actual DOM elements and managing the root container.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
7
|
+
import weakref
|
|
8
|
+
from .element import VNode
|
|
9
|
+
from .component import Component
|
|
10
|
+
from .reconciler import Reconciler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Root:
|
|
14
|
+
"""
|
|
15
|
+
Root container for a PyReact application.
|
|
16
|
+
|
|
17
|
+
Manages the root DOM element and coordinates updates.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, container: Any, options: Optional[Dict[str, Any]] = None):
|
|
21
|
+
"""Initialize root container"""
|
|
22
|
+
self.container = container
|
|
23
|
+
self.options = options or {}
|
|
24
|
+
self._reconciler = Reconciler()
|
|
25
|
+
self._current_vnode: Optional[VNode] = None
|
|
26
|
+
self._is_rendering: bool = False
|
|
27
|
+
self._update_scheduled: bool = False
|
|
28
|
+
self._callbacks: List[Callable] = []
|
|
29
|
+
|
|
30
|
+
def render(self, element: Optional[VNode]) -> None:
|
|
31
|
+
"""Render an element to the container"""
|
|
32
|
+
if element is None:
|
|
33
|
+
self.unmount()
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
self._is_rendering = True
|
|
37
|
+
try:
|
|
38
|
+
if self._current_vnode is None:
|
|
39
|
+
if not self.options.get('hydrate'):
|
|
40
|
+
self._clear_container()
|
|
41
|
+
|
|
42
|
+
dom = self._reconciler.create_dom(element)
|
|
43
|
+
self.container.append_child(dom)
|
|
44
|
+
else:
|
|
45
|
+
self._reconciler.diff(
|
|
46
|
+
self._current_vnode,
|
|
47
|
+
element,
|
|
48
|
+
self.container
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self._current_vnode = element
|
|
52
|
+
finally:
|
|
53
|
+
self._is_rendering = False
|
|
54
|
+
|
|
55
|
+
self._run_callbacks()
|
|
56
|
+
|
|
57
|
+
def unmount(self) -> None:
|
|
58
|
+
"""Unmount the current tree"""
|
|
59
|
+
if self._current_vnode:
|
|
60
|
+
self._reconciler.unmount(self._current_vnode)
|
|
61
|
+
self._clear_container()
|
|
62
|
+
self._current_vnode = None
|
|
63
|
+
|
|
64
|
+
def _clear_container(self) -> None:
|
|
65
|
+
"""Clear all children from container"""
|
|
66
|
+
while self.container.first_child:
|
|
67
|
+
self.container.remove_child(self.container.first_child)
|
|
68
|
+
|
|
69
|
+
def _run_callbacks(self) -> None:
|
|
70
|
+
"""Run pending callbacks"""
|
|
71
|
+
callbacks = self._callbacks.copy()
|
|
72
|
+
self._callbacks.clear()
|
|
73
|
+
for callback in callbacks:
|
|
74
|
+
callback()
|
|
75
|
+
|
|
76
|
+
def _schedule_callback(self, callback: Callable) -> None:
|
|
77
|
+
"""Schedule a callback to run after render"""
|
|
78
|
+
self._callbacks.append(callback)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_root(container: Any, options: Optional[Dict[str, Any]] = None) -> Root:
|
|
82
|
+
"""Create a root for rendering"""
|
|
83
|
+
return Root(container, options)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def render(element: VNode, container: Any) -> Root:
|
|
87
|
+
"""Render an element into a container (legacy API)"""
|
|
88
|
+
while container.first_child:
|
|
89
|
+
container.remove_child(container.first_child)
|
|
90
|
+
|
|
91
|
+
root = create_root(container)
|
|
92
|
+
root.render(element)
|
|
93
|
+
return root
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def hydrate(element: VNode, container: Any) -> Root:
|
|
97
|
+
"""Hydrate existing HTML with PyReact interactivity"""
|
|
98
|
+
root = create_root(container, {'hydrate': True})
|
|
99
|
+
root.render(element)
|
|
100
|
+
return root
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def unmount_component_at_node(container: Any) -> bool:
|
|
104
|
+
"""Unmount a PyReact component from a container"""
|
|
105
|
+
while container.first_child:
|
|
106
|
+
container.remove_child(container.first_child)
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def find_dom_node(component: Component) -> Optional[Any]:
|
|
111
|
+
"""Find the DOM node associated with a component"""
|
|
112
|
+
return component._dom_node
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler Module
|
|
3
|
+
================
|
|
4
|
+
|
|
5
|
+
This module implements the scheduler for managing updates
|
|
6
|
+
with different priorities.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
10
|
+
from enum import IntEnum
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Priority(IntEnum):
|
|
15
|
+
"""Priority levels for scheduled updates"""
|
|
16
|
+
IMMEDIATE = 99 # Execute immediately
|
|
17
|
+
USER_BLOCKING = 98 # User interactions
|
|
18
|
+
NORMAL = 97 # Normal updates
|
|
19
|
+
LOW = 96 # Preloading, etc.
|
|
20
|
+
IDLE = 95 # Background tasks
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_timeout_for_priority(priority: Priority) -> float:
|
|
24
|
+
"""Get timeout for a priority level"""
|
|
25
|
+
timeouts = {
|
|
26
|
+
Priority.IMMEDIATE: 0,
|
|
27
|
+
Priority.USER_BLOCKING: 250,
|
|
28
|
+
Priority.NORMAL: 5000,
|
|
29
|
+
Priority.LOW: 10000,
|
|
30
|
+
Priority.IDLE: float('inf')
|
|
31
|
+
}
|
|
32
|
+
return timeouts.get(priority, 5000)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Task:
|
|
36
|
+
"""Scheduled task"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
callback: Callable,
|
|
41
|
+
priority: Priority,
|
|
42
|
+
expiration_time: float
|
|
43
|
+
):
|
|
44
|
+
self.callback = callback
|
|
45
|
+
self.priority = priority
|
|
46
|
+
self.expiration_time = expiration_time
|
|
47
|
+
self.completed = False
|
|
48
|
+
|
|
49
|
+
def __lt__(self, other: 'Task') -> bool:
|
|
50
|
+
"""Compare tasks by priority (higher priority first)"""
|
|
51
|
+
return self.priority > other.priority
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
return f"Task(priority={self.priority.name}, completed={self.completed})"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Scheduler:
|
|
58
|
+
"""
|
|
59
|
+
Scheduler for managing updates with priorities
|
|
60
|
+
|
|
61
|
+
Implements a priority queue for scheduling callbacks.
|
|
62
|
+
Higher priority tasks are executed first.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
scheduler = Scheduler()
|
|
66
|
+
|
|
67
|
+
# Schedule high priority task
|
|
68
|
+
scheduler.schedule_callback(
|
|
69
|
+
lambda: print('User interaction'),
|
|
70
|
+
Priority.USER_BLOCKING
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Schedule low priority task
|
|
74
|
+
scheduler.schedule_callback(
|
|
75
|
+
lambda: print('Background task'),
|
|
76
|
+
Priority.IDLE
|
|
77
|
+
)
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
IMMEDIATE_PRIORITY = Priority.IMMEDIATE
|
|
81
|
+
USER_BLOCKING_PRIORITY = Priority.USER_BLOCKING
|
|
82
|
+
NORMAL_PRIORITY = Priority.NORMAL
|
|
83
|
+
LOW_PRIORITY = Priority.LOW
|
|
84
|
+
IDLE_PRIORITY = Priority.IDLE
|
|
85
|
+
|
|
86
|
+
def __init__(self):
|
|
87
|
+
self._queue: List[Task] = []
|
|
88
|
+
self._scheduled: bool = False
|
|
89
|
+
self._is_flushing: bool = False
|
|
90
|
+
self._current_task: Optional[Task] = None
|
|
91
|
+
|
|
92
|
+
def schedule_callback(
|
|
93
|
+
self,
|
|
94
|
+
callback: Callable,
|
|
95
|
+
priority: Priority = Priority.NORMAL
|
|
96
|
+
) -> Task:
|
|
97
|
+
"""
|
|
98
|
+
Schedule a callback with a priority
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
callback: Function to execute
|
|
102
|
+
priority: Priority level
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Task: The scheduled task
|
|
106
|
+
"""
|
|
107
|
+
timeout = get_timeout_for_priority(priority)
|
|
108
|
+
expiration_time = time.time() + timeout
|
|
109
|
+
|
|
110
|
+
task = Task(callback, priority, expiration_time)
|
|
111
|
+
|
|
112
|
+
# Insert sorted by priority
|
|
113
|
+
self._insert_sorted(task)
|
|
114
|
+
|
|
115
|
+
# Ensure work loop is scheduled
|
|
116
|
+
self._ensure_scheduled()
|
|
117
|
+
|
|
118
|
+
return task
|
|
119
|
+
|
|
120
|
+
def _insert_sorted(self, task: Task) -> None:
|
|
121
|
+
"""Insert task in sorted order"""
|
|
122
|
+
for i, existing in enumerate(self._queue):
|
|
123
|
+
if task < existing:
|
|
124
|
+
self._queue.insert(i, task)
|
|
125
|
+
return
|
|
126
|
+
self._queue.append(task)
|
|
127
|
+
|
|
128
|
+
def _ensure_scheduled(self) -> None:
|
|
129
|
+
"""Ensure the work loop is scheduled"""
|
|
130
|
+
if not self._scheduled:
|
|
131
|
+
self._scheduled = True
|
|
132
|
+
self._schedule_work_loop()
|
|
133
|
+
|
|
134
|
+
def _schedule_work_loop(self) -> None:
|
|
135
|
+
"""Schedule the work loop (override for different platforms)"""
|
|
136
|
+
# In a real implementation, this would use requestIdleCallback or similar
|
|
137
|
+
# For now, we'll execute immediately
|
|
138
|
+
self._work_loop()
|
|
139
|
+
|
|
140
|
+
def _work_loop(self) -> None:
|
|
141
|
+
"""Process tasks until deadline or queue is empty"""
|
|
142
|
+
self._is_flushing = True
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
while self._queue:
|
|
146
|
+
task = self._queue[0]
|
|
147
|
+
|
|
148
|
+
# Check if task has expired
|
|
149
|
+
if task.expiration_time < time.time():
|
|
150
|
+
# Task expired, execute it
|
|
151
|
+
self._queue.pop(0)
|
|
152
|
+
self._execute_task(task)
|
|
153
|
+
else:
|
|
154
|
+
# Check if we should yield
|
|
155
|
+
# In a real implementation, we'd check deadline
|
|
156
|
+
self._queue.pop(0)
|
|
157
|
+
self._execute_task(task)
|
|
158
|
+
finally:
|
|
159
|
+
self._is_flushing = False
|
|
160
|
+
self._scheduled = False
|
|
161
|
+
|
|
162
|
+
# Re-schedule if there are more tasks
|
|
163
|
+
if self._queue:
|
|
164
|
+
self._ensure_scheduled()
|
|
165
|
+
|
|
166
|
+
def _execute_task(self, task: Task) -> None:
|
|
167
|
+
"""Execute a single task"""
|
|
168
|
+
self._current_task = task
|
|
169
|
+
try:
|
|
170
|
+
task.callback()
|
|
171
|
+
task.completed = True
|
|
172
|
+
except Exception as e:
|
|
173
|
+
print(f"Error executing scheduled task: {e}")
|
|
174
|
+
finally:
|
|
175
|
+
self._current_task = None
|
|
176
|
+
|
|
177
|
+
def cancel_task(self, task: Task) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Cancel a scheduled task
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
task: Task to cancel
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
bool: True if task was cancelled
|
|
186
|
+
"""
|
|
187
|
+
if task in self._queue:
|
|
188
|
+
self._queue.remove(task)
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
def flush_all(self) -> None:
|
|
193
|
+
"""Execute all pending tasks"""
|
|
194
|
+
while self._queue:
|
|
195
|
+
task = self._queue.pop(0)
|
|
196
|
+
self._execute_task(task)
|
|
197
|
+
|
|
198
|
+
def get_first_task(self) -> Optional[Task]:
|
|
199
|
+
"""Get the highest priority task without removing it"""
|
|
200
|
+
return self._queue[0] if self._queue else None
|
|
201
|
+
|
|
202
|
+
def is_empty(self) -> bool:
|
|
203
|
+
"""Check if queue is empty"""
|
|
204
|
+
return len(self._queue) == 0
|
|
205
|
+
|
|
206
|
+
def get_pending_count(self) -> int:
|
|
207
|
+
"""Get number of pending tasks"""
|
|
208
|
+
return len(self._queue)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class UpdateScheduler:
|
|
212
|
+
"""
|
|
213
|
+
Scheduler specifically for component updates
|
|
214
|
+
|
|
215
|
+
Manages batch updates and priorities for component re-renders.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
def __init__(self):
|
|
219
|
+
self._pending_updates: Dict[int, Any] = {}
|
|
220
|
+
self._batch_depth: int = 0
|
|
221
|
+
self._is_batching: bool = False
|
|
222
|
+
self._scheduler = Scheduler()
|
|
223
|
+
|
|
224
|
+
def schedule_update(
|
|
225
|
+
self,
|
|
226
|
+
component: Any,
|
|
227
|
+
priority: Priority = Priority.NORMAL
|
|
228
|
+
) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Schedule a component update
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
component: Component to update
|
|
234
|
+
priority: Update priority
|
|
235
|
+
"""
|
|
236
|
+
component_id = id(component)
|
|
237
|
+
self._pending_updates[component_id] = component
|
|
238
|
+
|
|
239
|
+
if not self._is_batching:
|
|
240
|
+
self._flush_updates()
|
|
241
|
+
|
|
242
|
+
def batch_updates(self, callback: Callable) -> None:
|
|
243
|
+
"""
|
|
244
|
+
Execute callback with batched updates
|
|
245
|
+
|
|
246
|
+
Updates are not flushed until callback completes.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
callback: Function to execute
|
|
250
|
+
"""
|
|
251
|
+
self._is_batching = True
|
|
252
|
+
self._batch_depth += 1
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
callback()
|
|
256
|
+
finally:
|
|
257
|
+
self._batch_depth -= 1
|
|
258
|
+
if self._batch_depth == 0:
|
|
259
|
+
self._is_batching = False
|
|
260
|
+
self._flush_updates()
|
|
261
|
+
|
|
262
|
+
def _flush_updates(self) -> None:
|
|
263
|
+
"""Flush all pending updates"""
|
|
264
|
+
updates = self._pending_updates.copy()
|
|
265
|
+
self._pending_updates.clear()
|
|
266
|
+
|
|
267
|
+
for component in updates.values():
|
|
268
|
+
if hasattr(component, '_apply_state'):
|
|
269
|
+
component._apply_state()
|
|
270
|
+
if hasattr(component, 'render'):
|
|
271
|
+
component.render()
|
|
272
|
+
|
|
273
|
+
def defer_update(self, component: Any) -> None:
|
|
274
|
+
"""Defer an update to low priority"""
|
|
275
|
+
self.schedule_update(component, Priority.LOW)
|
|
276
|
+
|
|
277
|
+
def immediate_update(self, component: Any) -> None:
|
|
278
|
+
"""Execute an immediate update"""
|
|
279
|
+
self.schedule_update(component, Priority.IMMEDIATE)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# Global scheduler instance
|
|
283
|
+
_scheduler = Scheduler()
|
|
284
|
+
_update_scheduler = UpdateScheduler()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_scheduler() -> Scheduler:
|
|
288
|
+
"""Get the global scheduler"""
|
|
289
|
+
return _scheduler
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_update_scheduler() -> UpdateScheduler:
|
|
293
|
+
"""Get the global update scheduler"""
|
|
294
|
+
return _update_scheduler
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def schedule_callback(callback: Callable, priority: Priority = Priority.NORMAL) -> Task:
|
|
298
|
+
"""Schedule a callback with the global scheduler"""
|
|
299
|
+
return _scheduler.schedule_callback(callback, priority)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def batch_updates(callback: Callable) -> None:
|
|
303
|
+
"""Batch multiple updates together"""
|
|
304
|
+
_update_scheduler.batch_updates(callback)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyReact DevTools Module
|
|
3
|
+
=======================
|
|
4
|
+
|
|
5
|
+
Development tools for debugging and profiling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .debugger import Debugger, debug_component, get_component_tree
|
|
9
|
+
from .profiler import Profiler, profile_component, get_profile_data
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'Debugger',
|
|
13
|
+
'debug_component',
|
|
14
|
+
'get_component_tree',
|
|
15
|
+
'Profiler',
|
|
16
|
+
'profile_component',
|
|
17
|
+
'get_profile_data',
|
|
18
|
+
]
|