pythonnative 0.7.0__py3-none-any.whl → 0.9.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.
@@ -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 != "__Provider__"
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:
@@ -27,13 +27,18 @@ class ViewController: UIViewController {
27
27
  super.viewDidLoad()
28
28
  // Ensure a visible background when created programmatically (storyboards set this automatically)
29
29
  view.backgroundColor = .systemBackground
30
- NSLog("[PN][ViewController] viewDidLoad")
31
- if let bundleId = Bundle.main.bundleIdentifier {
32
- NSLog("[PN] Bundle Identifier: \(bundleId)")
33
- }
34
- NSLog("[PN] Bundle Path: \(Bundle.main.bundlePath)")
35
- NSLog("[PN] Resource Path: \(Bundle.main.resourcePath ?? "nil")")
36
- // Configure embedded Python if available in bundle
30
+
31
+ let firstInit = !ViewController.hasInitializedPython
32
+
33
+ // Signal to pythonnative that we're running on iOS. Read on the
34
+ // Python side (pythonnative.utils.IS_IOS) to gate iOS-only setup
35
+ // like sys.stdout redirection. Set before Python starts so it's
36
+ // visible to the very first import.
37
+ setenv("PN_PLATFORM", "ios", 1)
38
+
39
+ // Configure embedded Python if available in bundle. PYTHONHOME /
40
+ // PYTHONPATH only need to be set once per process, but setting them
41
+ // again is harmless and keeps the flow simple.
37
42
  if let resourcePath = Bundle.main.resourcePath {
38
43
  let pyStd = "\(resourcePath)/python-stdlib"
39
44
  let pyDyn = "\(resourcePath)/python-stdlib/lib-dynload"
@@ -44,8 +49,6 @@ class ViewController: UIViewController {
44
49
  }
45
50
  setenv("PYTHONHOME", pyStd, 1)
46
51
  setenv("PYTHONPATH", pyPath, 1)
47
- NSLog("[PN] Set PYTHONHOME=\(pyStd)")
48
- NSLog("[PN] Set PYTHONPATH=\(pyPath)")
49
52
  }
50
53
  #if canImport(PythonKit)
51
54
  // Ensure PythonKit knows where to load the Python library from when using an embedded framework.
@@ -53,34 +56,25 @@ class ViewController: UIViewController {
53
56
  let frameworkLib = "\(bundlePath)/Frameworks/Python.framework/Python"
54
57
  setenv("PYTHON_LIBRARY", frameworkLib, 1)
55
58
  if FileManager.default.fileExists(atPath: frameworkLib) {
56
- if !ViewController.hasInitializedPython {
57
- NSLog("[PN] Using embedded Python lib at: \(frameworkLib)")
59
+ if firstInit {
58
60
  PythonLibrary.useLibrary(at: frameworkLib)
59
61
  ViewController.hasInitializedPython = true
60
- } else {
61
- NSLog("[PN] Python library already initialized; skipping useLibrary")
62
62
  }
63
63
  pythonReady = true
64
64
  } else {
65
65
  NSLog("[PN] Embedded Python library not found at: \(frameworkLib)")
66
66
  }
67
67
  }
68
- NSLog("[PN] PythonKit available; attempting Python bootstrap")
69
68
  let sys = Python.import("sys")
70
- NSLog("[PN] Python version: \(sys.version)")
71
- NSLog("[PN] Initial sys.path: \(sys.path)")
69
+ if firstInit {
70
+ // One concise bootstrap line per process; per-page detail is left
71
+ // to Python-side print() statements streamed via pn run ios.
72
+ let shortVersion = "\(sys.version)".split(separator: "\n").first.map(String.init) ?? "\(sys.version)"
73
+ NSLog("[PN] Python \(shortVersion) initialized")
74
+ }
72
75
  if let resourcePath = Bundle.main.resourcePath {
73
76
  sys.path.append(resourcePath)
74
77
  sys.path.append("\(resourcePath)/app")
75
- NSLog("[PN] Updated sys.path: \(sys.path)")
76
- // List bundled resources to verify Python files are present
77
- let fm = FileManager.default
78
- let appDir = "\(resourcePath)/app"
79
- if let entries = try? fm.contentsOfDirectory(atPath: appDir) {
80
- NSLog("[PN] Contents of /app in bundle: \(entries)")
81
- } else {
82
- NSLog("[PN] Could not list contents of \(appDir).")
83
- }
84
78
  }
85
79
  // Determine which Python page to load
86
80
  let pagePath: String = requestedPagePath ?? "app.main_page.MainPage"
pythonnative/utils.py CHANGED
@@ -5,6 +5,7 @@ importing platform-specific packages at module level.
5
5
  """
6
6
 
7
7
  import os
8
+ import sys
8
9
  from typing import Any, Optional
9
10
 
10
11
  # ======================================================================
@@ -12,6 +13,7 @@ from typing import Any, Optional
12
13
  # ======================================================================
13
14
 
14
15
  _is_android: Optional[bool] = None
16
+ _is_ios: Optional[bool] = None
15
17
 
16
18
 
17
19
  def _detect_android() -> bool:
@@ -27,10 +29,40 @@ def _detect_android() -> bool:
27
29
  return False
28
30
 
29
31
 
32
+ def _detect_ios() -> bool:
33
+ """Detect whether we're running inside an iOS app bundle.
34
+
35
+ Signals, in priority order:
36
+
37
+ - Explicit ``PN_PLATFORM=ios`` env var (set by the iOS template's
38
+ ``ViewController.swift`` before Python starts). This is the
39
+ canonical signal and survives even on hosts where ``sys.platform``
40
+ is generic ``darwin``.
41
+ - ``sys.platform == "ios"`` (CPython 3.13+ native iOS builds).
42
+ - ``/CoreSimulator/Devices/`` in ``$HOME`` (iOS Simulator fallback
43
+ if the template signal is missing for some reason).
44
+
45
+ Crucially, having ``rubicon-objc`` importable is *not* enough:
46
+ developers frequently install it on macOS via the ``[ios]`` extra,
47
+ and treating that as iOS would cause subtle side effects
48
+ (e.g. stdout redirection) on desktop machines.
49
+ """
50
+ if os.environ.get("PN_PLATFORM") == "ios":
51
+ return True
52
+ if sys.platform == "ios":
53
+ return True
54
+ home = os.environ.get("HOME", "")
55
+ if "/CoreSimulator/Devices/" in home:
56
+ return True
57
+ return False
58
+
59
+
30
60
  def _ensure_platform_detection() -> None:
31
- global _is_android
61
+ global _is_android, _is_ios
32
62
  if _is_android is None:
33
63
  _is_android = _detect_android()
64
+ if _is_ios is None:
65
+ _is_ios = (not _is_android) and _detect_ios()
34
66
 
35
67
 
36
68
  def _get_is_android() -> bool:
@@ -39,7 +71,14 @@ def _get_is_android() -> bool:
39
71
  return _is_android
40
72
 
41
73
 
74
+ def _get_is_ios() -> bool:
75
+ _ensure_platform_detection()
76
+ assert _is_ios is not None
77
+ return _is_ios
78
+
79
+
42
80
  IS_ANDROID: bool = _get_is_android()
81
+ IS_IOS: bool = _get_is_ios()
43
82
 
44
83
  # ======================================================================
45
84
  # Android context management
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -1,20 +1,25 @@
1
- pythonnative/__init__.py,sha256=jhPsaI_qyTEQ_YMT5pJumYOQKYamfHbPalWd5VbJ5vY,1575
2
- pythonnative/components.py,sha256=dvLaXmCFU8O6bnYdudwBVsvad1DkwvmMJoTZRAZi540,10534
1
+ pythonnative/__init__.py,sha256=zKplWL38v3kFMeQ8Q2VqnjF-nAM5vx3EW8UIKqJddDo,2038
2
+ pythonnative/_ios_log.py,sha256=dkTSUeOF9xRQjgxMYdH6EohjzjmeInwOfr-_0tkBmpE,2639
3
+ pythonnative/components.py,sha256=zF24vXM6halq-fweLtLHxZrmBrUKCa9c44hf9Bych-Y,12767
3
4
  pythonnative/element.py,sha256=RBUsXzzzM7KdK-NqMD-InVPKdAb8XJ0h0VpI2rwsfHs,1795
4
- pythonnative/hooks.py,sha256=UMoNTFoMKON4uJpUU9WHBv60tR2GUvx4Q_OgfG24Xcg,10361
5
- pythonnative/hot_reload.py,sha256=GoooBkRm2_zu_bQGOsOvUx-x9GdFcvRwSWEkhltFg2Y,4498
6
- pythonnative/native_views.py,sha256=VuqseL-QRHsEoGe3j3Ubh-iPo0dAkx-rcieZqWlt5ho,62037
7
- pythonnative/page.py,sha256=Pjf2o-0ufmn_6q2hyRZ3zrnHWkDTm2PD6rI4niQTfS8,14803
8
- pythonnative/reconciler.py,sha256=4Ah8NtYawZH9ZHXoMMjMTfdrTkZ0b4OT3e_85WLTrbU,9850
5
+ pythonnative/hooks.py,sha256=bqnJvEpbYSlbwkBKphT5lLEXQ1mO6AlO4r9SCTBlu7w,13768
6
+ pythonnative/hot_reload.py,sha256=dtppJaQI6Rl7muCTMgLjFNsD4_F4yYnkEpliCLaaWm8,4508
7
+ pythonnative/navigation.py,sha256=dDY9qBPLJKr2NnvdIDs8Xy-6kFesQNCEaLjUDT-0_l8,19042
8
+ pythonnative/page.py,sha256=J1mC6jVAu6IjFSwDGPU3cMfiXLPHoW2RbyZ1kj3OSfk,16814
9
+ pythonnative/reconciler.py,sha256=EuB8InxqlbZpZ0qI8hSjwNQsS-lsvh0vdqGzTnmY1M8,14133
9
10
  pythonnative/style.py,sha256=NG58FSJCBTBBWRrDOyUiHZsTxQDA3jg2PSOcCsU2F5g,3882
10
- pythonnative/utils.py,sha256=IqR_GYknveM_NfAblcaizg9S66hCZfrfiH08HzpOc-4,2537
11
+ pythonnative/utils.py,sha256=dp-cOYSflWvsY9Qh851OeBq-x6S8W1cx4nyjDxlsd0M,3859
11
12
  pythonnative/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- pythonnative/cli/pn.py,sha256=Yi-RHjl3wUWHgGdwtA4UyEDy-tnjpmBB-PGupyDTae0,32823
13
+ pythonnative/cli/pn.py,sha256=KGDbxRYtZHUS4rId_Br6oI7kEGl0pcRAo5UGztUvTN4,37478
13
14
  pythonnative/native_modules/__init__.py,sha256=M_4SW-wCZ8azLuB0JLwUc8PESw00xMCM887-HuN9YNo,647
14
15
  pythonnative/native_modules/camera.py,sha256=b3UErkABhBm2nQ-e72lEeFOgXoVDY6245cV6TN9iPBk,3509
15
16
  pythonnative/native_modules/file_system.py,sha256=2fvYsIboCtjEyxmVeV72glOjtc9fJN3jTM6uaccru6E,4478
16
17
  pythonnative/native_modules/location.py,sha256=bMSNbtG60hAorKXh4RR2vybX88M_BwHkCpTprRbMsZU,1883
17
18
  pythonnative/native_modules/notifications.py,sha256=g-zT1GD-ojPsLN5eGXWkyNHTncSxdiEYtjllCXlsBSc,5377
19
+ pythonnative/native_views/__init__.py,sha256=0weXhCaDT79sez9Q1JibXSgouRVB1tjucFibhbPQeYc,2984
20
+ pythonnative/native_views/android.py,sha256=59_VzpGlADHFrY2cG8llVn4sDmPkUSGyUVKbyvfkKiU,32686
21
+ pythonnative/native_views/base.py,sha256=IS4_AQd4WsHgn8fka8np9zpaKdAOn1q0Zo09a8wYOtE,4229
22
+ pythonnative/native_views/ios.py,sha256=G5bQz57v5WSw5-wNmpcrcdX3rb7isb126la7UlveZLA,30087
18
23
  pythonnative/templates/android_template/build.gradle,sha256=4gE6CRS6RuBu9kp-_e_uYYU9mBgHVZrqQg9caSxgyuc,352
19
24
  pythonnative/templates/android_template/gradle.properties,sha256=REPaKLRfQiiVfIV8wYmgwzPWvF1f3bhh_kAMV9p4HME,1358
20
25
  pythonnative/templates/android_template/gradlew,sha256=YxNShxF6Hm0SyEWA8fScYdG6AiGOzShmBgXpf5dufWU,5766
@@ -55,7 +60,7 @@ pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties
55
60
  pythonnative/templates/ios_template/ios_template/AppDelegate.swift,sha256=_6G8GNcw4idXd75qKgQKTDCr45Ez73QB8WTvhBqqcMw,1349
56
61
  pythonnative/templates/ios_template/ios_template/Info.plist,sha256=ZQIJGpo8Y2qP0j29xqOsIEGvPpEVICLTAw2NehC5CSo,704
57
62
  pythonnative/templates/ios_template/ios_template/SceneDelegate.swift,sha256=lqtre92dc6d6s-f4ieh_M_4xmc_zMGW79j46tDu9cOY,2177
58
- pythonnative/templates/ios_template/ios_template/ViewController.swift,sha256=eRyxIIVYAYWY9Lt9IH498C2c0FDERUcq-L2TalkEoe0,8315
63
+ pythonnative/templates/ios_template/ios_template/ViewController.swift,sha256=5bVNJ4TT6GIaICzVxl8hgcFmBgaPqjiCivlJzFy0p7c,7920
59
64
  pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json,sha256=D9Sbo8NYXHCWeOAEaoIcPGBoXscGNyDTDTo0SL46IIs,63
60
65
  pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json,sha256=mvZQhvowtJJS-uGhIlcxaR3nlPd3WvdNcb7-tQfRK3w,123
61
66
  pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json,sha256=VUwGr7K_geOvQjFh5VKB6iVXV1mi0tjGMinUmB2JvQs,177
@@ -66,9 +71,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
66
71
  pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
67
72
  pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=l2Pwa50F_rv-qPu2go6e4bQernM6PTQJeNPFl_c4ivY,1387
68
73
  pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
69
- pythonnative-0.7.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
70
- pythonnative-0.7.0.dist-info/METADATA,sha256=aa5sabkb9mMAcl_PSvvkIeFfHKlkpOnxJZRkMLTc3gE,6692
71
- pythonnative-0.7.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
72
- pythonnative-0.7.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
73
- pythonnative-0.7.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
74
- pythonnative-0.7.0.dist-info/RECORD,,
74
+ pythonnative-0.9.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
75
+ pythonnative-0.9.0.dist-info/METADATA,sha256=YOu6Me-TqLtHWoWkL8A8UWp6WY9f-OzzkSfXZp8X86s,6692
76
+ pythonnative-0.9.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
77
+ pythonnative-0.9.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
78
+ pythonnative-0.9.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
79
+ pythonnative-0.9.0.dist-info/RECORD,,