pythonnative 0.36.0__py3-none-any.whl → 0.38.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 (187) hide show
  1. pythonnative/__init__.py +14 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +24 -86
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/cli/pn.py +96 -10
  11. pythonnative/components/_base.py +31 -0
  12. pythonnative/components/controls.py +19 -16
  13. pythonnative/components/layout.py +70 -11
  14. pythonnative/components/lists.py +6 -6
  15. pythonnative/gestures.py +40 -16
  16. pythonnative/hooks.py +41 -26
  17. pythonnative/hosts/__init__.py +13 -36
  18. pythonnative/hosts/native.py +348 -0
  19. pythonnative/images.py +6 -5
  20. pythonnative/native_modules/__init__.py +49 -29
  21. pythonnative/native_modules/app_state.py +10 -6
  22. pythonnative/native_modules/battery.py +22 -87
  23. pythonnative/native_modules/biometrics.py +15 -130
  24. pythonnative/native_modules/camera.py +24 -296
  25. pythonnative/native_modules/clipboard.py +14 -111
  26. pythonnative/native_modules/desktop.py +322 -0
  27. pythonnative/native_modules/file_system.py +90 -187
  28. pythonnative/native_modules/haptics.py +29 -135
  29. pythonnative/native_modules/linking.py +29 -141
  30. pythonnative/native_modules/location.py +32 -186
  31. pythonnative/native_modules/net_info.py +22 -135
  32. pythonnative/native_modules/notifications.py +37 -265
  33. pythonnative/native_modules/permissions.py +78 -194
  34. pythonnative/native_modules/registry.py +513 -0
  35. pythonnative/native_modules/secure_store.py +23 -164
  36. pythonnative/native_modules/share.py +9 -114
  37. pythonnative/native_views/__init__.py +67 -86
  38. pythonnative/native_views/bridge_backend.py +264 -0
  39. pythonnative/navigation/__init__.py +2 -1
  40. pythonnative/navigation/hooks.py +46 -7
  41. pythonnative/navigation/state.py +30 -9
  42. pythonnative/platform.py +19 -15
  43. pythonnative/platform_metrics.py +3 -3
  44. pythonnative/project/android.py +23 -3
  45. pythonnative/project/builder.py +99 -48
  46. pythonnative/project/config.py +96 -26
  47. pythonnative/project/deps.py +703 -0
  48. pythonnative/project/doctor.py +53 -4
  49. pythonnative/project/plugins.py +366 -0
  50. pythonnative/project/runtime_assets.py +13 -14
  51. pythonnative/py.typed +0 -0
  52. pythonnative/runtime.py +35 -203
  53. pythonnative/sdk/__init__.py +48 -33
  54. pythonnative/sdk/_components.py +57 -76
  55. pythonnative/storage.py +18 -243
  56. pythonnative/style.py +94 -44
  57. pythonnative/templates/android_template/app/build.gradle +11 -11
  58. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  59. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  60. pythonnative/templates/android_template/build.gradle +7 -4
  61. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +1 -1
  62. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  102. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  103. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  105. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  106. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
  107. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  108. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  109. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  110. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  111. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  112. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  113. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  114. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  115. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  116. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  117. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  118. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  119. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  120. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  121. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  122. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  123. pythonnative/templates/android_template/settings.gradle +1 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  157. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  158. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  159. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  160. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  161. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  162. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  163. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  164. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  165. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  166. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  167. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  168. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  169. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  170. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +41 -48
  171. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  172. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  173. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +82 -10
  174. pythonnative/utils.py +26 -109
  175. pythonnative/virtual_rows.py +13 -23
  176. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/METADATA +10 -12
  177. pythonnative-0.38.0.dist-info/RECORD +264 -0
  178. pythonnative/hosts/android.py +0 -269
  179. pythonnative/hosts/ios.py +0 -404
  180. pythonnative/native_views/android.py +0 -3702
  181. pythonnative/native_views/ios.py +0 -5253
  182. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  183. pythonnative-0.36.0.dist-info/RECORD +0 -155
  184. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/WHEEL +0 -0
  185. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/entry_points.txt +0 -0
  186. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/licenses/LICENSE +0 -0
  187. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/top_level.txt +0 -0
