pythonnative 0.35.0__py3-none-any.whl → 0.37.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 (176) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  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 +24 -6
  11. pythonnative/components/layout.py +65 -3
  12. pythonnative/gestures.py +13 -10
  13. pythonnative/hooks.py +41 -26
  14. pythonnative/hosts/__init__.py +13 -36
  15. pythonnative/hosts/native.py +348 -0
  16. pythonnative/images.py +6 -5
  17. pythonnative/mutations.py +21 -0
  18. pythonnative/native_modules/__init__.py +30 -29
  19. pythonnative/native_modules/app_state.py +10 -6
  20. pythonnative/native_modules/battery.py +20 -84
  21. pythonnative/native_modules/biometrics.py +11 -124
  22. pythonnative/native_modules/camera.py +15 -291
  23. pythonnative/native_modules/clipboard.py +14 -109
  24. pythonnative/native_modules/desktop.py +322 -0
  25. pythonnative/native_modules/file_system.py +15 -21
  26. pythonnative/native_modules/haptics.py +19 -129
  27. pythonnative/native_modules/linking.py +24 -137
  28. pythonnative/native_modules/location.py +28 -180
  29. pythonnative/native_modules/net_info.py +25 -135
  30. pythonnative/native_modules/notifications.py +36 -257
  31. pythonnative/native_modules/permissions.py +20 -184
  32. pythonnative/native_modules/registry.py +513 -0
  33. pythonnative/native_modules/secure_store.py +18 -155
  34. pythonnative/native_modules/share.py +8 -113
  35. pythonnative/native_views/__init__.py +67 -86
  36. pythonnative/native_views/bridge_backend.py +264 -0
  37. pythonnative/platform.py +19 -15
  38. pythonnative/platform_metrics.py +3 -3
  39. pythonnative/project/android.py +0 -3
  40. pythonnative/project/builder.py +38 -16
  41. pythonnative/project/config.py +12 -2
  42. pythonnative/project/plugins.py +366 -0
  43. pythonnative/py.typed +0 -0
  44. pythonnative/runtime.py +35 -203
  45. pythonnative/sdk/__init__.py +48 -33
  46. pythonnative/sdk/_components.py +57 -76
  47. pythonnative/storage.py +18 -243
  48. pythonnative/templates/android_template/app/build.gradle +6 -9
  49. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  50. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  51. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  102. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  105. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  106. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  110. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  111. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  112. pythonnative/templates/android_template/settings.gradle +1 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  157. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  158. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  159. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  160. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  161. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  162. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  163. pythonnative/utils.py +4 -82
  164. pythonnative/virtual_rows.py +3 -3
  165. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  166. pythonnative-0.37.0.dist-info/RECORD +263 -0
  167. pythonnative/hosts/android.py +0 -269
  168. pythonnative/hosts/ios.py +0 -404
  169. pythonnative/native_views/android.py +0 -3702
  170. pythonnative/native_views/ios.py +0 -5253
  171. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  172. pythonnative-0.35.0.dist-info/RECORD +0 -155
  173. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  174. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  175. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  176. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,11 @@
