pythonnative 0.7.0__py3-none-any.whl → 0.8.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.
- pythonnative/__init__.py +22 -1
- pythonnative/components.py +78 -21
- pythonnative/hooks.py +135 -29
- pythonnative/hot_reload.py +2 -2
- pythonnative/native_views/__init__.py +87 -0
- pythonnative/native_views/android.py +832 -0
- pythonnative/native_views/base.py +150 -0
- pythonnative/native_views/ios.py +777 -0
- pythonnative/navigation.py +571 -0
- pythonnative/page.py +61 -16
- pythonnative/reconciler.py +89 -1
- {pythonnative-0.7.0.dist-info → pythonnative-0.8.0.dist-info}/METADATA +1 -1
- {pythonnative-0.7.0.dist-info → pythonnative-0.8.0.dist-info}/RECORD +17 -13
- pythonnative/native_views.py +0 -1404
- {pythonnative-0.7.0.dist-info → pythonnative-0.8.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.7.0.dist-info → pythonnative-0.8.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.7.0.dist-info → pythonnative-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.7.0.dist-info → pythonnative-0.8.0.dist-info}/top_level.txt +0 -0
pythonnative/reconciler.py
CHANGED
|
@@ -11,8 +11,12 @@ Supports:
|
|
|
11
11
|
``@component``). Their hook state is preserved across renders.
|
|
12
12
|
- **Provider elements** (type ``"__Provider__"``), which push/pop
|
|
13
13
|
context values during tree traversal.
|
|
14
|
+
- **Error boundary elements** (type ``"__ErrorBoundary__"``), which
|
|
15
|
+
catch exceptions in child subtrees and render a fallback.
|
|
14
16
|
- **Key-based child reconciliation** for stable identity across
|
|
15
17
|
re-renders.
|
|
18
|
+
- **Post-render effect flushing**: after each mount or reconcile pass,
|
|
19
|
+
all queued effects are executed so they see the committed native tree.
|
|
16
20
|
"""
|
|
17
21
|
|
|
18
22
|
from typing import Any, List, Optional
|
|
@@ -36,6 +40,10 @@ class VNode:
|
|
|
36
40
|
class Reconciler:
|
|
37
41
|
"""Create, diff, and patch native view trees from Element descriptors.
|
|
38
42
|
|
|
43
|
+
After each ``mount`` or ``reconcile`` call the reconciler walks the
|
|
44
|
+
committed tree and flushes all pending effects so that effect
|
|
45
|
+
callbacks run **after** native mutations are applied.
|
|
46
|
+
|
|
39
47
|
Parameters
|
|
40
48
|
----------
|
|
41
49
|
backend:
|
|
@@ -56,6 +64,7 @@ class Reconciler:
|
|
|
56
64
|
def mount(self, element: Element) -> Any:
|
|
57
65
|
"""Build native views from *element* and return the root native view."""
|
|
58
66
|
self._tree = self._create_tree(element)
|
|
67
|
+
self._flush_effects()
|
|
59
68
|
return self._tree.native_view
|
|
60
69
|
|
|
61
70
|
def reconcile(self, new_element: Element) -> Any:
|
|
@@ -65,11 +74,28 @@ class Reconciler:
|
|
|
65
74
|
"""
|
|
66
75
|
if self._tree is None:
|
|
67
76
|
self._tree = self._create_tree(new_element)
|
|
77
|
+
self._flush_effects()
|
|
68
78
|
return self._tree.native_view
|
|
69
79
|
|
|
70
80
|
self._tree = self._reconcile_node(self._tree, new_element)
|
|
81
|
+
self._flush_effects()
|
|
71
82
|
return self._tree.native_view
|
|
72
83
|
|
|
84
|
+
# ------------------------------------------------------------------
|
|
85
|
+
# Effect flushing
|
|
86
|
+
# ------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def _flush_effects(self) -> None:
|
|
89
|
+
"""Walk the committed tree and flush pending effects (depth-first)."""
|
|
90
|
+
if self._tree is not None:
|
|
91
|
+
self._flush_tree_effects(self._tree)
|
|
92
|
+
|
|
93
|
+
def _flush_tree_effects(self, node: VNode) -> None:
|
|
94
|
+
for child in node.children:
|
|
95
|
+
self._flush_tree_effects(child)
|
|
96
|
+
if node.hook_state is not None:
|
|
97
|
+
node.hook_state.flush_pending_effects()
|
|
98
|
+
|
|
73
99
|
# ------------------------------------------------------------------
|
|
74
100
|
# Internal helpers
|
|
75
101
|
# ------------------------------------------------------------------
|
|
@@ -87,6 +113,10 @@ class Reconciler:
|
|
|
87
113
|
children = [child_node] if child_node else []
|
|
88
114
|
return VNode(element, native_view, children)
|
|
89
115
|
|
|
116
|
+
# Error boundary: catch exceptions in the child subtree
|
|
117
|
+
if element.type == "__ErrorBoundary__":
|
|
118
|
+
return self._create_error_boundary(element)
|
|
119
|
+
|
|
90
120
|
# Function component: call with hook context
|
|
91
121
|
if callable(element.type):
|
|
92
122
|
from .hooks import HookState, _set_hook_state
|
|
@@ -114,6 +144,20 @@ class Reconciler:
|
|
|
114
144
|
children.append(child_node)
|
|
115
145
|
return VNode(element, native_view, children)
|
|
116
146
|
|
|
147
|
+
def _create_error_boundary(self, element: Element) -> VNode:
|
|
148
|
+
fallback_fn = element.props.get("__fallback__")
|
|
149
|
+
try:
|
|
150
|
+
child_node = self._create_tree(element.children[0]) if element.children else None
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
if fallback_fn is not None:
|
|
153
|
+
fallback_el = fallback_fn(exc) if callable(fallback_fn) else fallback_fn
|
|
154
|
+
child_node = self._create_tree(fallback_el)
|
|
155
|
+
else:
|
|
156
|
+
raise
|
|
157
|
+
native_view = child_node.native_view if child_node else None
|
|
158
|
+
children = [child_node] if child_node else []
|
|
159
|
+
return VNode(element, native_view, children)
|
|
160
|
+
|
|
117
161
|
def _reconcile_node(self, old: VNode, new_el: Element) -> VNode:
|
|
118
162
|
if not self._same_type(old.element, new_el):
|
|
119
163
|
new_node = self._create_tree(new_el)
|
|
@@ -138,6 +182,10 @@ class Reconciler:
|
|
|
138
182
|
old.element = new_el
|
|
139
183
|
return old
|
|
140
184
|
|
|
185
|
+
# Error boundary
|
|
186
|
+
if new_el.type == "__ErrorBoundary__":
|
|
187
|
+
return self._reconcile_error_boundary(old, new_el)
|
|
188
|
+
|
|
141
189
|
# Function component
|
|
142
190
|
if callable(new_el.type):
|
|
143
191
|
from .hooks import _set_hook_state
|
|
@@ -175,10 +223,34 @@ class Reconciler:
|
|
|
175
223
|
old.element = new_el
|
|
176
224
|
return old
|
|
177
225
|
|
|
226
|
+
def _reconcile_error_boundary(self, old: VNode, new_el: Element) -> VNode:
|
|
227
|
+
fallback_fn = new_el.props.get("__fallback__")
|
|
228
|
+
try:
|
|
229
|
+
if old.children and new_el.children:
|
|
230
|
+
child = self._reconcile_node(old.children[0], new_el.children[0])
|
|
231
|
+
old.children = [child]
|
|
232
|
+
old.native_view = child.native_view
|
|
233
|
+
elif new_el.children:
|
|
234
|
+
child = self._create_tree(new_el.children[0])
|
|
235
|
+
old.children = [child]
|
|
236
|
+
old.native_view = child.native_view
|
|
237
|
+
except Exception as exc:
|
|
238
|
+
for c in old.children:
|
|
239
|
+
self._destroy_tree(c)
|
|
240
|
+
if fallback_fn is not None:
|
|
241
|
+
fallback_el = fallback_fn(exc) if callable(fallback_fn) else fallback_fn
|
|
242
|
+
child = self._create_tree(fallback_el)
|
|
243
|
+
old.children = [child]
|
|
244
|
+
old.native_view = child.native_view
|
|
245
|
+
else:
|
|
246
|
+
raise
|
|
247
|
+
old.element = new_el
|
|
248
|
+
return old
|
|
249
|
+
|
|
178
250
|
def _reconcile_children(self, parent: VNode, new_children: List[Element]) -> None:
|
|
179
251
|
old_children = parent.children
|
|
180
252
|
parent_type = parent.element.type
|
|
181
|
-
is_native = isinstance(parent_type, str) and parent_type
|
|
253
|
+
is_native = isinstance(parent_type, str) and parent_type not in ("__Provider__", "__ErrorBoundary__")
|
|
182
254
|
|
|
183
255
|
old_by_key: dict = {}
|
|
184
256
|
old_unkeyed: list = []
|
|
@@ -215,7 +287,11 @@ class Reconciler:
|
|
|
215
287
|
self.backend.insert_child(parent.native_view, node.native_view, parent_type, i)
|
|
216
288
|
new_child_nodes.append(node)
|
|
217
289
|
else:
|
|
290
|
+
old_native = matched.native_view
|
|
218
291
|
updated = self._reconcile_node(matched, new_el)
|
|
292
|
+
if is_native and updated.native_view is not old_native:
|
|
293
|
+
self.backend.remove_child(parent.native_view, old_native, parent_type)
|
|
294
|
+
self.backend.insert_child(parent.native_view, updated.native_view, parent_type, i)
|
|
219
295
|
new_child_nodes.append(updated)
|
|
220
296
|
|
|
221
297
|
# Destroy unused old nodes
|
|
@@ -229,6 +305,18 @@ class Reconciler:
|
|
|
229
305
|
self.backend.remove_child(parent.native_view, node.native_view, parent_type)
|
|
230
306
|
self._destroy_tree(node)
|
|
231
307
|
|
|
308
|
+
# Reorder native children when keyed children changed positions.
|
|
309
|
+
# Without this, native sibling order drifts from the logical tree
|
|
310
|
+
# when keyed children swap positions across reconcile passes.
|
|
311
|
+
if is_native and used_keyed:
|
|
312
|
+
old_key_order = [c.element.key for c in old_children if c.element.key in used_keyed]
|
|
313
|
+
new_key_order = [n.element.key for n in new_child_nodes if n.element.key in used_keyed]
|
|
314
|
+
if old_key_order != new_key_order:
|
|
315
|
+
for node in new_child_nodes:
|
|
316
|
+
self.backend.remove_child(parent.native_view, node.native_view, parent_type)
|
|
317
|
+
for node in new_child_nodes:
|
|
318
|
+
self.backend.add_child(parent.native_view, node.native_view, parent_type)
|
|
319
|
+
|
|
232
320
|
parent.children = new_child_nodes
|
|
233
321
|
|
|
234
322
|
def _destroy_tree(self, node: VNode) -> None:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
pythonnative/__init__.py,sha256=
|
|
2
|
-
pythonnative/components.py,sha256=
|
|
1
|
+
pythonnative/__init__.py,sha256=PJ5VzzASR3xUdgP1iPlGtwXaxzNmC-c_UlJeKu0mCBQ,2038
|
|
2
|
+
pythonnative/components.py,sha256=zF24vXM6halq-fweLtLHxZrmBrUKCa9c44hf9Bych-Y,12767
|
|
3
3
|
pythonnative/element.py,sha256=RBUsXzzzM7KdK-NqMD-InVPKdAb8XJ0h0VpI2rwsfHs,1795
|
|
4
|
-
pythonnative/hooks.py,sha256=
|
|
5
|
-
pythonnative/hot_reload.py,sha256=
|
|
6
|
-
pythonnative/
|
|
7
|
-
pythonnative/page.py,sha256=
|
|
8
|
-
pythonnative/reconciler.py,sha256=
|
|
4
|
+
pythonnative/hooks.py,sha256=bqnJvEpbYSlbwkBKphT5lLEXQ1mO6AlO4r9SCTBlu7w,13768
|
|
5
|
+
pythonnative/hot_reload.py,sha256=dtppJaQI6Rl7muCTMgLjFNsD4_F4yYnkEpliCLaaWm8,4508
|
|
6
|
+
pythonnative/navigation.py,sha256=dDY9qBPLJKr2NnvdIDs8Xy-6kFesQNCEaLjUDT-0_l8,19042
|
|
7
|
+
pythonnative/page.py,sha256=ya-UUk_0_lO2YLx3UmBjtPL0j1YXItzP3VhPIloRhXQ,16168
|
|
8
|
+
pythonnative/reconciler.py,sha256=EuB8InxqlbZpZ0qI8hSjwNQsS-lsvh0vdqGzTnmY1M8,14133
|
|
9
9
|
pythonnative/style.py,sha256=NG58FSJCBTBBWRrDOyUiHZsTxQDA3jg2PSOcCsU2F5g,3882
|
|
10
10
|
pythonnative/utils.py,sha256=IqR_GYknveM_NfAblcaizg9S66hCZfrfiH08HzpOc-4,2537
|
|
11
11
|
pythonnative/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -15,6 +15,10 @@ pythonnative/native_modules/camera.py,sha256=b3UErkABhBm2nQ-e72lEeFOgXoVDY6245cV
|
|
|
15
15
|
pythonnative/native_modules/file_system.py,sha256=2fvYsIboCtjEyxmVeV72glOjtc9fJN3jTM6uaccru6E,4478
|
|
16
16
|
pythonnative/native_modules/location.py,sha256=bMSNbtG60hAorKXh4RR2vybX88M_BwHkCpTprRbMsZU,1883
|
|
17
17
|
pythonnative/native_modules/notifications.py,sha256=g-zT1GD-ojPsLN5eGXWkyNHTncSxdiEYtjllCXlsBSc,5377
|
|
18
|
+
pythonnative/native_views/__init__.py,sha256=0weXhCaDT79sez9Q1JibXSgouRVB1tjucFibhbPQeYc,2984
|
|
19
|
+
pythonnative/native_views/android.py,sha256=59_VzpGlADHFrY2cG8llVn4sDmPkUSGyUVKbyvfkKiU,32686
|
|
20
|
+
pythonnative/native_views/base.py,sha256=IS4_AQd4WsHgn8fka8np9zpaKdAOn1q0Zo09a8wYOtE,4229
|
|
21
|
+
pythonnative/native_views/ios.py,sha256=G5bQz57v5WSw5-wNmpcrcdX3rb7isb126la7UlveZLA,30087
|
|
18
22
|
pythonnative/templates/android_template/build.gradle,sha256=4gE6CRS6RuBu9kp-_e_uYYU9mBgHVZrqQg9caSxgyuc,352
|
|
19
23
|
pythonnative/templates/android_template/gradle.properties,sha256=REPaKLRfQiiVfIV8wYmgwzPWvF1f3bhh_kAMV9p4HME,1358
|
|
20
24
|
pythonnative/templates/android_template/gradlew,sha256=YxNShxF6Hm0SyEWA8fScYdG6AiGOzShmBgXpf5dufWU,5766
|
|
@@ -66,9 +70,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
|
|
|
66
70
|
pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
|
|
67
71
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=l2Pwa50F_rv-qPu2go6e4bQernM6PTQJeNPFl_c4ivY,1387
|
|
68
72
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
|
|
69
|
-
pythonnative-0.
|
|
70
|
-
pythonnative-0.
|
|
71
|
-
pythonnative-0.
|
|
72
|
-
pythonnative-0.
|
|
73
|
-
pythonnative-0.
|
|
74
|
-
pythonnative-0.
|
|
73
|
+
pythonnative-0.8.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
|
|
74
|
+
pythonnative-0.8.0.dist-info/METADATA,sha256=FZB-auwBCmhEBCt3nKpY7ZSNN3mF6_kbDLjVNWwfBMw,6692
|
|
75
|
+
pythonnative-0.8.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
76
|
+
pythonnative-0.8.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
|
|
77
|
+
pythonnative-0.8.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
|
|
78
|
+
pythonnative-0.8.0.dist-info/RECORD,,
|