pythonnative/__init__.py CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  PythonNative is a cross-platform toolkit that turns Python ``@component``
4
4
  functions into real, native Android and iOS views. The component model
5
- is React-like (function components plus hooks), but rendering happens
6
- through direct platform bindings: Chaquopy on Android (Java) and
7
- rubicon-objc on iOS (Objective-C). There is no JavaScript bridge.
5
+ is React-like (function components plus hooks). Python owns the
6
+ component tree, reconciliation, and layout; each commit crosses once
7
+ into a native rendering core (Swift ``PythonNativeKit`` on iOS, the
8
+ Kotlin ``pythonnative`` module on Android) that owns every platform
9
+ view, gesture, animation, and device API. There is no JavaScript.
8
10
 
9
11
  Key building blocks:
10
12
 
@@ -60,7 +62,7 @@ Example:
60
62
  ```
61
63
  """
62
64
 
63
- __version__ = "0.36.0"
65
+ __version__ = "0.38.0"
64
66
 
65
67
  from . import appearance, diagnostics, gestures, images, runtime, sdk
66
68
  from .alerts import Alert
@@ -188,6 +190,7 @@ from .style import (
188
190
  AutoCapitalize,
189
191
  Color,
190
192
  Dimension,
193
+ Display,
191
194
  EdgeInsets,
192
195
  FlexDirection,
193
196
  FlexWrap,
@@ -196,6 +199,7 @@ from .style import (
196
199
  KeyboardType,
197
200
  LayoutDirection,
198
201
  Overflow,
202
+ PointerEvents,
199
203
  Position,
200
204
  ReturnKeyType,
201
205
  ScaleType,
@@ -205,6 +209,8 @@ from .style import (
205
209
  StyleSheet,
206
210
  TextAlign,
207
211
  TextDecoration,
212
+ TextTransform,
213
+ Theme,
208
214
  ThemeContext,
209
215
  TransformSpec,
210
216
  default_theme,
@@ -312,6 +318,7 @@ __all__ = [
312
318
  "DEFAULT_DARK_THEME",
313
319
  "DEFAULT_LIGHT_THEME",
314
320
  "Dimension",
321
+ "Display",
315
322
  "EdgeInsets",
316
323
  "FlexDirection",
317
324
  "FlexWrap",
@@ -320,6 +327,7 @@ __all__ = [
320
327
  "KeyboardType",
321
328
  "LayoutDirection",
322
329
  "Overflow",
330
+ "PointerEvents",
323
331
  "Position",
324
332
  "ReturnKeyType",
325
333
  "ScaleType",
@@ -329,6 +337,8 @@ __all__ = [
329
337
  "StyleSheet",
330
338
  "TextAlign",
331
339
  "TextDecoration",
340
+ "TextTransform",
341
+ "Theme",
332
342
  "ThemeContext",
333
343
  "TransformSpec",
334
344
  "default_theme",
pythonnative/_ios_log.py CHANGED
@@ -14,7 +14,7 @@ straight to fd 2 is a small, reliable fix: fd 2 *is* visible to
14
14
  `simctl` (that is exactly how `NSLog` reaches the terminal), so
15
15
  Python output lands next to the Swift logs with correct ordering.
16
16
 
17
- This module is intentionally self-contained (no rubicon-objc or
17
+ This module is intentionally self-contained (no bridge or
18
18
  platform-specific C bindings required), so it is safe to import
19
19
  early during `pythonnative` package initialization.
20
20
  """
pythonnative/alerts.py CHANGED
@@ -30,77 +30,39 @@ Example:
30
30
 
31
31
  from __future__ import annotations
32
32
 
33
- import asyncio
34
33
  from typing import Any, Dict, List, Optional, Sequence
35
34
 
36
- from .platform import Platform
37
- from .runtime import resolve_future
35
+ from .native_modules.registry import native_module
38
36
 
39
37
  # ======================================================================
40
38
  # Internal dispatch helpers
41
39
  # ======================================================================
42
40
 
43
41
 
44
- def _dispatch_alert(
42
+ async def _present(
45
43
  *,
46
44
  title: str,
47
45
  message: Optional[str],
48
46
  buttons: List[Dict[str, Any]],
49
47
  style: str,
50
- on_result: Any,
51
- ) -> None:
52
- """Route an alert request to the active platform presenter.
48
+ ) -> int:
49
+ """Ask the native ``Alert`` module to present and await the chosen index.
53
50
 
54
51
  ``buttons`` is a list of ``{"label": str, "style":
55
- "default"|"cancel"|"destructive"}`` dicts. The presenter must
56
- invoke ``on_result(index)`` exactly once when the user picks a
57
- button, or ``on_result(-1)`` if the dialog is dismissed without a
58
- selection. ``on_result`` may run on any thread.
52
+ "default"|"cancel"|"destructive"}`` dicts. The module resolves with
53
+ the index the user picked, or ``-1`` if the dialog was dismissed
54
+ without a selection. Off device the
55
+ [`DesktopAlert`][pythonnative.native_modules.desktop.DesktopAlert]
56
+ implementation records the call and answers from the queue set by
57
+ [`Alert.set_test_response`][pythonnative.alerts.Alert.set_test_response].
59
58
  """
60
- if Platform.is_ios:
61
- try:
62
- from .native_views.ios import _present_alert as _ios_present_alert
63
-
64
- _ios_present_alert(
65
- title=title,
66
- message=message,
67
- buttons=buttons,
68
- style=style,
69
- on_result=on_result,
70
- )
71
- return
72
- except Exception:
73
- on_result(-1)
74
- return
75
-
76
- if Platform.is_android:
77
- try:
78
- from .native_views.android import _present_alert as _android_present_alert
79
-
80
- _android_present_alert(
81
- title=title,
82
- message=message,
83
- buttons=buttons,
84
- style=style,
85
- on_result=on_result,
86
- )
87
- return
88
- except Exception:
89
- on_result(-1)
90
- return
91
-
92
- # Test backend: record the call so unit tests can assert on it,
93
- # then deliver the configured response.
94
- Alert._test_log.append(
95
- {
96
- "title": title,
97
- "message": message,
98
- "buttons": list(buttons),
99
- "style": style,
100
- }
59
+ result = await native_module("Alert").call_async(
60
+ "present", title=title, message=message, buttons=buttons, style=style
101
61
  )
102
- response = Alert._next_test_response()
103
- on_result(response)
62
+ try:
63
+ return int(result)
64
+ except (TypeError, ValueError):
65
+ return -1
104
66
 
105
67
 
106
68
  # ======================================================================
@@ -170,12 +132,8 @@ class Alert:
170
132
  [`choose`][pythonnative.alerts.Alert.choose] and ``await``
171
133
  the result.
172
134
  """
173
- _dispatch_alert(
174
- title=title,
175
- message=message,
176
- buttons=[{"label": button, "style": "default"}],
177
- style="alert",
178
- on_result=lambda _idx: None,
135
+ native_module("Alert").call(
136
+ "show", title=title, message=message, buttons=[{"label": button, "style": "default"}], style="alert"
179
137
  )
180
138
 
181
139
  @staticmethod
@@ -206,13 +164,7 @@ class Alert:
206
164
  await save()
207
165
  ```