1
1
  """Cross-platform clipboard access.
2
2
 
3
3
  [`Clipboard`][pythonnative.Clipboard] reads and writes the system
4
- pasteboard. All methods are synchronous: ``UIPasteboard`` (iOS) and
5
- ``ClipboardManager`` (Android) both answer immediately, so there's no
6
- need for a coroutine.
4
+ pasteboard through the native ``Clipboard`` module (``UIPasteboard``
5
+ on iOS, ``ClipboardManager`` on Android). All methods are synchronous.
7
6
 
8
- On a desktop machine (neither Android nor iOS) the module falls back to
9
- a process-local string buffer. That keeps it usable in the desktop
10
- mock target and unit tests instead of raising.
7
+ Off device the module is a process-local string buffer, which keeps it
8
+ usable in ``pn preview`` and unit tests.
11
9
 
12
10
  Example:
13
11
  ```python
@@ -20,13 +18,8 @@ Example:
20
18
 
21
19
  from __future__ import annotations
22
20
 
23
- from typing import Optional
24
-
25
21
  from .. import diagnostics
26
- from ..utils import IS_ANDROID, IS_IOS
27
-
28
- # Desktop fallback buffer so the API is usable off-device.
29
- _desktop_buffer: str = ""
22
+ from .registry import native_module
30
23
 
31
24
 
32
25
  class Clipboard:
@@ -35,109 +28,21 @@ class Clipboard:
35
28
  @staticmethod
36
29
  def set_string(text: str) -> None:
37
30
  """Copy ``text`` onto the system clipboard."""
38
- global _desktop_buffer
39
- value = "" if text is None else str(text)
40
- if IS_ANDROID:
41
- _android_set(value)
42
- elif IS_IOS:
43
- _ios_set(value)
44
- else:
45
- _desktop_buffer = value
31
+ try:
32
+ native_module("Clipboard").call("set_string", text="" if text is None else str(text))
33
+ except Exception:
34
+ diagnostics.swallowed("clipboard.set_string")
46
35
 
47
36
  @staticmethod
48
37
  def get_string() -> str:
49
38
  """Return the current clipboard string (``""`` when empty)."""
50
- if IS_ANDROID:
51
- return _android_get()
52
- if IS_IOS:
53
- return _ios_get()
54
- return _desktop_buffer
39
+ try:
40
+ value = native_module("Clipboard").call("get_string")
41
+ except Exception:
42
+ return ""
43
+ return "" if value is None else str(value)
55
44
 
56
45
  @staticmethod
57
46
  def has_string() -> bool:
58
47
  """Return ``True`` when the clipboard holds non-empty text."""
59
48
  return bool(Clipboard.get_string())
60
-
61
-
62
- # ======================================================================
63
- # iOS: UIPasteboard
64
- # ======================================================================
65
-
66
-
67
- def _ios_pasteboard() -> Optional[object]:
68
- try:
69
- from rubicon.objc import ObjCClass
70
-
71
- return ObjCClass("UIPasteboard").generalPasteboard
72
- except Exception:
73
- return None
74
-
75
-
76
- def _ios_set(text: str) -> None:
77
- pb = _ios_pasteboard()
78
- if pb is None:
79
- return
80
- try:
81
- pb.string = text
82
- except Exception:
83
- diagnostics.swallowed("clipboard._ios_set")
84
-
85
-
86
- def _ios_get() -> str:
87
- pb = _ios_pasteboard()
88
- if pb is None:
89
- return ""
90
- try:
91
- value = pb.string
92
- return str(value) if value is not None else ""
93
- except Exception:
94
- return ""
95
-
96
-
97
- # ======================================================================
98
- # Android: ClipboardManager
99
- # ======================================================================
100
-
101
-
102
- def _android_manager() -> Optional[object]:
103
- try:
104
- from java import jclass
105
-
106
- from ..utils import get_android_context
107
-
108
- ctx = get_android_context()
109
- Context = jclass("android.content.Context")
110
- return ctx.getSystemService(Context.CLIPBOARD_SERVICE)
111
- except Exception:
112
- return None
113
-
114
-
115
- def _android_set(text: str) -> None:
116
- manager = _android_manager()
117
- if manager is None:
118
- return
119
- try:
120
- from java import jclass
121
-
122
- ClipData = jclass("android.content.ClipData")
123
- clip = ClipData.newPlainText("pythonnative", text)
124
- manager.setPrimaryClip(clip)
125
- except Exception:
126
- diagnostics.swallowed("clipboard._android_set")
127
-
128
-
129
- def _android_get() -> str:
130
- manager = _android_manager()
131
- if manager is None:
132
- return ""
133
- try:
134
- if not manager.hasPrimaryClip():
135
- return ""
136
- clip = manager.getPrimaryClip()
137
- if clip is None or clip.getItemCount() == 0:
138
- return ""
139
- item = clip.getItemAt(0)
140
- text = item.getText()
141
- return str(text) if text is not None else ""
142
- except Exception:
143
- return ""
@@ -0,0 +1,322 @@
1
+ """Off-device implementations of the built-in native modules.
2
+
3
+ On iOS and Android every module in this package is implemented in
4
+ Swift and Kotlin (``PythonNativeKit`` and the ``pythonnative`` Gradle
5
+ module). Off device (``pn preview``, unit tests) the same module names
6
+ resolve to the plain Python classes below, which keep the API usable
7
+ without a device: in-memory buffers, ``"unknown"`` states, and no-op
8
+ feedback.
9
+
10
+ Each class has the same method names and argument shapes as its native
11
+ counterpart, so a facade in this package never branches on platform.
12
+ Apps and tests may swap any of these with
13
+ [`register_python_module`][pythonnative.native_modules.registry.register_python_module].
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import platform as _platform
21
+ import sys
22
+ import threading
23
+ from typing import Any, Callable, Dict, List, Optional
24
+
25
+ __all__ = ["default_implementation"]
26
+
27
+
28
+ # ======================================================================
29
+ # Device / host information
30
+ # ======================================================================
31
+
32
+
33
+ class DesktopDevice:
34
+ """Static device information for the desktop preview."""
35
+
36
+ def info(self) -> Dict[str, Any]:
37
+ home = os.path.expanduser("~")
38
+ app_dir = os.path.join(home, ".pythonnative_data")
39
+ return {
40
+ "platform": "desktop",
41
+ "os": sys.platform,
42
+ "os_version": _platform.release(),
43
+ "model": _platform.machine(),
44
+ "app_dir": app_dir,
45
+ "cache_dir": os.path.join(app_dir, "cache"),
46
+ "temp_dir": os.path.join(app_dir, "tmp"),
47
+ "locale": os.environ.get("LANG", "en_US").split(".")[0],
48
+ "app_version": "0.0.0",
49
+ "build_number": "0",
50
+ "bundle_id": "com.pythonnative.preview",
51
+ "python_version": _platform.python_version(),
52
+ }
53
+
54
+
55
+ class DesktopAppState:
56
+ def current_state(self) -> str:
57
+ return "active"
58
+
59
+
60
+ # ======================================================================
61
+ # Storage
62
+ # ======================================================================
63
+
64
+
65
+ class DesktopStorage:
66
+ """Dict-backed ``AsyncStorage`` with optional JSON persistence.
67
+
68
+ Set ``PN_STORAGE_DIR`` to persist between runs (``pn preview`` does
69
+ this); leave it unset in tests for a purely in-memory store.
70
+ """
71
+
72
+ def __init__(self) -> None:
73
+ self._lock = threading.Lock()
74
+ self._store: Dict[str, str] = {}
75
+ self._loaded = False
76
+
77
+ def _path(self) -> Optional[str]:
78
+ base = os.environ.get("PN_STORAGE_DIR")
79
+ if not base:
80
+ return None
81
+ try:
82
+ os.makedirs(base, exist_ok=True)
83
+ except OSError:
84
+ return None
85
+ return os.path.join(base, "pn_async_storage.json")
86
+
87
+ def _load(self) -> None:
88
+ if self._loaded:
89
+ return
90
+ self._loaded = True
91
+ path = self._path()
92
+ if path is None or not os.path.exists(path):
93
+ return
94
+ try:
95
+ with open(path, encoding="utf-8") as f:
96
+ data = json.load(f)
97
+ except (OSError, json.JSONDecodeError):
98
+ return
99
+ if isinstance(data, dict):
100
+ self._store.update({str(k): str(v) for k, v in data.items()})
101
+
102
+ def _persist(self) -> None:
103
+ path = self._path()
104
+ if path is None:
105
+ return
106
+ try:
107
+ with open(path, "w", encoding="utf-8") as f:
108
+ json.dump(self._store, f)
109
+ except OSError:
110
+ pass
111
+
112
+ def get(self, key: str) -> Optional[str]:
113
+ with self._lock:
114
+ self._load()
115
+ return self._store.get(key)
116
+
117
+ def set(self, key: str, value: str) -> None:
118
+ with self._lock:
119
+ self._load()
120
+ self._store[key] = value
121
+ self._persist()
122
+
123
+ def delete(self, key: str) -> None:
124
+ with self._lock:
125
+ self._load()
126
+ self._store.pop(key, None)
127
+ self._persist()
128
+
129
+ def all_keys(self) -> List[str]:
130
+ with self._lock:
131
+ self._load()
132
+ return list(self._store.keys())
133
+
134
+ def clear(self) -> None:
135
+ with self._lock:
136
+ self._store.clear()
137
+ self._persist()
138
+
139
+ def _reset(self) -> None:
140
+ """Forget everything, including the on-disk snapshot (tests)."""
141
+ with self._lock:
142
+ self._store.clear()
143
+ self._loaded = False
144
+
145
+
146
+ class DesktopSecureStore:
147
+ def __init__(self) -> None:
148
+ self._store: Dict[str, str] = {}
149
+
150
+ def set_item(self, key: str, value: str) -> bool:
151
+ self._store[key] = value
152
+ return True
153
+
154
+ def get_item(self, key: str) -> Optional[str]:
155
+ return self._store.get(key)
156
+
157
+ def delete_item(self, key: str) -> bool:
158
+ return self._store.pop(key, None) is not None
159
+
160
+ def clear(self) -> None:
161
+ self._store.clear()
162
+
163
+
164
+ class DesktopClipboard:
165
+ def __init__(self) -> None:
166
+ self._buffer = ""
167
+
168
+ def set_string(self, text: str) -> None:
169
+ self._buffer = "" if text is None else str(text)
170
+
171
+ def get_string(self) -> str:
172
+ return self._buffer
173
+
174
+
175
+ # ======================================================================
176
+ # System integration
177
+ # ======================================================================
178
+
179
+
180
+ class DesktopAlert:
181
+ """Records alerts and answers with scripted responses.
182
+
183
+ The log and response queue live on
184
+ [`Alert`][pythonnative.alerts.Alert] (``Alert._test_log`` and
185
+ ``Alert.set_test_response``) so tests have one place to look.
186
+ """
187
+
188
+ @staticmethod
189
+ def _record(title: str, message: Optional[str], buttons: List[Dict[str, Any]], style: str) -> Any:
190
+ from ..alerts import Alert
191
+
192
+ Alert._test_log.append({"title": title, "message": message, "buttons": list(buttons), "style": style})
193
+ return Alert
194
+
195
+ def show(self, title: str, message: Optional[str], buttons: List[Dict[str, Any]], style: str) -> None:
196
+ self._record(title, message, buttons, style)
197
+
198
+ def present(self, title: str, message: Optional[str], buttons: List[Dict[str, Any]], style: str) -> int:
199
+ return int(self._record(title, message, buttons, style)._next_test_response())
200
+
201
+
202
+ class DesktopShare:
203
+ def share(self, message: Optional[str] = None, url: Optional[str] = None, title: Optional[str] = None) -> bool:
204
+ del message, url, title
205
+ return False
206
+
207
+
208
+ class DesktopLinking:
209
+ def open_url(self, url: str) -> bool:
210
+ del url
211
+ return False
212
+
213
+ def can_open_url(self, url: str) -> bool:
214
+ del url
215
+ return False
216
+
217
+ def open_settings(self) -> bool:
218
+ return False
219
+
220
+
221
+ class DesktopHaptics:
222
+ def impact(self, style: str = "medium") -> None:
223
+ del style
224
+
225
+ def notification(self, type: str = "success") -> None: # noqa: A002 - wire name
226
+ del type
227
+
228
+ def selection(self) -> None:
229
+ pass
230
+
231
+ def vibrate(self, duration_ms: int = 400) -> None:
232
+ del duration_ms
233
+
234
+ def cancel(self) -> None:
235
+ pass
236
+
237
+
238
+ class DesktopBattery:
239
+ def get_level(self) -> float:
240
+ return -1.0
241
+
242
+ def get_state(self) -> str:
243
+ return "unknown"
244
+
245
+
246
+ class DesktopNetInfo:
247
+ def fetch(self) -> Optional[Dict[str, Any]]:
248
+ # ``None`` means "no fresh reading"; the facade keeps whatever
249
+ # snapshot was last dispatched (tests push those directly).
250
+ return None
251
+
252
+
253
+ class DesktopPermissions:
254
+ def check(self, permission: str) -> str:
255
+ del permission
256
+ return "undetermined"
257
+
258
+ def request(self, permission: str) -> str:
259
+ del permission
260
+ return "undetermined"
261
+
262
+
263
+ class DesktopNotifications:
264
+ def request_permission(self) -> bool:
265
+ return False
266
+
267
+ def schedule(self, title: str, body: str = "", delay_seconds: float = 0, identifier: str = "default") -> bool:
268
+ del title, body, delay_seconds, identifier
269
+ return False
270
+
271
+ def cancel(self, identifier: str = "default") -> None:
272
+ del identifier
273
+
274
+ def get_device_token(self) -> Optional[str]:
275
+ return None
276
+
277
+
278
+ class DesktopCamera:
279
+ def take_photo(self) -> Optional[str]:
280
+ return None
281
+
282
+ def pick_from_gallery(self) -> Optional[str]:
283
+ return None
284
+
285
+
286
+ class DesktopLocation:
287
+ def get_current(self) -> Optional[Dict[str, float]]:
288
+ return None
289
+
290
+
291
+ class DesktopBiometrics:
292
+ def is_available(self) -> bool:
293
+ return False
294
+
295
+ def authenticate(self, reason: str = "Authenticate") -> bool:
296
+ del reason
297
+ return False
298
+
299
+
300
+ _DEFAULTS: Dict[str, Callable[[], Any]] = {
301
+ "Device": DesktopDevice,
302
+ "AppState": DesktopAppState,
303
+ "Storage": DesktopStorage,
304
+ "SecureStore": DesktopSecureStore,
305
+ "Clipboard": DesktopClipboard,
306
+ "Alert": DesktopAlert,
307
+ "Share": DesktopShare,
308
+ "Linking": DesktopLinking,
309
+ "Haptics": DesktopHaptics,
310
+ "Battery": DesktopBattery,
311
+ "NetInfo": DesktopNetInfo,
312
+ "Permissions": DesktopPermissions,
313
+ "Notifications": DesktopNotifications,
314
+ "Camera": DesktopCamera,
315
+ "Location": DesktopLocation,
316
+ "Biometrics": DesktopBiometrics,
317
+ }
318
+
319
+
320
+ def default_implementation(name: str) -> Optional[Callable[[], Any]]:
321
+ """Return the factory for the built-in desktop implementation of ``name``."""
322
+ return _DEFAULTS.get(name)
@@ -17,8 +17,9 @@ Example:
17
17
  import os
