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/element.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Virtual DOM Element Module
|
|
3
|
+
==========================
|
|
4
|
+
|
|
5
|
+
This module defines VNode (Virtual Node) and the h() function (hyperscript)
|
|
6
|
+
for creating virtual DOM elements in PyReact.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .component import Component
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VNode:
|
|
16
|
+
"""
|
|
17
|
+
Virtual DOM Node
|
|
18
|
+
|
|
19
|
+
Represents a node in the virtual DOM tree. Can be:
|
|
20
|
+
- An HTML element ('div', 'span', etc.)
|
|
21
|
+
- A Component (function or class)
|
|
22
|
+
- A text node (string)
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
type: Element type (string tag name or Component class/callable)
|
|
26
|
+
props: Dictionary of properties/attributes
|
|
27
|
+
children: List of child VNodes or strings
|
|
28
|
+
key: Optional key for list reconciliation
|
|
29
|
+
ref: Optional reference to DOM node or component instance
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
type: Union[str, Callable, type],
|
|
35
|
+
props: Optional[Dict[str, Any]] = None,
|
|
36
|
+
children: Optional[List[Union['VNode', str]]] = None,
|
|
37
|
+
key: Optional[Union[str, int]] = None,
|
|
38
|
+
ref: Optional[Any] = None
|
|
39
|
+
):
|
|
40
|
+
self.type = type
|
|
41
|
+
self.props = props or {}
|
|
42
|
+
self.children = children or []
|
|
43
|
+
self.key = key
|
|
44
|
+
self.ref = ref
|
|
45
|
+
|
|
46
|
+
# Internal properties (managed by renderer)
|
|
47
|
+
self._dom_node: Optional[Any] = None
|
|
48
|
+
self._parent: Optional['VNode'] = None
|
|
49
|
+
self._component_instance: Optional['Component'] = None
|
|
50
|
+
self._hooks: List[Any] = []
|
|
51
|
+
self._hook_index: int = 0
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
type_name = self.type if isinstance(self.type, str) else self.type.__name__
|
|
55
|
+
return f"VNode(type={type_name!r}, key={self.key!r}, children={len(self.children)})"
|
|
56
|
+
|
|
57
|
+
def __eq__(self, other: Any) -> bool:
|
|
58
|
+
if not isinstance(other, VNode):
|
|
59
|
+
return False
|
|
60
|
+
return (
|
|
61
|
+
self.type == other.type and
|
|
62
|
+
self.key == other.key and
|
|
63
|
+
self.props == other.props
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def clone(self) -> 'VNode':
|
|
67
|
+
"""Create a shallow clone of this VNode"""
|
|
68
|
+
return VNode(
|
|
69
|
+
type=self.type,
|
|
70
|
+
props=self.props.copy(),
|
|
71
|
+
children=self.children.copy(),
|
|
72
|
+
key=self.key,
|
|
73
|
+
ref=self.ref
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def h(
|
|
78
|
+
type: Union[str, Callable, type],
|
|
79
|
+
props: Optional[Dict[str, Any]] = None,
|
|
80
|
+
*children: Union['VNode', str, List[Union['VNode', str]]]
|
|
81
|
+
) -> VNode:
|
|
82
|
+
"""
|
|
83
|
+
Create a virtual DOM element (hyperscript function)
|
|
84
|
+
|
|
85
|
+
This is the primary way to create VNodes in PyReact, similar to
|
|
86
|
+
React.createElement().
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
type: Element type - HTML tag string ('div', 'span') or Component
|
|
90
|
+
props: Dictionary of properties/attributes (optional)
|
|
91
|
+
*children: Child elements (VNodes, strings, or lists)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
VNode: The created virtual node
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
>>> h('div', {'id': 'app'}, 'Hello World')
|
|
98
|
+
VNode(type='div', key=None, children=1)
|
|
99
|
+
|
|
100
|
+
>>> h('div', None, h('span', None, 'text'))
|
|
101
|
+
VNode(type='div', key=None, children=1)
|
|
102
|
+
|
|
103
|
+
>>> h(Counter, {'initialCount': 0})
|
|
104
|
+
VNode(type='Counter', key=None, children=0)
|
|
105
|
+
"""
|
|
106
|
+
# Flatten children (handle nested lists)
|
|
107
|
+
flat_children: List[Union[VNode, str]] = []
|
|
108
|
+
for child in children:
|
|
109
|
+
if isinstance(child, list):
|
|
110
|
+
flat_children.extend(_flatten_children(child))
|
|
111
|
+
elif child is not None:
|
|
112
|
+
flat_children.append(child)
|
|
113
|
+
|
|
114
|
+
# Extract special props
|
|
115
|
+
props = props.copy() if props else {}
|
|
116
|
+
key = props.pop('key', None)
|
|
117
|
+
ref = props.pop('ref', None)
|
|
118
|
+
|
|
119
|
+
return VNode(
|
|
120
|
+
type=type,
|
|
121
|
+
props=props,
|
|
122
|
+
children=flat_children,
|
|
123
|
+
key=key,
|
|
124
|
+
ref=ref
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _flatten_children(
|
|
129
|
+
children: List[Union[VNode, str, List]]
|
|
130
|
+
) -> List[Union[VNode, str]]:
|
|
131
|
+
"""Recursively flatten nested children lists"""
|
|
132
|
+
result: List[Union[VNode, str]] = []
|
|
133
|
+
for child in children:
|
|
134
|
+
if isinstance(child, list):
|
|
135
|
+
result.extend(_flatten_children(child))
|
|
136
|
+
elif child is not None:
|
|
137
|
+
result.append(child)
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def create_element(
|
|
142
|
+
type: Union[str, Callable, type],
|
|
143
|
+
props: Optional[Dict[str, Any]] = None,
|
|
144
|
+
*children: Union[VNode, str, List[Union[VNode, str]]]
|
|
145
|
+
) -> VNode:
|
|
146
|
+
"""
|
|
147
|
+
Alias for h() function
|
|
148
|
+
|
|
149
|
+
Provided for compatibility with React's createElement API.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
type: Element type
|
|
153
|
+
props: Properties/attributes
|
|
154
|
+
*children: Child elements
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
VNode: The created virtual node
|
|
158
|
+
"""
|
|
159
|
+
return h(type, props, *children)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def is_valid_element(element: Any) -> bool:
|
|
163
|
+
"""
|
|
164
|
+
Check if a value is a valid VNode
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
element: Value to check
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
bool: True if element is a valid VNode
|
|
171
|
+
"""
|
|
172
|
+
return isinstance(element, VNode)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def clone_element(
|
|
176
|
+
element: VNode,
|
|
177
|
+
props: Optional[Dict[str, Any]] = None,
|
|
178
|
+
*children: Union[VNode, str, List[Union[VNode, str]]]
|
|
179
|
+
) -> VNode:
|
|
180
|
+
"""
|
|
181
|
+
Clone and return a new VNode with optional new props and children
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
element: VNode to clone
|
|
185
|
+
props: New props to merge (optional)
|
|
186
|
+
*children: New children (optional)
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
VNode: Cloned element
|
|
190
|
+
"""
|
|
191
|
+
if not is_valid_element(element):
|
|
192
|
+
raise ValueError('clone_element requires a valid VNode')
|
|
193
|
+
|
|
194
|
+
# Merge props
|
|
195
|
+
new_props = element.props.copy()
|
|
196
|
+
if props:
|
|
197
|
+
new_props.update(props)
|
|
198
|
+
|
|
199
|
+
# Use new children if provided, otherwise keep original
|
|
200
|
+
new_children = list(children) if children else element.children.copy()
|
|
201
|
+
|
|
202
|
+
return VNode(
|
|
203
|
+
type=element.type,
|
|
204
|
+
props=new_props,
|
|
205
|
+
children=new_children,
|
|
206
|
+
key=props.get('key', element.key) if props else element.key,
|
|
207
|
+
ref=props.get('ref', element.ref) if props else element.ref
|
|
208
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error Boundary Module
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
This module implements Error Boundaries for catching errors
|
|
6
|
+
in child components.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, Optional
|
|
10
|
+
from .component import Component
|
|
11
|
+
from .element import VNode
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ErrorBoundary(Component):
|
|
15
|
+
"""
|
|
16
|
+
Error Boundary component
|
|
17
|
+
|
|
18
|
+
Catches errors in child components and displays a fallback UI.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
h(ErrorBoundary, {
|
|
22
|
+
'fallback': h('div', None, 'Something went wrong!')
|
|
23
|
+
},
|
|
24
|
+
h(RiskyComponent, None)
|
|
25
|
+
)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, props: Optional[Dict[str, Any]] = None):
|
|
29
|
+
super().__init__(props)
|
|
30
|
+
self.state = {
|
|
31
|
+
'has_error': False,
|
|
32
|
+
'error': None,
|
|
33
|
+
'error_info': None
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_derived_state_from_error(error: Exception) -> Dict[str, Any]:
|
|
38
|
+
"""
|
|
39
|
+
Update state when an error is thrown
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
error: The error that was thrown
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
dict: State updates
|
|
46
|
+
"""
|
|
47
|
+
return {
|
|
48
|
+
'has_error': True,
|
|
49
|
+
'error': error
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def component_did_catch(self, error: Exception, info: Dict[str, Any]) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Log error information
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
error: The error that was thrown
|
|
58
|
+
info: Object with componentStack key
|
|
59
|
+
"""
|
|
60
|
+
# Log error (could be sent to error reporting service)
|
|
61
|
+
print(f"ErrorBoundary caught an error: {error}")
|
|
62
|
+
if 'componentStack' in info:
|
|
63
|
+
print(f"Component stack: {info['componentStack']}")
|
|
64
|
+
|
|
65
|
+
def render(self) -> VNode:
|
|
66
|
+
"""Render fallback or children"""
|
|
67
|
+
if self.state['has_error']:
|
|
68
|
+
fallback = self.props.get('fallback')
|
|
69
|
+
if fallback:
|
|
70
|
+
return fallback
|
|
71
|
+
|
|
72
|
+
# Default fallback
|
|
73
|
+
return VNode('div', {'className': 'error-boundary'}, [
|
|
74
|
+
VNode('h2', None, ['Something went wrong.']),
|
|
75
|
+
VNode('p', None, [str(self.state['error'])])
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
return self.props.get('children', VNode('div', None, []))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def with_error_boundary(
|
|
82
|
+
component: Callable,
|
|
83
|
+
fallback: Optional[VNode] = None,
|
|
84
|
+
on_error: Optional[Callable[[Exception, Dict], None]] = None
|
|
85
|
+
) -> Callable:
|
|
86
|
+
"""
|
|
87
|
+
Higher-order component that wraps a component with an ErrorBoundary
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
component: Component to wrap
|
|
91
|
+
fallback: Fallback UI to show on error
|
|
92
|
+
on_error: Callback when error occurs
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Wrapped component
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
@with_error_boundary(fallback=h('div', None, 'Error!'))
|
|
99
|
+
def RiskyComponent(props):
|
|
100
|
+
# May throw an error
|
|
101
|
+
return h('div', None, risky_operation())
|
|
102
|
+
"""
|
|
103
|
+
class WrappedErrorBoundary(ErrorBoundary):
|
|
104
|
+
def component_did_catch(self, error, info):
|
|
105
|
+
super().component_did_catch(error, info)
|
|
106
|
+
if on_error:
|
|
107
|
+
on_error(error, info)
|
|
108
|
+
|
|
109
|
+
def wrapper(props):
|
|
110
|
+
return VNode(WrappedErrorBoundary, {
|
|
111
|
+
'fallback': fallback
|
|
112
|
+
}, [VNode(component, props)])
|
|
113
|
+
|
|
114
|
+
return wrapper
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ErrorInfo:
|
|
118
|
+
"""
|
|
119
|
+
Error information object passed to component_did_catch
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, component_stack: str = ''):
|
|
123
|
+
self.component_stack = component_stack
|
|
124
|
+
|
|
125
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
126
|
+
return {
|
|
127
|
+
'componentStack': self.component_stack
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def capture_error(error: Exception, component_stack: str = '') -> Dict[str, Any]:
|
|
132
|
+
"""
|
|
133
|
+
Capture error information
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
error: The error that was thrown
|
|
137
|
+
component_stack: Component stack trace
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict: Error information
|
|
141
|
+
"""
|
|
142
|
+
return {
|
|
143
|
+
'error': error,
|
|
144
|
+
'errorInfo': ErrorInfo(component_stack).to_dict()
|
|
145
|
+
}
|