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.
Files changed (41) hide show
  1. pyreact/__init__.py +144 -0
  2. pyreact/cli/__init__.py +3 -0
  3. pyreact/cli/main.py +512 -0
  4. pyreact/core/__init__.py +80 -0
  5. pyreact/core/component.py +372 -0
  6. pyreact/core/context.py +173 -0
  7. pyreact/core/element.py +208 -0
  8. pyreact/core/error_boundary.py +145 -0
  9. pyreact/core/hooks.py +550 -0
  10. pyreact/core/memo.py +221 -0
  11. pyreact/core/portal.py +159 -0
  12. pyreact/core/reconciler.py +399 -0
  13. pyreact/core/refs.py +213 -0
  14. pyreact/core/renderer.py +112 -0
  15. pyreact/core/scheduler.py +304 -0
  16. pyreact/devtools/__init__.py +18 -0
  17. pyreact/devtools/debugger.py +314 -0
  18. pyreact/devtools/profiler.py +288 -0
  19. pyreact/dom/__init__.py +64 -0
  20. pyreact/dom/attributes.py +317 -0
  21. pyreact/dom/dom_operations.py +333 -0
  22. pyreact/dom/events.py +349 -0
  23. pyreact/server/__init__.py +34 -0
  24. pyreact/server/hydration.py +216 -0
  25. pyreact/server/ssr.py +344 -0
  26. pyreact/styles/__init__.py +19 -0
  27. pyreact/styles/css_module.py +231 -0
  28. pyreact/styles/styled.py +303 -0
  29. pyreact/testing/__init__.py +71 -0
  30. pyreact/testing/fire_event.py +355 -0
  31. pyreact/testing/screen.py +267 -0
  32. pyreact/testing/test_renderer.py +232 -0
  33. pyreact/utils/__init__.py +17 -0
  34. pyreact/utils/diff.py +182 -0
  35. pyreact/utils/object_pool.py +216 -0
  36. pyreact_framework-1.0.0.dist-info/METADATA +363 -0
  37. pyreact_framework-1.0.0.dist-info/RECORD +41 -0
  38. pyreact_framework-1.0.0.dist-info/WHEEL +5 -0
  39. pyreact_framework-1.0.0.dist-info/entry_points.txt +2 -0
  40. pyreact_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
  41. pyreact_framework-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,80 @@
1
+ """
2
+ PyReact Core Module
3
+ ===================
4
+
5
+ This module contains the core functionality of PyReact framework:
6
+ - VNode: Virtual DOM node representation
7
+ - Component: Base class for stateful components
8
+ - h(): Hyperscript function for creating elements
9
+ - Reconciler: Diff algorithm for efficient updates
10
+ - Renderer: DOM rendering engine
11
+ """
12
+
13
+ from .element import VNode, h, create_element
14
+ from .component import Component
15
+ from .renderer import render, hydrate, create_root
16
+ from .reconciler import Reconciler
17
+ from .hooks import (
18
+ use_state,
19
+ use_reducer,
20
+ use_effect,
21
+ use_layout_effect,
22
+ use_context,
23
+ use_ref,
24
+ use_memo,
25
+ use_callback,
26
+ use_imperative_handle,
27
+ use_debug_value,
28
+ use_id,
29
+ use_transition,
30
+ use_deferred_value,
31
+ )
32
+ from .context import create_context
33
+ from .refs import create_ref, forward_ref
34
+ from .portal import create_portal
35
+ from .memo import memo, lazy
36
+ from .error_boundary import ErrorBoundary
37
+ from .scheduler import Scheduler
38
+
39
+ __all__ = [
40
+ # Element
41
+ 'VNode',
42
+ 'h',
43
+ 'create_element',
44
+ # Component
45
+ 'Component',
46
+ # Renderer
47
+ 'render',
48
+ 'hydrate',
49
+ 'create_root',
50
+ # Reconciler
51
+ 'Reconciler',
52
+ # Hooks
53
+ 'use_state',
54
+ 'use_reducer',
55
+ 'use_effect',
56
+ 'use_layout_effect',
57
+ 'use_context',
58
+ 'use_ref',
59
+ 'use_memo',
60
+ 'use_callback',
61
+ 'use_imperative_handle',
62
+ 'use_debug_value',
63
+ 'use_id',
64
+ 'use_transition',
65
+ 'use_deferred_value',
66
+ # Context
67
+ 'create_context',
68
+ # Refs
69
+ 'create_ref',
70
+ 'forward_ref',
71
+ # Portal
72
+ 'create_portal',
73
+ # Memo
74
+ 'memo',
75
+ 'lazy',
76
+ # Error Boundary
77
+ 'ErrorBoundary',
78
+ # Scheduler
79
+ 'Scheduler',
80
+ ]
@@ -0,0 +1,372 @@
1
+ """
2
+ Component Base Class Module
3
+ ===========================
4
+
5
+ This module defines the base Component class for stateful components
6
+ in PyReact, similar to React's React.Component.
7
+ """
8
+
9
+ from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
10
+ from abc import ABC, abstractmethod
11
+ import weakref
12
+
13
+ if TYPE_CHECKING:
14
+ from .element import VNode
15
+ from .renderer import Root
16
+
17
+
18
+ class Component(ABC):
19
+ """
20
+ Base class for stateful components
21
+
22
+ Components are the building blocks of PyReact applications.
23
+ They manage their own state and define how to render themselves.
24
+
25
+ Lifecycle Methods:
26
+ - component_will_mount(): Called before mounting (deprecated)
27
+ - component_did_mount(): Called after mounting
28
+ - component_will_receive_props(): Called when props change (deprecated)
29
+ - should_component_update(): Return False to prevent re-render
30
+ - component_will_update(): Called before update (deprecated)
31
+ - component_did_update(): Called after update
32
+ - component_will_unmount(): Called before unmounting
33
+ - component_did_catch(): Called when child throws error
34
+
35
+ Example:
36
+ class Counter(Component):
37
+ def __init__(self, props):
38
+ super().__init__(props)
39
+ self.state = {'count': 0}
40
+
41
+ def increment(self, event):
42
+ self.set_state({'count': self.state['count'] + 1})
43
+
44
+ def render(self):
45
+ return h('div', {'className': 'counter'},
46
+ h('span', None, f"Count: {self.state['count']}"),
47
+ h('button', {'onClick': self.increment}, '+1')
48
+ )
49
+ """
50
+
51
+ def __init__(self, props: Optional[Dict[str, Any]] = None):
52
+ """
53
+ Initialize component
54
+
55
+ Args:
56
+ props: Properties passed from parent component
57
+ """
58
+ self.props: Dict[str, Any] = props or {}
59
+ self.state: Dict[str, Any] = {}
60
+ self.context: Dict[str, Any] = {}
61
+
62
+ # Internal state
63
+ self._pending_state: Optional[Dict[str, Any]] = None
64
+ self._pending_callbacks: List[Callable] = []
65
+ self._is_mounted: bool = False
66
+ self._is_rendering: bool = False
67
+ self._root: Optional[weakref.ref] = None
68
+ self._vnode: Optional['VNode'] = None
69
+ self._dom_node: Optional[Any] = None
70
+ self._updater: Optional[Callable] = None
71
+ self._force_update: bool = False
72
+
73
+ @abstractmethod
74
+ def render(self) -> 'VNode':
75
+ """
76
+ Render the component
77
+
78
+ Must be implemented by subclasses.
79
+
80
+ Returns:
81
+ VNode: Virtual DOM node representing the component's output
82
+ """
83
+ pass
84
+
85
+ def set_state(
86
+ self,
87
+ new_state: Union[Dict[str, Any], Callable[[Dict[str, Any]], Dict[str, Any]]],
88
+ callback: Optional[Callable] = None
89
+ ) -> None:
90
+ """
91
+ Update component state
92
+
93
+ Triggers a re-render after state update.
94
+
95
+ Args:
96
+ new_state: New state dict or function that returns new state
97
+ callback: Optional callback called after update
98
+
99
+ Example:
100
+ # Direct state update
101
+ self.set_state({'count': 1})
102
+
103
+ # Functional update
104
+ self.set_state(lambda state: {'count': state['count'] + 1})
105
+ """
106
+ if self._is_rendering:
107
+ # Queue update for after render
108
+ if callable(new_state):
109
+ new_state = new_state(self.state)
110
+ self._pending_state = {**self.state, **new_state}
111
+ if callback:
112
+ self._pending_callbacks.append(callback)
113
+ return
114
+
115
+ # Compute new state
116
+ if callable(new_state):
117
+ new_state = new_state(self.state)
118
+
119
+ # Merge with pending state if exists
120
+ if self._pending_state:
121
+ self._pending_state = {**self._pending_state, **new_state}
122
+ else:
123
+ self._pending_state = {**self.state, **new_state}
124
+
125
+ if callback:
126
+ self._pending_callbacks.append(callback)
127
+
128
+ # Schedule update
129
+ self._schedule_update()
130
+
131
+ def force_update(self, callback: Optional[Callable] = None) -> None:
132
+ """
133
+ Force a re-render of the component
134
+
135
+ Args:
136
+ callback: Optional callback called after update
137
+ """
138
+ self._force_update = True
139
+ if callback:
140
+ self._pending_callbacks.append(callback)
141
+ self._schedule_update()
142
+
143
+ def _schedule_update(self) -> None:
144
+ """Schedule an update with the renderer"""
145
+ if self._updater:
146
+ self._updater(self)
147
+
148
+ def _apply_state(self) -> bool:
149
+ """
150
+ Apply pending state changes
151
+
152
+ Returns:
153
+ bool: True if state was applied
154
+ """
155
+ if self._pending_state:
156
+ self.state = self._pending_state
157
+ self._pending_state = None
158
+ return True
159
+ return False
160
+
161
+ def _run_callbacks(self) -> None:
162
+ """Run pending callbacks after update"""
163
+ callbacks = self._pending_callbacks.copy()
164
+ self._pending_callbacks.clear()
165
+ for callback in callbacks:
166
+ callback()
167
+
168
+ # Lifecycle Methods
169
+
170
+ def component_will_mount(self) -> None:
171
+ """
172
+ Called before the component is mounted to the DOM
173
+
174
+ Deprecated: Use component_did_mount instead
175
+ """
176
+ pass
177
+
178
+ def component_did_mount(self) -> None:
179
+ """
180
+ Called after the component is mounted to the DOM
181
+
182
+ Use this for:
183
+ - Fetching data
184
+ - Setting up subscriptions
185
+ - DOM manipulation
186
+ """
187
+ pass
188
+
189
+ def component_will_receive_props(self, next_props: Dict[str, Any]) -> None:
190
+ """
191
+ Called before new props are received
192
+
193
+ Deprecated: Use get_derived_state_from_props instead
194
+
195
+ Args:
196
+ next_props: The new props
197
+ """
198
+ pass
199
+
200
+ @staticmethod
201
+ def get_derived_state_from_props(
202
+ props: Dict[str, Any],
203
+ state: Dict[str, Any]
204
+ ) -> Optional[Dict[str, Any]]:
205
+ """
206
+ Called before render with new props
207
+
208
+ Return an object to update state, or None to not update.
209
+
210
+ Args:
211
+ props: New props
212
+ state: Current state
213
+
214
+ Returns:
215
+ dict or None: State updates
216
+ """
217
+ return None
218
+
219
+ def should_component_update(
220
+ self,
221
+ next_props: Dict[str, Any],
222
+ next_state: Dict[str, Any]
223
+ ) -> bool:
224
+ """
225
+ Called before re-render to determine if update should proceed
226
+
227
+ Override to implement custom comparison logic.
228
+
229
+ Args:
230
+ next_props: New props
231
+ next_state: New state
232
+
233
+ Returns:
234
+ bool: True to proceed with update
235
+ """
236
+ return True
237
+
238
+ def component_will_update(
239
+ self,
240
+ next_props: Dict[str, Any],
241
+ next_state: Dict[str, Any]
242
+ ) -> None:
243
+ """
244
+ Called before re-render
245
+
246
+ Deprecated: Use component_did_update instead
247
+
248
+ Args:
249
+ next_props: New props
250
+ next_state: New state
251
+ """
252
+ pass
253
+
254
+ def component_did_update(
255
+ self,
256
+ prev_props: Dict[str, Any],
257
+ prev_state: Dict[str, Any]
258
+ ) -> None:
259
+ """
260
+ Called after re-render
261
+
262
+ Use for:
263
+ - DOM operations after update
264
+ - Network requests (compare props first)
265
+
266
+ Args:
267
+ prev_props: Previous props
268
+ prev_state: Previous state
269
+ """
270
+ pass
271
+
272
+ def component_will_unmount(self) -> None:
273
+ """
274
+ Called before the component is unmounted
275
+
276
+ Use for:
277
+ - Cleaning up subscriptions
278
+ - Canceling network requests
279
+ - Invalidating timers
280
+ """
281
+ pass
282
+
283
+ def component_did_catch(self, error: Exception, info: Dict[str, Any]) -> None:
284
+ """
285
+ Called when a child component throws an error
286
+
287
+ Use for logging error information.
288
+
289
+ Args:
290
+ error: The error that was thrown
291
+ info: Object with componentStack key
292
+ """
293
+ pass
294
+
295
+ @staticmethod
296
+ def get_derived_state_from_error(error: Exception) -> Optional[Dict[str, Any]]:
297
+ """
298
+ Called when a child component throws an error
299
+
300
+ Return an object to update state.
301
+
302
+ Args:
303
+ error: The error that was thrown
304
+
305
+ Returns:
306
+ dict or None: State updates
307
+ """
308
+ return None
309
+
310
+ def __repr__(self) -> str:
311
+ return f"<{self.__class__.__name__}>"
312
+
313
+
314
+ class PureComponent(Component):
315
+ """
316
+ Component that implements should_component_update with shallow comparison
317
+
318
+ Use PureComponent when you want to prevent unnecessary re-renders
319
+ when props and state haven't changed.
320
+
321
+ Example:
322
+ class UserAvatar(PureComponent):
323
+ def render(self):
324
+ return h('img', {
325
+ 'src': self.props['user']['avatar_url'],
326
+ 'alt': self.props['user']['name']
327
+ })
328
+ """
329
+
330
+ def should_component_update(
331
+ self,
332
+ next_props: Dict[str, Any],
333
+ next_state: Dict[str, Any]
334
+ ) -> bool:
335
+ """
336
+ Only re-render if props or state have changed (shallow comparison)
337
+
338
+ Args:
339
+ next_props: New props
340
+ next_state: New state
341
+
342
+ Returns:
343
+ bool: True if props or state changed
344
+ """
345
+ return (
346
+ not shallow_equal(self.props, next_props) or
347
+ not shallow_equal(self.state, next_state)
348
+ )
349
+
350
+
351
+ def shallow_equal(obj1: Dict[str, Any], obj2: Dict[str, Any]) -> bool:
352
+ """
353
+ Perform shallow comparison of two objects
354
+
355
+ Args:
356
+ obj1: First object
357
+ obj2: Second object
358
+
359
+ Returns:
360
+ bool: True if objects are shallowly equal
361
+ """
362
+ if obj1 is obj2:
363
+ return True
364
+
365
+ if len(obj1) != len(obj2):
366
+ return False
367
+
368
+ for key in obj1:
369
+ if key not in obj2 or obj1[key] != obj2[key]:
370
+ return False
371
+
372
+ return True
@@ -0,0 +1,173 @@
1
+ """
2
+ Context API Module
3
+ ==================
4
+
5
+ This module implements the Context API for sharing state across components
6
+ without prop drilling.
7
+ """
8
+
9
+ from typing import Any, Callable, Dict, List, Optional
10
+ import uuid
11
+
12
+
13
+ class Context:
14
+ """
15
+ Context object for sharing state
16
+
17
+ Created by create_context() function.
18
+
19
+ Attributes:
20
+ _id: Unique identifier for this context
21
+ _default_value: Default value when no Provider is found
22
+ _providers: Stack of provider values
23
+ """
24
+
25
+ def __init__(self, default_value: Any = None):
26
+ self._id = f"context-{uuid.uuid4().hex[:8]}"
27
+ self._default_value = default_value
28
+ self._providers: Dict[int, Any] = {}
29
+ self._provider_stack: List[int] = []
30
+
31
+ def _get_value(self) -> Any:
32
+ """Get current context value"""
33
+ if self._provider_stack:
34
+ provider_id = self._provider_stack[-1]
35
+ return self._providers.get(provider_id, self._default_value)
36
+ return self._default_value
37
+
38
+ def _push_provider(self, provider_id: int, value: Any) -> None:
39
+ """Push a provider value onto the stack"""
40
+ self._providers[provider_id] = value
41
+ self._provider_stack.append(provider_id)
42
+
43
+ def _pop_provider(self, provider_id: int) -> None:
44
+ """Pop a provider value from the stack"""
45
+ if provider_id in self._providers:
46
+ del self._providers[provider_id]
47
+ if provider_id in self._provider_stack:
48
+ self._provider_stack.remove(provider_id)
49
+
50
+ class Provider:
51
+ """
52
+ Provider component for context
53
+
54
+ Example:
55
+ h(ThemeContext.Provider, {'value': 'dark'},
56
+ h(App, None)
57
+ )
58
+ """
59
+
60
+ def __init__(self, context: 'Context', props: Dict[str, Any]):
61
+ self.context = context
62
+ self.props = props
63
+ self._id = id(self)
64
+ self._children = props.get('children', [])
65
+
66
+ def render(self) -> Any:
67
+ """Provider doesn't render anything, just provides value"""
68
+ return self._children
69
+
70
+ def __enter__(self):
71
+ """Context manager support"""
72
+ value = self.props.get('value', self.context._default_value)
73
+ self.context._push_provider(self._id, value)
74
+ return self
75
+
76
+ def __exit__(self, *args):
77
+ """Context manager support"""
78
+ self.context._pop_provider(self._id)
79
+
80
+ class Consumer:
81
+ """
82
+ Consumer component for context
83
+
84
+ Example:
85
+ h(ThemeContext.Consumer, None,
86
+ lambda theme: h('div', None, f"Theme: {theme}")
87
+ )
88
+ """
89
+
90
+ def __init__(self, context: 'Context', props: Dict[str, Any]):
91
+ self.context = context
92
+ self.props = props
93
+
94
+ def render(self) -> Any:
95
+ """Call children function with context value"""
96
+ children = self.props.get('children')
97
+ if callable(children):
98
+ return children(self.context._get_value())
99
+ return children
100
+
101
+
102
+ def create_context(default_value: Any = None) -> Context:
103
+ """
104
+ Create a new Context object
105
+
106
+ Args:
107
+ default_value: Default value when no Provider is found
108
+
109
+ Returns:
110
+ Context: The created context
111
+
112
+ Example:
113
+ ThemeContext = create_context('light')
114
+
115
+ def App():
116
+ return h(ThemeContext.Provider, {'value': 'dark'},
117
+ h(Toolbar, None)
118
+ )
119
+
120
+ def Toolbar():
121
+ theme = use_context(ThemeContext)
122
+ return h('div', {'className': f"toolbar-{theme}"}, '...')
123
+ """
124
+ return Context(default_value)
125
+
126
+
127
+ def use_context(context: Context) -> Any:
128
+ """
129
+ Hook to consume context value
130
+
131
+ This is a re-export from hooks.py for convenience.
132
+
133
+ Args:
134
+ context: Context object created by create_context()
135
+
136
+ Returns:
137
+ Current context value
138
+ """
139
+ from .hooks import use_context as _use_context
140
+ return _use_context(context)
141
+
142
+
143
+ class ContextManager:
144
+ """
145
+ Manager for all contexts in the application
146
+
147
+ Handles nested providers and context updates.
148
+ """
149
+
150
+ def __init__(self):
151
+ self._contexts: Dict[str, Context] = {}
152
+
153
+ def register(self, context: Context) -> None:
154
+ """Register a context"""
155
+ self._contexts[context._id] = context
156
+
157
+ def unregister(self, context: Context) -> None:
158
+ """Unregister a context"""
159
+ if context._id in self._contexts:
160
+ del self._contexts[context._id]
161
+
162
+ def get_context(self, context_id: str) -> Optional[Context]:
163
+ """Get a context by ID"""
164
+ return self._contexts.get(context_id)
165
+
166
+
167
+ # Global context manager
168
+ _context_manager = ContextManager()
169
+
170
+
171
+ def get_context_manager() -> ContextManager:
172
+ """Get the global context manager"""
173
+ return _context_manager