208
166
  """
209
- loop = asyncio.get_running_loop()
210
- future: asyncio.Future[bool] = loop.create_future()
211
-
212
- def _on_result(index: int) -> None:
213
- resolve_future(future, index == 1)
214
-
215
- _dispatch_alert(
167
+ index = await _present(
216
168
  title=title,
217
169
  message=message,
218
170
  buttons=[
@@ -220,9 +172,8 @@ class Alert:
220
172
  {"label": confirm_label, "style": "default"},
221
173
  ],
222
174
  style="alert",
223
- on_result=_on_result,
224
175
  )
225
- return await future
176
+ return index == 1
226
177
 
227
178
  @staticmethod
228
179
  async def choose(
@@ -265,9 +216,6 @@ class Alert:
265
216
  if not options:
266
217
  raise ValueError("Alert.choose requires at least one option")
267
218
 
268
- loop = asyncio.get_running_loop()
269
- future: asyncio.Future[Optional[str]] = loop.create_future()
270
-
271
219
  destructive = set(destructive_labels)
272
220
  buttons: List[Dict[str, Any]] = [
273
221
  {
@@ -279,20 +227,10 @@ class Alert:
279
227
  if cancel_label is not None:
280
228
  buttons.append({"label": cancel_label, "style": "cancel"})
281
229
 
282
- def _on_result(index: int) -> None:
283
- if 0 <= index < len(options):
284
- resolve_future(future, options[index])
285
- else:
286
- resolve_future(future, None)
287
-
288
- _dispatch_alert(
289
- title=title,
290
- message=message,
291
- buttons=buttons,
292
- style=style,
293
- on_result=_on_result,
294
- )
295
- return await future
230
+ index = await _present(title=title, message=message, buttons=buttons, style=style)
231
+ if 0 <= index < len(options):
232
+ return options[index]
233
+ return None
296
234
 
297
235
 
298
236
  __all__ = ["Alert"]
@@ -0,0 +1,90 @@
1
+ """Entry point the native app templates run right after Python starts.
2
+
3
+ Both templates execute one line of Python once the interpreter is up:
4
+
5
+ ```python
6
+ import pythonnative.bootstrap; pythonnative.bootstrap.start()
7
+ ```
8
+
9
+ [`start`][pythonnative.bootstrap.start] connects the two halves of the
10
+ bridge (installing the native -> Python callback on iOS), verifies the
11
+ protocol version, routes ``print()`` to the console on iOS, and warms
12
+ the asyncio runtime. From then on the native runtime drives everything
13
+ through ``callback("host", ...)``; see ``docs/concepts/bridge.md``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import sys
20
+ import traceback
21
+ from typing import Any, Dict
22
+
23
+ __all__ = ["start", "status"]
24
+
25
+ _started: Dict[str, Any] = {}
26
+
27
+
28
+ def start(dev: bool = False, strict: bool = False) -> Dict[str, Any]:
29
+ """Connect the bridge and prepare the runtime.
30
+
31
+ Args:
32
+ dev: Enable dev mode (RedBox, validation warnings). Debug
33
+ templates pass ``True``; hot reload turns it on as well.
34
+ strict: Re-raise the failure after recording it. The templates
35
+ pass ``True`` so a broken bridge surfaces as a bootstrap
36
+ error screen with the full traceback.
37
+
38
+ Returns:
39
+ A status dict (``{"protocol": 1, "platform": "ios"}``) that the
40
+ template logs. Unless ``strict`` is set this never raises:
41
+ failures are printed and reported in the dict under
42
+ ``"error"``.
43
+ """
44
+ global _started
45
+ if _started and "error" not in _started:
46
+ return dict(_started)
47
+ status_: Dict[str, Any] = {"platform": None, "protocol": None}
48
+ try:
49
+ from .utils import IS_ANDROID, IS_IOS
50
+
51
+ status_["platform"] = "ios" if IS_IOS else "android" if IS_ANDROID else "off-device"
52
+ if IS_IOS:
53
+ try:
54
+ from . import _ios_log
55
+
56
+ _ios_log.install()
57
+ except Exception:
58
+ pass
59
+ from . import bridge
60
+
61
+ status_["protocol"] = bridge.handshake()
62
+ if dev or os.environ.get("PN_DEV") in ("1", "true"):
63
+ from . import diagnostics
64
+
65
+ diagnostics.set_dev_mode(True)
66
+ # Create the guest loop now so the first pump request has a
67
+ # loop to drive and effects can schedule work during mount.
68
+ from .runtime import get_loop
69
+
70
+ get_loop()
71
+ # Import the view backend eagerly: on a slow device the first
72
+ # commit shouldn't also pay for importing the reconciler.
73
+ from .native_views import get_registry
74
+
75
+ get_registry()
76
+ except Exception as exc:
77
+ status_["error"] = f"{type(exc).__name__}: {exc}"
78
+ print(f"[pn.bootstrap] start failed: {exc!r}", file=sys.stderr)
79
+ traceback.print_exc()
80
+ _started = status_
81
+ if strict:
82
+ raise
83
+ return dict(status_)
84
+ _started = status_
85
+ return dict(status_)
86
+
87
+
88
+ def status() -> Dict[str, Any]:
89
+ """Return the result of the last [`start`][pythonnative.bootstrap.start] (empty before it ran)."""
90
+ return dict(_started)
@@ -0,0 +1,306 @@
1
+ """The native bridge: one channel between Python and Swift / Kotlin.
2
+
3
+ Everything that crosses into native code goes through a
4
+ [`Transport`][pythonnative.bridge.Transport] (``apply`` a transaction,
5
+ ``measure`` a view, run a ``command``, drive an ``animate`` request, or
6
+ ``call`` a native module), and everything native sends back arrives at
7
+ [`native_callback`][pythonnative.bridge.native_callback]. The protocol
8
+ is documented in ``docs/concepts/bridge.md``.
9
+
10
+ Off-device (tests, ``pn preview``) there is no transport; the desktop
11
+ registry renders with Tkinter and native modules fall back to their
12
+ Python implementations. Tests that want to exercise the bridge itself
13
+ install a [`FakeTransport`][pythonnative.bridge.fake.FakeTransport]
14
+ with [`set_transport`][pythonnative.bridge.set_transport].
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import threading
20
+ from collections import deque
21
+ from typing import Any, Callable, Deque, Dict, Optional, Protocol, Tuple
22
+
23
+ from . import codec
24
+
25
+ __all__ = [
26
+ "PROTOCOL_VERSION",
27
+ "Transport",
28
+ "get_transport",
29
+ "has_transport",
30
+ "handshake",
31
+ "native_callback",
32
+ "post_to_main",
33
+ "set_transport",
34
+ ]
35
+
36
+ PROTOCOL_VERSION = 1
37
+ """Bridge protocol version this Python package speaks."""
38
+
39
+
40
+ class Transport(Protocol):
41
+ """The Python -> native half of the bridge."""
42
+
43
+ name: str
44
+
45
+ def protocol_version(self) -> int:
46
+ """Return the protocol version compiled into the native library."""
47
+
48
+ def apply(self, transaction_json: str) -> None:
49
+ """Apply one serialized transaction (a JSON array of ops)."""
50
+
51
+ def measure(self, tag: int, max_width: float, max_height: float) -> Tuple[float, float]:
52
+ """Return the intrinsic ``(width, height)`` of the view ``tag`` under the constraints."""
53
+
54
+ def command(self, tag: int, name: str, args_json: str) -> Optional[str]:
55
+ """Run an imperative command on one view; returns its JSON result or ``None``."""
56
+
57
+ def animate(self, tag: int, request_json: str) -> Optional[str]:
58
+ """Handle an animation request (``set`` / ``start`` / ``cancel``) for one view."""
59
+
60
+ def call(self, module: str, method: str, args_json: str) -> Optional[str]:
61
+ """Call a native module method with a ``{"call_id", "args"}`` envelope."""
62
+
63
+ def set_callback(self, callback: Callable[[str, int, str, str], Optional[str]]) -> None:
64
+ """Install ``callback`` as the native -> Python entry point."""
65
+
66
+
67
+ # ======================================================================
68
+ # Transport selection
69
+ # ======================================================================
70
+
71
+ _transport: Optional[Transport] = None
72
+ _transport_lock = threading.Lock()
73
+ _explicit = False
74
+
75
+
76
+ def _create_platform_transport() -> Optional[Transport]:
77
+ from ..utils import IS_ANDROID, IS_IOS
78
+
79
+ if IS_IOS:
80
+ from .ios import IOSTransport
81
+
82
+ transport: Transport = IOSTransport()
83
+ transport.set_callback(native_callback)
84
+ return transport
85
+ if IS_ANDROID:
86
+ from .android import AndroidTransport
87
+
88
+ return AndroidTransport()
89
+ return None
90
+
91
+
92
+ def get_transport() -> Transport:
93
+ """Return the active transport, creating the platform one on first use.
94
+
95
+ Raises:
96
+ RuntimeError: Off-device, where no native runtime exists.
97
+ """
98
+ global _transport
99
+ if _transport is not None:
100
+ return _transport
101
+ with _transport_lock:
102
+ if _transport is None:
103
+ created = _create_platform_transport()
104
+ if created is None:
105
+ raise RuntimeError(
106
+ "No native bridge is available on this platform (running off-device). "
107
+ "Use `pn preview` for the desktop renderer or install a FakeTransport in tests."
108
+ )
109
+ _transport = created
110
+ return _transport
111
+
112
+
113
+ def has_transport() -> bool:
114
+ """Whether a transport exists or can be created without raising."""
115
+ if _transport is not None:
116
+ return True
117
+ if _explicit:
118
+ return False
119
+ from ..utils import IS_ANDROID, IS_IOS
120
+
121
+ return bool(IS_IOS or IS_ANDROID)
122
+
123
+
124
+ def set_transport(transport: Optional[Transport]) -> None:
125
+ """Install a transport explicitly (tests) or reset with ``None``."""
126
+ global _transport, _explicit
127
+ with _transport_lock:
128
+ _transport = transport
129
+ _explicit = transport is not None
130
+ if transport is not None:
131
+ transport.set_callback(native_callback)
132
+
133
+
134
+ def handshake() -> int:
135
+ """Verify the native library speaks our protocol version.
136
+
137
+ Called by the native templates right after Python starts. Returns
138
+ the negotiated version.
139
+
140
+ Raises:
141
+ RuntimeError: On a version mismatch, with a hint to rebuild.
142
+ """
143
+ version = get_transport().protocol_version()
144
+ if version != PROTOCOL_VERSION:
145
+ raise RuntimeError(
146
+ f"Bridge protocol mismatch: the native runtime speaks v{version} but pythonnative "
147
+ f"expects v{PROTOCOL_VERSION}. Re-run 'pn build' so the staged template matches the "
148
+ "installed pythonnative package."
149
+ )
150
+ return version
151
+
152
+
153
+ # ======================================================================
154
+ # Main-queue posting
155
+ # ======================================================================
156
+ #
157
+ # The asyncio guest loop and ``call_on_main_thread`` need a way to run
158
+ # a callable on the platform main thread's next turn. Native provides
159
+ # it: ``Host.post()`` schedules ``callback("pump")``, which drains this
160
+ # queue. Keeping the queue in Python means one native crossing per
161
+ # batch of callables rather than one per callable.
162
+
163
+ _main_queue: Deque[Callable[[], None]] = deque()
164
+ _main_queue_lock = threading.Lock()
165
+ _pump_requested = False
166
+
167
+
168
+ def post_to_main(fn: Callable[[], None]) -> None:
169
+ """Queue ``fn`` for the next main-thread turn (never runs inline)."""
170
+ global _pump_requested
171
+ with _main_queue_lock:
172
+ _main_queue.append(fn)
173
+ if _pump_requested:
174
+ return
175
+ _pump_requested = True
176
+ try:
177
+ get_transport().call("Host", "post", codec.dumps({"call_id": 0, "args": {}}))
178
+ except Exception as exc:
179
+ with _main_queue_lock:
180
+ _pump_requested = False
181
+ print(f"[pn.bridge] Host.post failed; running {len(_main_queue)} queued callable(s) inline: {exc!r}")
182
+ _drain_main_queue()
183
+
184
+
185
+ def _drain_main_queue() -> None:
186
+ global _pump_requested
187
+ while True:
188
+ with _main_queue_lock:
189
+ if not _main_queue:
190
+ _pump_requested = False
191
+ return
192
+ fn = _main_queue.popleft()
193
+ try:
194
+ fn()
195
+ except Exception as exc:
196
+ print(f"[pn.bridge] main-thread callable raised: {exc!r}")
197
+
198
+
199
+ # ======================================================================
200
+ # native -> Python
201
+ # ======================================================================
202
+
203
+
204
+ def native_callback(kind: str, tag: int, name: str, payload: str) -> Optional[str]:
205
+ """Single entry point for every native -> Python message.
206
+
207
+ Args:
208
+ kind: ``"event"``, ``"module"``, ``"host"``, ``"animation"``,
209
+ or ``"pump"``.
210
+ tag: View tag (events), screen id (host), otherwise ``0``.
211
+ name: Event name, module name, or host event.
212
+ payload: JSON text whose shape depends on ``kind``.
213
+
214
+ Returns:
215
+ A JSON string for request-style messages (a handler's return
216
+ value, ``"true"`` / ``"false"`` for ``back_pressed``), else
217
+ ``None``. Never raises: failures are reported through
218
+ ``diagnostics`` so nothing propagates into UIKit or the
219
+ Android looper.
220
+ """
221
+ try:
222
+ if kind == "event":
223
+ return _on_event(int(tag), name, payload)
224
+ if kind == "module":
225
+ from ..native_modules.registry import dispatch_module_message
226
+
227
+ dispatch_module_message(name, codec.loads(payload) or {})
228
+ return None
229
+ if kind == "host":
230
+ from ..hosts.native import dispatch_host_event
231
+
232
+ return dispatch_host_event(int(tag), name, codec.loads(payload))
233
+ if kind == "animation":
234
+ from ..animated import native_animation_completed
235
+
236
+ data = codec.loads(payload) or {}
237
+ native_animation_completed(int(data.get("id", 0)), bool(data.get("finished", True)))
238
+ return None
239
+ if kind == "pump":
240
+ _drain_main_queue()
241
+ return None
242
+ print(f"[pn.bridge] unknown callback kind {kind!r}")
243
+ except Exception as exc:
244
+ from .. import diagnostics
245
+
246
+ if not diagnostics.report_error(exc, phase=f"bridge {kind}:{name}"):
247
+ import traceback
248
+
249
+ traceback.print_exc()
250
+ return None
251
+
252
+
253
+ def _on_event(tag: int, name: str, payload: str) -> Optional[str]:
254
+ from ..events import get_event_registry
255
+
256
+ args = codec.loads(payload)
257
+ if args is None:
258
+ args = []
259
+ elif not isinstance(args, list):
260
+ args = [args]
261
+ callback = get_event_registry().get(tag, name)
262
+ if callback is None:
263
+ from ..native_views import get_registry
264
+
265
+ backend = get_registry()
266
+ internal = getattr(backend, "handle_internal_event", None)
267
+ if internal is not None:
268
+ result = internal(tag, name, args)
269
+ return None if result is None else codec.dumps(codec.to_jsonable(result))
270
+ return None
271
+ try:
272
+ result = callback(*args)
273
+ except Exception as exc:
274
+ from .. import diagnostics
275
+
276
+ if not diagnostics.report_error(exc, phase=f"event {name!r}"):
277
+ import traceback
278
+
279
+ traceback.print_exc()
280
+ return None
281
+ if result is None:
282
+ return None
283
+ try:
284
+ return codec.dumps(codec.to_jsonable(result))
285
+ except (TypeError, ValueError):
286
+ return None
287
+
288
+
289
+ def _reset_for_tests() -> None:
290
+ """Drop the transport and any queued main-thread work (test isolation)."""
291
+ global _transport, _explicit, _pump_requested
292
+ with _transport_lock:
293
+ _transport = None
294
+ _explicit = False
295
+ with _main_queue_lock:
296
+ _main_queue.clear()
297
+ _pump_requested = False
298
+
299
+
300
+ def transport_state() -> Dict[str, Any]:
301
+ """Diagnostics snapshot (used by ``pn doctor`` and tests)."""
302
+ return {
303
+ "transport": None if _transport is None else _transport.name,
304
+ "protocol_version": PROTOCOL_VERSION,
305
+ "queued_main_callables": len(_main_queue),
306
+ }