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
pyreact/core/memo.py ADDED
@@ -0,0 +1,221 @@
1
+ """
2
+ Memo Module
3
+ ===========
4
+
5
+ This module implements memoization utilities for optimizing
6
+ component re-renders.
7
+ """
8
+
9
+ from typing import Any, Callable, Dict, List, Optional, Union
10
+ from functools import wraps
11
+ from .element import VNode
12
+
13
+
14
+ def memo(
15
+ component: Callable,
16
+ are_props_equal: Optional[Callable[[Dict, Dict], bool]] = None
17
+ ) -> Callable:
18
+ """
19
+ Memoize a component to prevent unnecessary re-renders
20
+
21
+ Similar to React.memo, wraps a component and only re-renders
22
+ when props change.
23
+
24
+ Args:
25
+ component: Component function to memoize
26
+ are_props_equal: Optional custom comparison function
27
+
28
+ Returns:
29
+ Memoized component
30
+
31
+ Example:
32
+ @memo
33
+ def ExpensiveComponent(props):
34
+ return h('div', None, expensive_computation(props['data']))
35
+
36
+ # With custom comparison
37
+ @memo(are_props_equal=lambda prev, next: prev['id'] == next['id'])
38
+ def UserAvatar(props):
39
+ return h('img', {'src': props['user']['avatar']})
40
+ """
41
+ @wraps(component)
42
+ def memoized(props: Dict[str, Any]) -> VNode:
43
+ # Check if we have previous props
44
+ if memoized._prev_props is not None:
45
+ # Compare props
46
+ if are_props_equal:
47
+ should_update = not are_props_equal(memoized._prev_props, props)
48
+ else:
49
+ should_update = not _shallow_equal(memoized._prev_props, props)
50
+
51
+ if not should_update:
52
+ return memoized._prev_result
53
+
54
+ # Render and cache
55
+ result = component(props)
56
+ memoized._prev_props = props.copy()
57
+ memoized._prev_result = result
58
+ return result
59
+
60
+ memoized._prev_props = None
61
+ memoized._prev_result = None
62
+ memoized._component = component
63
+
64
+ return memoized
65
+
66
+
67
+ def lazy(loader: Callable[[], Any]) -> Callable:
68
+ """
69
+ Lazy load a component
70
+
71
+ Returns a component that is loaded on demand.
72
+
73
+ Args:
74
+ loader: Async function that returns the component module
75
+
76
+ Returns:
77
+ Lazy component
78
+
79
+ Example:
80
+ HeavyComponent = lazy(lambda: import_module('./HeavyComponent.py'))
81
+
82
+ h(Suspense, {'fallback': h('div', None, 'Loading...')},
83
+ h(HeavyComponent, None)
84
+ )
85
+ """
86
+ from .component import Component
87
+
88
+ class LazyComponent(Component):
89
+ """Lazy-loaded component wrapper"""
90
+
91
+ def __init__(self, props):
92
+ super().__init__(props)
93
+ self.state = {
94
+ 'loaded': False,
95
+ 'component': None,
96
+ 'error': None
97
+ }
98
+
99
+ def component_did_mount(self):
100
+ """Load the component"""
101
+ import asyncio
102
+
103
+ async def load():
104
+ try:
105
+ module = await loader()
106
+ component = getattr(module, 'default', module)
107
+ self.set_state({
108
+ 'loaded': True,
109
+ 'component': component
110
+ })
111
+ except Exception as e:
112
+ self.set_state({'error': e})
113
+
114
+ try:
115
+ loop = asyncio.get_event_loop()
116
+ except RuntimeError:
117
+ loop = asyncio.new_event_loop()
118
+ asyncio.set_event_loop(loop)
119
+
120
+ loop.run_until_complete(load())
121
+
122
+ def render(self):
123
+ if self.state['error']:
124
+ return self.props.get('fallback', VNode('div', None, ['Error loading component']))
125
+
126
+ if not self.state['loaded']:
127
+ return self.props.get('loading', VNode('div', None, ['Loading...']))
128
+
129
+ Component = self.state['component']
130
+ return VNode(Component, self.props)
131
+
132
+ return LazyComponent
133
+
134
+
135
+ class Suspense:
136
+ """
137
+ Suspense boundary for handling loading states
138
+
139
+ Wraps components that may suspend (like lazy components)
140
+ and shows a fallback while loading.
141
+
142
+ Example:
143
+ h(Suspense, {'fallback': h('div', None, 'Loading...')},
144
+ h(LazyComponent, None)
145
+ )
146
+ """
147
+
148
+ def __init__(self, props: Dict[str, Any]):
149
+ self.props = props
150
+ self.fallback = props.get('fallback', VNode('div', None, ['Loading...']))
151
+ self.children = props.get('children', [])
152
+
153
+ def render(self) -> VNode:
154
+ """Render fallback or children"""
155
+ # In a real implementation, this would check if children are suspended
156
+ return self.children if self.children else self.fallback
157
+
158
+
159
+ def _shallow_equal(obj1: Dict[str, Any], obj2: Dict[str, Any]) -> bool:
160
+ """
161
+ Perform shallow comparison of two objects
162
+
163
+ Args:
164
+ obj1: First object
165
+ obj2: Second object
166
+
167
+ Returns:
168
+ bool: True if objects are shallowly equal
169
+ """
170
+ if obj1 is obj2:
171
+ return True
172
+
173
+ if len(obj1) != len(obj2):
174
+ return False
175
+
176
+ for key in obj1:
177
+ if key not in obj2 or obj1[key] != obj2[key]:
178
+ return False
179
+
180
+ return True
181
+
182
+
183
+ def are_props_shallow_equal(prev_props: Dict, next_props: Dict) -> bool:
184
+ """
185
+ Check if props are shallowly equal
186
+
187
+ Args:
188
+ prev_props: Previous props
189
+ next_props: Next props
190
+
191
+ Returns:
192
+ bool: True if props are shallowly equal
193
+ """
194
+ return _shallow_equal(prev_props, next_props)
195
+
196
+
197
+ def shallow_compare(
198
+ prev_props: Dict,
199
+ next_props: Dict,
200
+ prev_state: Optional[Dict] = None,
201
+ next_state: Optional[Dict] = None
202
+ ) -> bool:
203
+ """
204
+ Compare props and state for PureComponent
205
+
206
+ Args:
207
+ prev_props: Previous props
208
+ next_props: Next props
209
+ prev_state: Previous state (optional)
210
+ next_state: Next state (optional)
211
+
212
+ Returns:
213
+ bool: True if props and state are shallowly equal
214
+ """
215
+ props_equal = _shallow_equal(prev_props, next_props)
216
+
217
+ if prev_state is not None and next_state is not None:
218
+ state_equal = _shallow_equal(prev_state, next_state)
219
+ return props_equal and state_equal
220
+
221
+ return props_equal
pyreact/core/portal.py ADDED
@@ -0,0 +1,159 @@
1
+ """
2
+ Portal Module
3
+ =============
4
+
5
+ This module implements Portals for rendering children into a different
6
+ DOM node than their parent.
7
+ """
8
+
9
+ from typing import Any, Dict, Optional
10
+ from .element import VNode
11
+
12
+
13
+ class Portal:
14
+ """
15
+ Portal for rendering into a different DOM container
16
+
17
+ Portals provide a way to render children into a DOM node that exists
18
+ outside the parent component's DOM hierarchy.
19
+
20
+ Example:
21
+ def Modal(props):
22
+ return create_portal(
23
+ h('div', {'className': 'modal-overlay'},
24
+ h('div', {'className': 'modal-content'},
25
+ props['children']
26
+ )
27
+ ),
28
+ document.body
29
+ )
30
+ """
31
+
32
+ def __init__(self, children: Any, container: Any):
33
+ """
34
+ Initialize portal
35
+
36
+ Args:
37
+ children: VNode or list of VNodes to render
38
+ container: DOM node to render into
39
+ """
40
+ self.children = children
41
+ self.container = container
42
+ self._dom_nodes: list = []
43
+
44
+ def __repr__(self) -> str:
45
+ return f"Portal(container={self.container!r})"
46
+
47
+
48
+ def create_portal(children: Any, container: Any) -> Portal:
49
+ """
50
+ Create a portal to render children into a different container
51
+
52
+ Args:
53
+ children: VNode or list of VNodes to render
54
+ container: DOM node to render into
55
+
56
+ Returns:
57
+ Portal: A portal object
58
+
59
+ Example:
60
+ def Tooltip(props):
61
+ return create_portal(
62
+ h('div', {'className': 'tooltip'},
63
+ props['content']
64
+ ),
65
+ document.getElementById('tooltip-root')
66
+ )
67
+ """
68
+ return Portal(children, container)
69
+
70
+
71
+ def is_portal(element: Any) -> bool:
72
+ """
73
+ Check if an element is a portal
74
+
75
+ Args:
76
+ element: Element to check
77
+
78
+ Returns:
79
+ bool: True if element is a portal
80
+ """
81
+ return isinstance(element, Portal)
82
+
83
+
84
+ def unmount_portal(portal: Portal) -> None:
85
+ """
86
+ Unmount a portal's children
87
+
88
+ Args:
89
+ portal: Portal to unmount
90
+ """
91
+ for dom_node in portal._dom_nodes:
92
+ if dom_node and dom_node.parentNode:
93
+ dom_node.parentNode.removeChild(dom_node)
94
+ portal._dom_nodes.clear()
95
+
96
+
97
+ def render_portal(portal: Portal, renderer: Any) -> None:
98
+ """
99
+ Render a portal's children into its container
100
+
101
+ Args:
102
+ portal: Portal to render
103
+ renderer: Renderer instance
104
+ """
105
+ # Clear existing content
106
+ while portal.container.firstChild:
107
+ portal.container.removeChild(portal.container.firstChild)
108
+
109
+ # Render children
110
+ if isinstance(portal.children, VNode):
111
+ dom = renderer.create_dom(portal.children)
112
+ portal.container.appendChild(dom)
113
+ portal._dom_nodes.append(dom)
114
+ elif isinstance(portal.children, list):
115
+ for child in portal.children:
116
+ if isinstance(child, VNode):
117
+ dom = renderer.create_dom(child)
118
+ portal.container.appendChild(dom)
119
+ portal._dom_nodes.append(dom)
120
+
121
+
122
+ class PortalManager:
123
+ """
124
+ Manager for all portals in the application
125
+ """
126
+
127
+ def __init__(self):
128
+ self._portals: Dict[int, Portal] = {}
129
+
130
+ def register(self, portal: Portal) -> int:
131
+ """Register a portal"""
132
+ portal_id = id(portal)
133
+ self._portals[portal_id] = portal
134
+ return portal_id
135
+
136
+ def unregister(self, portal: Portal) -> None:
137
+ """Unregister a portal"""
138
+ portal_id = id(portal)
139
+ if portal_id in self._portals:
140
+ del self._portals[portal_id]
141
+
142
+ def get_portal(self, portal_id: int) -> Optional[Portal]:
143
+ """Get a portal by ID"""
144
+ return self._portals.get(portal_id)
145
+
146
+ def unmount_all(self) -> None:
147
+ """Unmount all portals"""
148
+ for portal in self._portals.values():
149
+ unmount_portal(portal)
150
+ self._portals.clear()
151
+
152
+
153
+ # Global portal manager
154
+ _portal_manager = PortalManager()
155
+
156
+
157
+ def get_portal_manager() -> PortalManager:
158
+ """Get the global portal manager"""
159
+ return _portal_manager