18
18
  from typing import Any, Optional
19
19
 
20
- from .. import diagnostics
21
- from ..utils import IS_ANDROID
20
+ from .registry import native_module
21
+
22
+ _app_dir_cache: Optional[str] = None
22
23
 
23
24
 
24
25
  class FileSystem:
@@ -39,31 +40,24 @@ class FileSystem:
39
40
  On Android the result is `Context.getFilesDir()`. On iOS it is
40
41
  the user's Documents directory. On a desktop machine without
41
42
  either runtime, a `.pythonnative_data` directory is created
42
- under the user's home folder.
43
+ under the user's home folder. The value comes from the native
44
+ ``Device`` module's ``info()`` and is cached after the first
45
+ call.
43
46
 
44
47
  Returns:
45
48
  Absolute path to the app's data directory.
46
49
  """
47
- if IS_ANDROID:
48
- try:
49
- from ..utils import get_android_context
50
-
51
- return str(get_android_context().getFilesDir().getAbsolutePath())
52
- except Exception:
53
- diagnostics.swallowed("file_system.FileSystem.app_dir")
54
- else:
50
+ global _app_dir_cache
51
+ if _app_dir_cache is None:
52
+ path: Optional[str] = None
55
53
  try:
56
- from rubicon.objc import ObjCClass
57
-
58
- NSSearchPathForDirectoriesInDomains = ObjCClass(
59
- "NSFileManager"
60
- ).defaultManager.URLsForDirectory_inDomains_
61
- docs = NSSearchPathForDirectoriesInDomains(9, 1) # NSDocumentDirectory, NSUserDomainMask
62
- if docs and docs.count > 0:
63
- return str(docs.objectAtIndex_(0).path)
54
+ info = native_module("Device").call("info")
55
+ if isinstance(info, dict):
56
+ path = info.get("app_dir")
64
57
  except Exception:
65
- diagnostics.swallowed("file_system.FileSystem.app_dir")
66
- return os.path.join(os.path.expanduser("~"), ".pythonnative_data")
58
+ path = None
59
+ _app_dir_cache = str(path) if path else os.path.join(os.path.expanduser("~"), ".pythonnative_data")
60
+ return _app_dir_cache
67
61
 
68
62
  @staticmethod
69
63
  def read_text(path: str, encoding: str = "utf-8") -> Optional[str]: