pythonnative 0.40.0__cp313-cp313-win_amd64.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 (418) hide show
  1. pythonnative/__init__.py +411 -0
  2. pythonnative/_ios_log.py +92 -0
  3. pythonnative/_yoga.cp313-win_amd64.pyd +0 -0
  4. pythonnative/alerts.py +236 -0
  5. pythonnative/animated.py +1687 -0
  6. pythonnative/animation_graph.py +78 -0
  7. pythonnative/appearance.py +124 -0
  8. pythonnative/bootstrap.py +100 -0
  9. pythonnative/bridge/__init__.py +302 -0
  10. pythonnative/bridge/android.py +78 -0
  11. pythonnative/bridge/codec.py +170 -0
  12. pythonnative/bridge/commits.py +114 -0
  13. pythonnative/bridge/fake.py +243 -0
  14. pythonnative/bridge/ios.py +138 -0
  15. pythonnative/bridge/web.py +489 -0
  16. pythonnative/cli/__init__.py +7 -0
  17. pythonnative/cli/pn.py +1214 -0
  18. pythonnative/component.py +271 -0
  19. pythonnative/components/__init__.py +111 -0
  20. pythonnative/components/_base.py +166 -0
  21. pythonnative/components/controls.py +521 -0
  22. pythonnative/components/layout.py +518 -0
  23. pythonnative/components/lists.py +785 -0
  24. pythonnative/components/media.py +204 -0
  25. pythonnative/components/overlays.py +107 -0
  26. pythonnative/components/pressable.py +252 -0
  27. pythonnative/components/structural.py +167 -0
  28. pythonnative/components/text.py +291 -0
  29. pythonnative/devclient.py +824 -0
  30. pythonnative/devserver/__init__.py +34 -0
  31. pythonnative/devserver/server.py +718 -0
  32. pythonnative/devserver/static/animation_graph.js +82 -0
  33. pythonnative/devserver/static/bridge.js +147 -0
  34. pythonnative/devserver/static/colors.js +70 -0
  35. pythonnative/devserver/static/host.js +552 -0
  36. pythonnative/devserver/static/index.html +60 -0
  37. pythonnative/devserver/static/layout.js +85 -0
  38. pythonnative/devserver/static/preview.css +415 -0
  39. pythonnative/devserver/static/renderer.js +1994 -0
  40. pythonnative/devserver/static/shell.js +345 -0
  41. pythonnative/devserver/static/yoga/LICENSE +21 -0
  42. pythonnative/devserver/static/yoga/binaries/yoga-wasm-base64-esm.js +75 -0
  43. pythonnative/devserver/static/yoga/src/generated/YGEnums.d.ts +189 -0
  44. pythonnative/devserver/static/yoga/src/generated/YGEnums.js +211 -0
  45. pythonnative/devserver/static/yoga/src/generated/YGEnums.js.map +1 -0
  46. pythonnative/devserver/static/yoga/src/index.d.ts +12 -0
  47. pythonnative/devserver/static/yoga/src/index.js +16 -0
  48. pythonnative/devserver/static/yoga/src/index.js.map +1 -0
  49. pythonnative/devserver/static/yoga/src/load.d.ts +11 -0
  50. pythonnative/devserver/static/yoga/src/load.js +17 -0
  51. pythonnative/devserver/static/yoga/src/load.js.map +1 -0
  52. pythonnative/devserver/static/yoga/src/wrapAssembly.d.ts +155 -0
  53. pythonnative/devserver/static/yoga/src/wrapAssembly.js +125 -0
  54. pythonnative/devserver/static/yoga/src/wrapAssembly.js.map +1 -0
  55. pythonnative/devserver/watcher.py +267 -0
  56. pythonnative/devserver/ws.py +421 -0
  57. pythonnative/diagnostics.py +241 -0
  58. pythonnative/element.py +159 -0
  59. pythonnative/equality.py +19 -0
  60. pythonnative/events.py +231 -0
  61. pythonnative/gestures.py +1333 -0
  62. pythonnative/hooks.py +1621 -0
  63. pythonnative/hosts/__init__.py +63 -0
  64. pythonnative/hosts/base.py +596 -0
  65. pythonnative/hosts/native.py +302 -0
  66. pythonnative/hot_reload.py +585 -0
  67. pythonnative/layout.py +328 -0
  68. pythonnative/mutations.py +142 -0
  69. pythonnative/native/android/build.gradle +52 -0
  70. pythonnative/native/android/gradle.properties +3 -0
  71. pythonnative/native/android/settings.gradle +12 -0
  72. pythonnative/native/android/src/main/AndroidManifest.xml +11 -0
  73. pythonnative/native/android/src/main/cpp/CMakeLists.txt +9 -0
  74. pythonnative/native/android/src/main/cpp/yoga_jni.cpp +52 -0
  75. pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeModules.kt +5 -0
  76. pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeProps.kt +3705 -0
  77. pythonnative/native/android/src/main/java/com/pythonnative/generated/PNContracts.kt +129 -0
  78. pythonnative/native/android/src/main/java/com/pythonnative/runtime/PNBridge.kt +261 -0
  79. pythonnative/native/android/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  80. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationGraph.kt +108 -0
  81. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  82. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +257 -0
  83. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/CommitState.kt +106 -0
  84. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  85. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +40 -0
  86. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  87. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  88. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  89. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +117 -0
  90. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  91. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +37 -0
  92. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +32 -0
  93. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  94. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  95. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  96. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  97. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  98. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +162 -0
  99. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  100. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  101. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +86 -0
  102. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  103. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  104. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScreenStackManager.kt +110 -0
  105. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  106. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  107. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  108. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  109. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +250 -0
  110. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  111. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  112. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +159 -0
  113. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  114. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  115. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  116. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  117. pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/NativeLayout.kt +145 -0
  118. pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/YogaNode.kt +29 -0
  119. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  120. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  121. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +61 -0
  122. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  123. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +158 -0
  124. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  125. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
  126. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  127. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  128. pythonnative/native/android/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  129. pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +188 -0
  130. pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  131. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNAccessibilityDelegate.kt +48 -0
  132. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNBorderDrawable.kt +76 -0
  133. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  134. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNFrameLayout.kt +49 -0
  135. pythonnative/native/android/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  136. pythonnative/native/android/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  137. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  138. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  139. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PromiseTest.kt +89 -0
  140. pythonnative/native/ios/Package.swift +23 -0
  141. pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimationGraph.swift +199 -0
  142. pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimator.swift +312 -0
  143. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNBridge.swift +255 -0
  144. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNCommit.swift +104 -0
  145. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNRegistry.swift +142 -0
  146. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNTransaction.swift +166 -0
  147. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  148. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNButtonManagers.swift +207 -0
  149. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  150. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  151. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  152. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  153. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNImageManager.swift +222 -0
  154. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  155. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  156. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScreenStackManager.swift +107 -0
  157. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  158. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  159. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextInputManager.swift +297 -0
  160. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  161. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  162. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  163. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  164. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  165. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +155 -0
  166. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  167. pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeModules.swift +1 -0
  168. pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeProps.swift +3775 -0
  169. pythonnative/native/ios/Sources/PythonNativeKit/Generated/PNContracts.swift +46 -0
  170. pythonnative/native/ios/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  171. pythonnative/native/ios/Sources/PythonNativeKit/Layout/PNLayout.swift +173 -0
  172. pythonnative/native/ios/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  173. pythonnative/native/ios/Sources/PythonNativeKit/Modules/HostModule.swift +80 -0
  174. pythonnative/native/ios/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  175. pythonnative/native/ios/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  176. pythonnative/native/ios/Sources/PythonNativeKit/Modules/PNNativeModule.swift +168 -0
  177. pythonnative/native/ios/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
  178. pythonnative/native/ios/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  179. pythonnative/native/ios/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  180. pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  181. pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNViewController.swift +237 -0
  182. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  183. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  184. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  185. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  186. pythonnative/native/ios/Tests/PythonNativeKitTests/PNAnimationGraphTests.swift +26 -0
  187. pythonnative/native/ios/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  188. pythonnative/native/ios/Tests/PythonNativeKitTests/PNCommitTests.swift +86 -0
  189. pythonnative/native/ios/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  190. pythonnative/native/ios/Tests/PythonNativeKitTests/PNManagerTests.swift +126 -0
  191. pythonnative/native/ios/Tests/PythonNativeKitTests/PNModuleTests.swift +111 -0
  192. pythonnative/native/ios/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  193. pythonnative/native/yoga/LICENSE +21 -0
  194. pythonnative/native/yoga/Package.swift +9 -0
  195. pythonnative/native/yoga/VENDORED.md +2 -0
  196. pythonnative/native/yoga/include/PNStyle.h +10 -0
  197. pythonnative/native/yoga/include/module.modulemap +5 -0
  198. pythonnative/native/yoga/include/yoga/YGConfig.h +158 -0
  199. pythonnative/native/yoga/include/yoga/YGEnums.h +142 -0
  200. pythonnative/native/yoga/include/yoga/YGMacros.h +99 -0
  201. pythonnative/native/yoga/include/yoga/YGNode.h +304 -0
  202. pythonnative/native/yoga/include/yoga/YGNodeLayout.h +35 -0
  203. pythonnative/native/yoga/include/yoga/YGNodeStyle.h +130 -0
  204. pythonnative/native/yoga/include/yoga/YGPixelGrid.h +29 -0
  205. pythonnative/native/yoga/include/yoga/YGValue.h +84 -0
  206. pythonnative/native/yoga/include/yoga/Yoga.h +21 -0
  207. pythonnative/native/yoga/python.cpp +16 -0
  208. pythonnative/native/yoga/style.cpp +272 -0
  209. pythonnative/native/yoga/yoga/CMakeLists.txt +41 -0
  210. pythonnative/native/yoga/yoga/YGConfig.cpp +92 -0
  211. pythonnative/native/yoga/yoga/YGConfig.h +158 -0
  212. pythonnative/native/yoga/yoga/YGEnums.cpp +262 -0
  213. pythonnative/native/yoga/yoga/YGEnums.h +142 -0
  214. pythonnative/native/yoga/yoga/YGMacros.h +99 -0
  215. pythonnative/native/yoga/yoga/YGNode.cpp +366 -0
  216. pythonnative/native/yoga/yoga/YGNode.h +304 -0
  217. pythonnative/native/yoga/yoga/YGNodeLayout.cpp +92 -0
  218. pythonnative/native/yoga/yoga/YGNodeLayout.h +35 -0
  219. pythonnative/native/yoga/yoga/YGNodeStyle.cpp +405 -0
  220. pythonnative/native/yoga/yoga/YGNodeStyle.h +130 -0
  221. pythonnative/native/yoga/yoga/YGPixelGrid.cpp +22 -0
  222. pythonnative/native/yoga/yoga/YGPixelGrid.h +29 -0
  223. pythonnative/native/yoga/yoga/YGValue.cpp +20 -0
  224. pythonnative/native/yoga/yoga/YGValue.h +84 -0
  225. pythonnative/native/yoga/yoga/Yoga.h +21 -0
  226. pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.cpp +563 -0
  227. pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.h +41 -0
  228. pythonnative/native/yoga/yoga/algorithm/Align.h +72 -0
  229. pythonnative/native/yoga/yoga/algorithm/Baseline.cpp +78 -0
  230. pythonnative/native/yoga/yoga/algorithm/Baseline.h +21 -0
  231. pythonnative/native/yoga/yoga/algorithm/BoundAxis.h +79 -0
  232. pythonnative/native/yoga/yoga/algorithm/Cache.cpp +121 -0
  233. pythonnative/native/yoga/yoga/algorithm/Cache.h +30 -0
  234. pythonnative/native/yoga/yoga/algorithm/CalculateLayout.cpp +2428 -0
  235. pythonnative/native/yoga/yoga/algorithm/CalculateLayout.h +38 -0
  236. pythonnative/native/yoga/yoga/algorithm/FlexDirection.h +120 -0
  237. pythonnative/native/yoga/yoga/algorithm/FlexLine.cpp +124 -0
  238. pythonnative/native/yoga/yoga/algorithm/FlexLine.h +75 -0
  239. pythonnative/native/yoga/yoga/algorithm/PixelGrid.cpp +132 -0
  240. pythonnative/native/yoga/yoga/algorithm/PixelGrid.h +29 -0
  241. pythonnative/native/yoga/yoga/algorithm/SizingMode.h +73 -0
  242. pythonnative/native/yoga/yoga/algorithm/TrailingPosition.h +44 -0
  243. pythonnative/native/yoga/yoga/config/Config.cpp +135 -0
  244. pythonnative/native/yoga/yoga/config/Config.h +92 -0
  245. pythonnative/native/yoga/yoga/debug/AssertFatal.cpp +53 -0
  246. pythonnative/native/yoga/yoga/debug/AssertFatal.h +29 -0
  247. pythonnative/native/yoga/yoga/debug/Log.cpp +107 -0
  248. pythonnative/native/yoga/yoga/debug/Log.h +34 -0
  249. pythonnative/native/yoga/yoga/enums/Align.h +47 -0
  250. pythonnative/native/yoga/yoga/enums/BoxSizing.h +40 -0
  251. pythonnative/native/yoga/yoga/enums/Dimension.h +40 -0
  252. pythonnative/native/yoga/yoga/enums/Direction.h +41 -0
  253. pythonnative/native/yoga/yoga/enums/Display.h +41 -0
  254. pythonnative/native/yoga/yoga/enums/Edge.h +47 -0
  255. pythonnative/native/yoga/yoga/enums/Errata.h +41 -0
  256. pythonnative/native/yoga/yoga/enums/ExperimentalFeature.h +39 -0
  257. pythonnative/native/yoga/yoga/enums/FlexDirection.h +42 -0
  258. pythonnative/native/yoga/yoga/enums/Gutter.h +41 -0
  259. pythonnative/native/yoga/yoga/enums/Justify.h +44 -0
  260. pythonnative/native/yoga/yoga/enums/LogLevel.h +44 -0
  261. pythonnative/native/yoga/yoga/enums/MeasureMode.h +41 -0
  262. pythonnative/native/yoga/yoga/enums/NodeType.h +40 -0
  263. pythonnative/native/yoga/yoga/enums/Overflow.h +41 -0
  264. pythonnative/native/yoga/yoga/enums/PhysicalEdge.h +21 -0
  265. pythonnative/native/yoga/yoga/enums/PositionType.h +41 -0
  266. pythonnative/native/yoga/yoga/enums/Unit.h +42 -0
  267. pythonnative/native/yoga/yoga/enums/Wrap.h +41 -0
  268. pythonnative/native/yoga/yoga/enums/YogaEnums.h +85 -0
  269. pythonnative/native/yoga/yoga/event/event.cpp +87 -0
  270. pythonnative/native/yoga/yoga/event/event.h +130 -0
  271. pythonnative/native/yoga/yoga/module.modulemap +21 -0
  272. pythonnative/native/yoga/yoga/node/CachedMeasurement.h +53 -0
  273. pythonnative/native/yoga/yoga/node/LayoutResults.cpp +48 -0
  274. pythonnative/native/yoga/yoga/node/LayoutResults.h +122 -0
  275. pythonnative/native/yoga/yoga/node/LayoutableChildren.h +148 -0
  276. pythonnative/native/yoga/yoga/node/Node.cpp +443 -0
  277. pythonnative/native/yoga/yoga/node/Node.h +337 -0
  278. pythonnative/native/yoga/yoga/numeric/Comparison.h +81 -0
  279. pythonnative/native/yoga/yoga/numeric/FloatOptional.h +93 -0
  280. pythonnative/native/yoga/yoga/style/SmallValueBuffer.h +133 -0
  281. pythonnative/native/yoga/yoga/style/Style.h +757 -0
  282. pythonnative/native/yoga/yoga/style/StyleLength.h +107 -0
  283. pythonnative/native/yoga/yoga/style/StyleValueHandle.h +98 -0
  284. pythonnative/native/yoga/yoga/style/StyleValuePool.h +127 -0
  285. pythonnative/native_modules/__init__.py +124 -0
  286. pythonnative/native_modules/app_state.py +99 -0
  287. pythonnative/native_modules/battery.py +75 -0
  288. pythonnative/native_modules/biometrics.py +42 -0
  289. pythonnative/native_modules/camera.py +65 -0
  290. pythonnative/native_modules/clipboard.py +46 -0
  291. pythonnative/native_modules/fallback.py +324 -0
  292. pythonnative/native_modules/file_system.py +145 -0
  293. pythonnative/native_modules/haptics.py +75 -0
  294. pythonnative/native_modules/linking.py +123 -0
  295. pythonnative/native_modules/location.py +75 -0
  296. pythonnative/native_modules/net_info.py +112 -0
  297. pythonnative/native_modules/notifications.py +129 -0
  298. pythonnative/native_modules/permissions.py +108 -0
  299. pythonnative/native_modules/registry.py +526 -0
  300. pythonnative/native_modules/secure_store.py +52 -0
  301. pythonnative/native_modules/share.py +51 -0
  302. pythonnative/native_views/__init__.py +375 -0
  303. pythonnative/native_views/base.py +316 -0
  304. pythonnative/native_views/bridge_backend.py +277 -0
  305. pythonnative/navigation/__init__.py +103 -0
  306. pythonnative/navigation/container.py +154 -0
  307. pythonnative/navigation/handle.py +540 -0
  308. pythonnative/navigation/hooks.py +133 -0
  309. pythonnative/navigation/host.py +58 -0
  310. pythonnative/navigation/linking.py +200 -0
  311. pythonnative/navigation/navigators.py +659 -0
  312. pythonnative/navigation/screen.py +149 -0
  313. pythonnative/navigation/state.py +269 -0
  314. pythonnative/net.py +248 -0
  315. pythonnative/platform.py +166 -0
  316. pythonnative/platform_metrics.py +252 -0
  317. pythonnative/preview.py +287 -0
  318. pythonnative/profiling.py +101 -0
  319. pythonnative/project/__init__.py +68 -0
  320. pythonnative/project/android.py +557 -0
  321. pythonnative/project/builder.py +817 -0
  322. pythonnative/project/config.py +773 -0
  323. pythonnative/project/deps.py +719 -0
  324. pythonnative/project/devices.py +304 -0
  325. pythonnative/project/doctor.py +283 -0
  326. pythonnative/project/fingerprint.py +171 -0
  327. pythonnative/project/icons.py +247 -0
  328. pythonnative/project/ios.py +325 -0
  329. pythonnative/project/lockfile.py +100 -0
  330. pythonnative/project/permissions.py +361 -0
  331. pythonnative/project/plugins.py +519 -0
  332. pythonnative/project/runtime_assets.py +183 -0
  333. pythonnative/py.typed +0 -0
  334. pythonnative/query.py +153 -0
  335. pythonnative/reconciler/__init__.py +29 -0
  336. pythonnative/reconciler/boundaries.py +375 -0
  337. pythonnative/reconciler/children.py +88 -0
  338. pythonnative/reconciler/core.py +1165 -0
  339. pythonnative/reconciler/layout_pass.py +393 -0
  340. pythonnative/reconciler/vnode.py +266 -0
  341. pythonnative/refresh.py +55 -0
  342. pythonnative/runtime.py +306 -0
  343. pythonnative/scheduler.py +159 -0
  344. pythonnative/sdk/__init__.py +153 -0
  345. pythonnative/sdk/_components.py +427 -0
  346. pythonnative/sdk/builtins.py +91 -0
  347. pythonnative/sdk/codegen.py +149 -0
  348. pythonnative/sdk/module_codegen.py +184 -0
  349. pythonnative/sdk/schema.py +224 -0
  350. pythonnative/sdk/templates/contracts.kt +46 -0
  351. pythonnative/sdk/templates/contracts.swift +46 -0
  352. pythonnative/storage.py +184 -0
  353. pythonnative/style.py +841 -0
  354. pythonnative/suspense.py +183 -0
  355. pythonnative/templates/android_template/app/build.gradle +75 -0
  356. pythonnative/templates/android_template/app/proguard-rules.pro +21 -0
  357. pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +24 -0
  358. pythonnative/templates/android_template/app/src/debug/AndroidManifest.xml +7 -0
  359. pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +32 -0
  360. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +130 -0
  361. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -0
  362. pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  363. pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
  364. pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
  365. pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
  366. pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
  367. pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  368. pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  369. pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  370. pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  371. pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  372. pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  373. pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  374. pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  375. pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  376. pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  377. pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
  378. pythonnative/templates/android_template/app/src/main/res/values/colors.xml +5 -0
  379. pythonnative/templates/android_template/app/src/main/res/values/strings.xml +5 -0
  380. pythonnative/templates/android_template/app/src/main/res/values/themes.xml +9 -0
  381. pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +7 -0
  382. pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +13 -0
  383. pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +19 -0
  384. pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +17 -0
  385. pythonnative/templates/android_template/build.gradle +10 -0
  386. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  387. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +6 -0
  388. pythonnative/templates/android_template/gradle.properties +23 -0
  389. pythonnative/templates/android_template/gradlew +185 -0
  390. pythonnative/templates/android_template/gradlew.bat +89 -0
  391. pythonnative/templates/android_template/settings.gradle +17 -0
  392. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +51 -0
  393. pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  394. pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
  395. pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +6 -0
  396. pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +25 -0
  397. pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
  398. pythonnative/templates/ios_template/ios_template/Info.plist +28 -0
  399. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +390 -0
  400. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +61 -0
  401. pythonnative/templates/ios_template/ios_template/ViewController.swift +23 -0
  402. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +745 -0
  403. pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  404. pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +36 -0
  405. pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +41 -0
  406. pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +32 -0
  407. pythonnative/testing/__init__.py +53 -0
  408. pythonnative/testing/backend.py +276 -0
  409. pythonnative/testing/harness.py +369 -0
  410. pythonnative/utils.py +145 -0
  411. pythonnative-0.40.0.dist-info/DELVEWHEEL +2 -0
  412. pythonnative-0.40.0.dist-info/METADATA +150 -0
  413. pythonnative-0.40.0.dist-info/RECORD +418 -0
  414. pythonnative-0.40.0.dist-info/WHEEL +5 -0
  415. pythonnative-0.40.0.dist-info/entry_points.txt +2 -0
  416. pythonnative-0.40.0.dist-info/licenses/LICENSE +21 -0
  417. pythonnative-0.40.0.dist-info/top_level.txt +1 -0
  418. pythonnative.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
@@ -0,0 +1,824 @@
1
+ """The on-device dev client: sync sources from ``pn start`` and Fast Refresh.
2
+
3
+ A debug build of a PythonNative app is a *dev client*. On launch it
4
+ connects to the dev server (``pn start`` / ``pn preview`` / ``pn run``)
5
+ over WebSocket, reports the sources it already holds, receives whatever
6
+ is missing, and from then on applies every save as a Fast Refresh. Its
7
+ ``print`` output and errors stream back to the terminal running the
8
+ server, so the device log viewer is optional.
9
+
10
+ How the pieces fit:
11
+
12
+ - The native template configures a writable **overlay** directory
13
+ (``pythonnative.hot_reload.configure_dev_environment``) ahead of the
14
+ bundled sources on ``sys.path`` and exposes the server URL the CLI
15
+ baked in as ``PN_DEV_SERVER``. ``pythonnative.bootstrap.start(dev=True)``
16
+ calls [`start_if_configured`][pythonnative.devclient.start_if_configured].
17
+ - A build made with ``pn run <platform> --dev-client`` has no app of
18
+ its own: its bundled ``app/main.py`` renders
19
+ [`ConnectScreen`][pythonnative.devclient.ConnectScreen], where the
20
+ developer types (or picks) a server URL. Once the first sync lands,
21
+ the real ``app.main`` from the overlay shadows the placeholder and
22
+ the screen remounts into the developer's app. The URL is remembered
23
+ for the next launch.
24
+
25
+ All network I/O runs on a daemon thread; file writes happen there too,
26
+ and only the reload itself hops to the main thread through
27
+ ``call_on_application_thread``.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import base64
33
+ import hashlib
34
+ import json
35
+ import os
36
+ import socket
37
+ import sys
38
+ import threading
39
+ import time
40
+ import traceback
41
+ from typing import Any, Callable, Dict, List, Optional
42
+
43
+ from .devserver import ws
44
+ from .devserver.watcher import is_synced_file, modules_for_paths
45
+
46
+ __all__ = [
47
+ "ConnectScreen",
48
+ "DevClient",
49
+ "current",
50
+ "normalize_server_url",
51
+ "saved_server_url",
52
+ "start",
53
+ "start_if_configured",
54
+ "stop",
55
+ ]
56
+
57
+ SERVER_URL_ENV = "PN_DEV_SERVER"
58
+ """Environment variable carrying the dev server URL baked in by ``pn run``."""
59
+
60
+ DEV_CLIENT_ENV = "PN_DEV_CLIENT"
61
+ """Set to ``1`` in ``--dev-client`` builds (the connect screen remembers URLs)."""
62
+
63
+ _SAVED_URL_FILE = "server.json"
64
+ _RECONNECT_DELAYS = (0.5, 1.0, 2.0, 3.0, 5.0)
65
+
66
+ Logger = Callable[[str], None]
67
+
68
+ _current: Optional["DevClient"] = None
69
+ _lock = threading.Lock()
70
+
71
+
72
+ # ======================================================================
73
+ # URLs
74
+ # ======================================================================
75
+
76
+
77
+ def normalize_server_url(text: str) -> str:
78
+ """Turn whatever the developer typed into a dev-client WebSocket URL.
79
+
80
+ Accepts ``192.168.1.20``, ``192.168.1.20:8765``, ``http://host:port``,
81
+ ``ws://host:port``, and full URLs with a path; the result always
82
+ ends in ``/ws?role=client``.
83
+ """
84
+ value = (text or "").strip()
85
+ if not value:
86
+ raise ValueError("empty server address")
87
+ if "://" not in value:
88
+ value = "ws://" + value
89
+ value = value.replace("http://", "ws://", 1).replace("https://", "wss://", 1)
90
+ scheme, _, rest = value.partition("://")
91
+ hostport, _, path = rest.partition("/")
92
+ if ":" not in hostport:
93
+ from .devserver.server import DEFAULT_PORT
94
+
95
+ hostport = f"{hostport}:{DEFAULT_PORT}"
96
+ if not path or path.startswith("?"):
97
+ path = "ws?role=client"
98
+ elif "role=" not in path:
99
+ path = path.rstrip("/")
100
+ path = (path + ("&" if "?" in path else "?") + "role=client") if path.startswith("ws") else "ws?role=client"
101
+ return f"{scheme}://{hostport}/{path}"
102
+
103
+
104
+ def _saved_url_path(overlay: str) -> str:
105
+ return os.path.join(overlay, _SAVED_URL_FILE)
106
+
107
+
108
+ def saved_server_url(overlay: Optional[str] = None) -> Optional[str]:
109
+ """The URL remembered by a previous connection (``--dev-client`` builds)."""
110
+ from .hot_reload import overlay_root
111
+
112
+ root = overlay or overlay_root()
113
+ if not root:
114
+ return None
115
+ try:
116
+ with open(_saved_url_path(root), encoding="utf-8") as handle:
117
+ data = json.load(handle)
118
+ except (OSError, ValueError):
119
+ return None
120
+ url = data.get("url") if isinstance(data, dict) else None
121
+ return str(url) if url else None
122
+
123
+
124
+ def _save_server_url(overlay: str, url: str) -> None:
125
+ try:
126
+ os.makedirs(overlay, exist_ok=True)
127
+ with open(_saved_url_path(overlay), "w", encoding="utf-8") as handle:
128
+ json.dump({"url": url, "saved_at": time.time()}, handle)
129
+ except OSError:
130
+ pass
131
+
132
+
133
+ # ======================================================================
134
+ # Log forwarding
135
+ # ======================================================================
136
+
137
+
138
+ class _Tee:
139
+ """A text stream that writes through and also forwards whole lines."""
140
+
141
+ def __init__(self, inner: Any, forward: Callable[[str], None]) -> None:
142
+ self._inner = inner
143
+ self._forward = forward
144
+ self._buffer = ""
145
+
146
+ def write(self, text: str) -> int:
147
+ try:
148
+ self._inner.write(text)
149
+ except Exception:
150
+ pass
151
+ self._buffer += text
152
+ if "\n" in self._buffer:
153
+ lines = self._buffer.split("\n")
154
+ self._buffer = lines.pop()
155
+ for line in lines:
156
+ if line:
157
+ self._forward(line)
158
+ return len(text)
159
+
160
+ def flush(self) -> None:
161
+ try:
162
+ self._inner.flush()
163
+ except Exception:
164
+ pass
165
+ if self._buffer:
166
+ line, self._buffer = self._buffer, ""
167
+ self._forward(line)
168
+
169
+ def isatty(self) -> bool:
170
+ return False
171
+
172
+ def fileno(self) -> int:
173
+ return self._inner.fileno()
174
+
175
+ def __getattr__(self, name: str) -> Any:
176
+ return getattr(self._inner, name)
177
+
178
+
179
+ # ======================================================================
180
+ # The client
181
+ # ======================================================================
182
+
183
+
184
+ class DevClient:
185
+ """Keep this process in sync with a dev server.
186
+
187
+ Args:
188
+ url: Server URL (any form accepted by
189
+ [`normalize_server_url`][pythonnative.devclient.normalize_server_url]).
190
+ overlay: Writable directory that shadows the bundled sources.
191
+ entry_module: The app's entry module, for the ``hello`` message.
192
+ forward_logs: Mirror ``print`` output to the server.
193
+ log: Local logger for the client's own status lines.
194
+ """
195
+
196
+ def __init__(
197
+ self,
198
+ url: str,
199
+ overlay: str,
200
+ *,
201
+ entry_module: str = "app.main",
202
+ forward_logs: bool = True,
203
+ log: Optional[Logger] = None,
204
+ ) -> None:
205
+ self.url = normalize_server_url(url)
206
+ self.overlay = os.path.abspath(overlay)
207
+ self.entry_module = entry_module
208
+ self._forward_logs = forward_logs
209
+ self._log: Logger = log or self._default_log
210
+ self._thread: Optional[threading.Thread] = None
211
+ self._stop = threading.Event()
212
+ self._socket: Optional[ws.WebSocketClient] = None
213
+ self._state = "idle"
214
+ self._state_lock = threading.Lock()
215
+ self._listeners: List[Callable[[str, str], None]] = []
216
+ self._outbox: List[str] = []
217
+ self._outbox_lock = threading.Lock()
218
+ self._tee_installed = False
219
+ self._orig_stdout: Any = None
220
+ self._orig_stderr: Any = None
221
+ self._device = ""
222
+ self.synced_version: Optional[str] = None
223
+ self.synced_once = threading.Event()
224
+
225
+ # -- state -----------------------------------------------------------
226
+
227
+ @property
228
+ def state(self) -> str:
229
+ """``"idle"``, ``"connecting"``, ``"connected"``, ``"syncing"``, or ``"disconnected"``."""
230
+ with self._state_lock:
231
+ return self._state
232
+
233
+ def add_listener(self, callback: Callable[[str, str], None]) -> Callable[[], None]:
234
+ """Subscribe to ``(state, detail)`` changes (called on the client thread)."""
235
+ self._listeners.append(callback)
236
+
237
+ def _remove() -> None:
238
+ try:
239
+ self._listeners.remove(callback)
240
+ except ValueError:
241
+ pass
242
+
243
+ return _remove
244
+
245
+ def _set_state(self, state: str, detail: str = "") -> None:
246
+ with self._state_lock:
247
+ self._state = state
248
+ for listener in list(self._listeners):
249
+ try:
250
+ listener(state, detail)
251
+ except Exception:
252
+ pass
253
+
254
+ # -- lifecycle -------------------------------------------------------
255
+
256
+ def start(self) -> None:
257
+ """Connect on a daemon thread (idempotent)."""
258
+ if self._thread is not None:
259
+ return
260
+ self._stop.clear()
261
+ # Native modules expect the main thread; ``start`` runs there (the
262
+ # bootstrap or a tap handler), the client thread never does.
263
+ self._device = _device_name()
264
+ self._install_tee()
265
+ self._thread = threading.Thread(target=self._run, name="pn-dev-client", daemon=True)
266
+ self._thread.start()
267
+
268
+ def stop(self) -> None:
269
+ """Disconnect and stop the thread."""
270
+ self._stop.set()
271
+ sock = self._socket
272
+ if sock is not None:
273
+ try:
274
+ sock.close()
275
+ except Exception:
276
+ pass
277
+ thread, self._thread = self._thread, None
278
+ if thread is not None and thread is not threading.current_thread():
279
+ thread.join(timeout=3.0)
280
+ self._remove_tee()
281
+ self._set_state("idle")
282
+
283
+ def _run(self) -> None:
284
+ attempt = 0
285
+ while not self._stop.is_set():
286
+ self._set_state("connecting", self.url)
287
+ try:
288
+ self._session()
289
+ attempt = 0
290
+ except (OSError, ws.WebSocketError) as exc:
291
+ detail = f"{type(exc).__name__}: {exc}"
292
+ self._set_state("disconnected", detail)
293
+ if attempt == 0:
294
+ self._log(f"[pn dev] cannot reach {self.url} ({detail}); retrying")
295
+ except Exception as exc:
296
+ self._set_state("disconnected", repr(exc))
297
+ self._log(f"[pn dev] client error: {exc!r}")
298
+ traceback.print_exc(file=self._local_stderr())
299
+ if self._stop.is_set():
300
+ break
301
+ delay = _RECONNECT_DELAYS[min(attempt, len(_RECONNECT_DELAYS) - 1)]
302
+ attempt += 1
303
+ self._stop.wait(delay)
304
+
305
+ def _session(self) -> None:
306
+ client = ws.WebSocketClient(self.url, timeout=1.0)
307
+ client.connect()
308
+ self._socket = client
309
+ try:
310
+ client.send_text(json.dumps(self._hello()))
311
+ self._set_state("connected", self.url)
312
+ self._flush_outbox()
313
+ while not self._stop.is_set():
314
+ try:
315
+ text = client.recv()
316
+ except socket.timeout:
317
+ self._flush_outbox()
318
+ continue
319
+ if text is None:
320
+ break
321
+ self._flush_outbox()
322
+ try:
323
+ message = json.loads(text)
324
+ except ValueError:
325
+ continue
326
+ if isinstance(message, dict):
327
+ self._handle(message)
328
+ finally:
329
+ self._socket = None
330
+ try:
331
+ client.close()
332
+ except Exception:
333
+ pass
334
+ if not self._stop.is_set():
335
+ self._set_state("disconnected", "connection closed")
336
+
337
+ # -- protocol --------------------------------------------------------
338
+
339
+ def _hello(self) -> Dict[str, Any]:
340
+ from .platform import Platform
341
+
342
+ self._seed_overlay_from_bundle()
343
+ return {
344
+ "type": "hello",
345
+ "platform": Platform.OS,
346
+ "device": self._device,
347
+ "app": self.entry_module,
348
+ "files": self._overlay_manifest(),
349
+ }
350
+
351
+ def _seed_overlay_from_bundle(self) -> None:
352
+ """Copy the bundled sources into an empty overlay before the first ``hello``.
353
+
354
+ The overlay must be a complete tree (a partial ``app/`` without
355
+ ``__init__.py`` would lose to the bundled package on ``sys.path``),
356
+ so the first sync used to download every file and reload every
357
+ module even when the build was made from the very sources the
358
+ server holds. Seeding from the bundle lets the manifest report
359
+ what the app already runs; the server then sends only real
360
+ differences, and an up-to-date build connects without a reload.
361
+ """
362
+ top = self.entry_module.split(".")[0]
363
+ if not top or self._overlay_has_files(top):
364
+ return
365
+ root = _bundled_package_root(top, exclude=self.overlay)
366
+ if root is None:
367
+ self._log(f"[pn dev] bundled sources for '{top}' not found; the server will send everything")
368
+ return
369
+ seeded = 0
370
+
371
+ def copy_tree(node: Any, rel: str) -> None:
372
+ nonlocal seeded
373
+ try:
374
+ children = list(node.iterdir())
375
+ except Exception as exc:
376
+ self._log(f"[pn dev] could not list bundled {rel}: {exc}")
377
+ return
378
+ for child in children:
379
+ child_rel = f"{rel}/{child.name}"
380
+ if child.is_dir():
381
+ if child.name != "__pycache__":
382
+ copy_tree(child, child_rel)
383
+ continue
384
+ if not is_synced_file(child_rel):
385
+ continue
386
+ target = os.path.join(self.overlay, *child_rel.split("/"))
387
+ try:
388
+ data = child.read_bytes()
389
+ os.makedirs(os.path.dirname(target), exist_ok=True)
390
+ with open(target, "wb") as handle:
391
+ handle.write(data)
392
+ seeded += 1
393
+ except Exception as exc:
394
+ self._log(f"[pn dev] could not seed {child_rel}: {exc}")
395
+
396
+ copy_tree(root, top)
397
+ self._log(f"[pn dev] seeded the overlay with {seeded} bundled file(s) from {root}")
398
+
399
+ def _overlay_has_files(self, top: str) -> bool:
400
+ """Whether the overlay already holds sources for ``top``.
401
+
402
+ ``configure_dev_environment`` pre-creates the (empty) package
403
+ directory, so the directory's existence says nothing; byte-code
404
+ caches don't count either.
405
+ """
406
+ for dirpath, dirnames, filenames in os.walk(os.path.join(self.overlay, top)):
407
+ dirnames[:] = [d for d in dirnames if d != "__pycache__"]
408
+ if filenames:
409
+ return True
410
+ return False
411
+
412
+ def _overlay_manifest(self) -> Dict[str, str]:
413
+ files: Dict[str, str] = {}
414
+ base = self.overlay
415
+ for dirpath, dirnames, filenames in os.walk(base):
416
+ dirnames[:] = [d for d in dirnames if d != "__pycache__"]
417
+ for name in filenames:
418
+ full = os.path.join(dirpath, name)
419
+ rel = os.path.relpath(full, base).replace(os.sep, "/")
420
+ if rel == _SAVED_URL_FILE or not is_synced_file(rel):
421
+ continue
422
+ try:
423
+ with open(full, "rb") as handle:
424
+ files[rel] = hashlib.sha256(handle.read()).hexdigest()
425
+ except OSError:
426
+ continue
427
+ return files
428
+
429
+ def _handle(self, message: Dict[str, Any]) -> None:
430
+ kind = message.get("type")
431
+ if kind not in ("sync", "update"):
432
+ return
433
+ self._set_state("syncing", str(message.get("version") or ""))
434
+ files = message.get("files") or []
435
+ removed = message.get("removed") or []
436
+ written: List[str] = []
437
+ for entry in files:
438
+ if not isinstance(entry, dict):
439
+ continue
440
+ path = str(entry.get("path") or "")
441
+ if not path or not self._safe_relpath(path):
442
+ continue
443
+ data = _decode_content(entry)
444
+ if data is None:
445
+ continue
446
+ target = os.path.join(self.overlay, *path.split("/"))
447
+ try:
448
+ os.makedirs(os.path.dirname(target), exist_ok=True)
449
+ with open(target, "wb") as handle:
450
+ handle.write(data)
451
+ written.append(path)
452
+ except OSError as exc:
453
+ self._log(f"[pn dev] could not write {path}: {exc}")
454
+ for path in removed:
455
+ rel = str(path)
456
+ if not self._safe_relpath(rel):
457
+ continue
458
+ try:
459
+ os.remove(os.path.join(self.overlay, *rel.split("/")))
460
+ except OSError:
461
+ pass
462
+ written.append(rel)
463
+ version = str(message.get("version") or "")
464
+ self.synced_version = version
465
+ self.synced_once.set()
466
+ _save_server_url(self.overlay, self.url)
467
+ self._set_state("connected", version)
468
+ if not written:
469
+ # The app already runs these exact sources (a fresh build, or an
470
+ # overlay from the last session): nothing to reload.
471
+ return
472
+ self._log(f"[pn dev] synced {len(written)} file(s) from {self.url}")
473
+ modules = modules_for_paths(written)
474
+ if modules:
475
+ self._schedule_reload(modules, version)
476
+
477
+ def _schedule_reload(self, modules: List[str], version: str) -> None:
478
+ from .runtime import call_on_application_thread
479
+
480
+ def _apply() -> None:
481
+ from .hot_reload import apply_reload
482
+
483
+ result = apply_reload(modules)
484
+ if result.mode == "error":
485
+ self.send({"type": "error", "phase": "hot reload", "text": result.error or "unknown error"})
486
+ elif result.mode != "none":
487
+ self.send(
488
+ {"type": "reloaded", "version": version, "mode": result.mode, "modules": result.reloaded or modules}
489
+ )
490
+
491
+ call_on_application_thread(_apply)
492
+
493
+ def _safe_relpath(self, rel: str) -> bool:
494
+ parts = rel.split("/")
495
+ return bool(parts) and all(part and part not in (".", "..") for part in parts)
496
+
497
+ # -- outbound --------------------------------------------------------
498
+
499
+ def send(self, message: Dict[str, Any]) -> None:
500
+ """Queue a message to the server (dropped when the outbox overflows offline)."""
501
+ text = json.dumps(message)
502
+ with self._outbox_lock:
503
+ if len(self._outbox) > 500:
504
+ del self._outbox[:100]
505
+ self._outbox.append(text)
506
+ sock = self._socket
507
+ if sock is not None and threading.current_thread() is self._thread:
508
+ self._flush_outbox()
509
+
510
+ def _flush_outbox(self) -> None:
511
+ sock = self._socket
512
+ if sock is None:
513
+ return
514
+ while True:
515
+ with self._outbox_lock:
516
+ if not self._outbox:
517
+ return
518
+ text = self._outbox.pop(0)
519
+ try:
520
+ sock.send_text(text)
521
+ except Exception:
522
+ with self._outbox_lock:
523
+ self._outbox.insert(0, text)
524
+ return
525
+
526
+ def log(self, text: str, level: str = "info") -> None:
527
+ """Forward one log line to the server."""
528
+ self.send({"type": "log", "level": level, "text": text})
529
+
530
+ def report_error(self, phase: str, text: str) -> None:
531
+ """Forward an error report (a traceback) to the server."""
532
+ self.send({"type": "error", "phase": phase, "text": text})
533
+
534
+ # -- stdout / stderr tee -----------------------------------------------
535
+
536
+ def _local_stderr(self) -> Any:
537
+ """The process's real stderr, bypassing the tee (so status lines stay local).
538
+
539
+ Not ``sys.__stderr__``: embedded interpreters replace ``sys.stderr``
540
+ with a stream that reaches the platform log (logcat, the Xcode
541
+ console) while the original file descriptor goes nowhere.
542
+ """
543
+ stream = sys.stderr
544
+ while isinstance(stream, _Tee):
545
+ stream = stream._inner
546
+ return stream if stream is not None else sys.__stderr__
547
+
548
+ def _default_log(self, line: str) -> None:
549
+ try:
550
+ print(line, file=self._local_stderr(), flush=True)
551
+ except Exception:
552
+ pass
553
+
554
+ def _install_tee(self) -> None:
555
+ if not self._forward_logs or self._tee_installed:
556
+ return
557
+ self._tee_installed = True
558
+ self._orig_stdout, self._orig_stderr = sys.stdout, sys.stderr
559
+ sys.stdout = _Tee(self._orig_stdout, lambda line: self.log(line, "info"))
560
+ sys.stderr = _Tee(self._orig_stderr, lambda line: self.log(line, "error"))
561
+
562
+ def _remove_tee(self) -> None:
563
+ if not self._tee_installed:
564
+ return
565
+ self._tee_installed = False
566
+ if isinstance(sys.stdout, _Tee):
567
+ sys.stdout = self._orig_stdout
568
+ if isinstance(sys.stderr, _Tee):
569
+ sys.stderr = self._orig_stderr
570
+
571
+
572
+ def _device_name() -> str:
573
+ """A short device label for the server's client list (``iPhone``, ``Pixel 8``)."""
574
+ try:
575
+ from .native_modules.registry import native_module
576
+
577
+ info = native_module("Device").call("info")
578
+ if isinstance(info, dict):
579
+ return str(info.get("model") or info.get("os") or "")
580
+ except Exception:
581
+ pass
582
+ return ""
583
+
584
+
585
+ def _bundled_package_root(top: str, *, exclude: str) -> Optional[Any]:
586
+ """A ``Traversable`` for the bundled top-level package ``top``, ignoring the overlay.
587
+
588
+ Resolves through ``sys.path`` the way the import system would once the
589
+ overlay is out of the picture, without importing anything, then asks
590
+ the loader for its resource reader. On iOS that is a plain directory;
591
+ under Chaquopy the ``.py`` sources live in the APK's asset archive and
592
+ only the reader can list and read them (``os.walk`` on the extracted
593
+ directory sees data files alone).
594
+ """
595
+ import importlib.machinery
596
+
597
+ excluded = os.path.abspath(exclude)
598
+ search = [p for p in sys.path if p and os.path.abspath(p) != excluded]
599
+ try:
600
+ spec = importlib.machinery.PathFinder.find_spec(top, search)
601
+ except Exception:
602
+ return None
603
+ if spec is None or not spec.submodule_search_locations or spec.loader is None:
604
+ return None
605
+ get_reader = getattr(spec.loader, "get_resource_reader", None)
606
+ if get_reader is None:
607
+ return None
608
+ try:
609
+ reader = get_reader(top)
610
+ files = getattr(reader, "files", None)
611
+ root = files() if files is not None else None
612
+ except Exception:
613
+ return None
614
+ if root is None or not root.is_dir():
615
+ return None
616
+ return root
617
+
618
+
619
+ def _decode_content(entry: Dict[str, Any]) -> Optional[bytes]:
620
+ content = entry.get("content")
621
+ if not isinstance(content, str):
622
+ return None
623
+ if entry.get("encoding") == "base64":
624
+ try:
625
+ return base64.b64decode(content)
626
+ except (ValueError, TypeError):
627
+ return None
628
+ return content.encode("utf-8")
629
+
630
+
631
+ # ======================================================================
632
+ # Process-wide client
633
+ # ======================================================================
634
+
635
+
636
+ def current() -> Optional[DevClient]:
637
+ """The running dev client, if any."""
638
+ return _current
639
+
640
+
641
+ def start(url: str, overlay: Optional[str] = None, **kwargs: Any) -> DevClient:
642
+ """Start (or replace) the process-wide dev client for ``url``."""
643
+ global _current
644
+ from .hot_reload import configure_dev_environment, overlay_root
645
+
646
+ root = overlay or overlay_root()
647
+ if root is None:
648
+ root = configure_dev_environment(os.path.join(os.path.expanduser("~"), ".pythonnative"))
649
+ with _lock:
650
+ previous = _current
651
+ if previous is not None:
652
+ previous.stop()
653
+ client = DevClient(url, root, **kwargs)
654
+ _current = client
655
+ client.start()
656
+ return client
657
+
658
+
659
+ def stop() -> None:
660
+ """Stop the process-wide dev client."""
661
+ global _current
662
+ with _lock:
663
+ client, _current = _current, None
664
+ if client is not None:
665
+ client.stop()
666
+
667
+
668
+ def start_if_configured(entry_module: Optional[str] = None) -> Optional[DevClient]:
669
+ """Start the dev client when the build points at a server.
670
+
671
+ Called by ``bootstrap.start(dev=True)``. The URL comes from
672
+ ``PN_DEV_SERVER`` (baked in by ``pn run``) or, for ``--dev-client``
673
+ builds, from the URL saved by a previous connection. Returns the
674
+ client, or ``None`` when nothing is configured.
675
+ """
676
+ from .hot_reload import overlay_root
677
+
678
+ root = overlay_root()
679
+ if root is None:
680
+ return None
681
+ url = os.environ.get(SERVER_URL_ENV) or saved_server_url(root)
682
+ if not url:
683
+ return None
684
+ entry = entry_module or os.environ.get("PN_ENTRY_MODULE") or "app.main"
685
+ try:
686
+ return start(url, root, entry_module=entry)
687
+ except Exception as exc:
688
+ print(f"[pn dev] could not start the dev client: {exc!r}", file=sys.stderr)
689
+ return None
690
+
691
+
692
+ # ======================================================================
693
+ # Connect screen (dev-client builds)
694
+ # ======================================================================
695
+
696
+
697
+ def _connect_screen() -> Any:
698
+ from . import components as c
699
+ from .component import component
700
+ from .hooks import use_effect, use_state
701
+
702
+ @component
703
+ def ConnectScreen() -> Any:
704
+ client = current()
705
+ initial = client.url if client is not None else (saved_server_url() or "")
706
+ url, set_url = use_state(initial.replace("/ws?role=client", "") if initial else "")
707
+ status, set_status = use_state(client.state if client is not None else "idle")
708
+ detail, set_detail = use_state("")
709
+
710
+ def _subscribe() -> Optional[Callable[[], None]]:
711
+ live = current()
712
+ if live is None:
713
+ return None
714
+
715
+ def _on_state(state: str, info: str) -> None:
716
+ from .runtime import call_on_application_thread
717
+
718
+ def _update() -> None:
719
+ set_status(state)
720
+ set_detail(info)
721
+
722
+ call_on_application_thread(_update)
723
+
724
+ return live.add_listener(_on_state)
725
+
726
+ use_effect(_subscribe, [client])
727
+
728
+ def _connect() -> None:
729
+ try:
730
+ normalized = normalize_server_url(url)
731
+ except ValueError as exc:
732
+ set_status("error")
733
+ set_detail(str(exc))
734
+ return
735
+ start(normalized)
736
+ set_status("connecting")
737
+ set_detail(normalized)
738
+
739
+ lan_hint = "Run `pn start` on your computer and enter its address (for example 192.168.1.20:8765)."
740
+ colors = {
741
+ "idle": "#8E8E93",
742
+ "connecting": "#FF9F0A",
743
+ "connected": "#30D158",
744
+ "syncing": "#0A84FF",
745
+ "disconnected": "#FF453A",
746
+ "error": "#FF453A",
747
+ }
748
+ return c.Column(
749
+ c.Column(
750
+ c.Text("PythonNative", style={"font_size": 30, "bold": True, "color": "#FFFFFF"}),
751
+ c.Text("Dev client", style={"font_size": 17, "color": "#8E8E93"}),
752
+ style={"spacing": 4, "padding_top": 72},
753
+ ),
754
+ c.Column(
755
+ c.Text("Dev server address", style={"font_size": 13, "color": "#8E8E93"}),
756
+ c.TextInput(
757
+ value=url,
758
+ on_change=set_url,
759
+ placeholder="192.168.1.20:8765",
760
+ auto_correct=False,
761
+ auto_capitalize="none",
762
+ keyboard_type="url",
763
+ style={
764
+ "background_color": "#1C1C1E",
765
+ "color": "#FFFFFF",
766
+ "padding": 12,
767
+ "border_radius": 10,
768
+ "font_size": 16,
769
+ },
770
+ ),
771
+ c.Button(
772
+ "Connect",
773
+ on_press=_connect,
774
+ style={"background_color": "#0A84FF", "color": "#FFFFFF", "padding": 12, "border_radius": 10},
775
+ ),
776
+ style={"spacing": 8, "align_items": "stretch"},
777
+ ),
778
+ c.Column(
779
+ c.Row(
780
+ c.View(
781
+ style={"width": 10, "height": 10, "border_radius": 5, "background_color": colors.get(status)}
782
+ ),
783
+ c.Text(status, style={"color": "#FFFFFF", "font_size": 15}),
784
+ style={"spacing": 8, "align_items": "center"},
785
+ ),
786
+ c.Text(detail, style={"color": "#8E8E93", "font_size": 12}),
787
+ style={"spacing": 4},
788
+ ),
789
+ c.Text(lan_hint, style={"color": "#636366", "font_size": 12}),
790
+ style={"flex": 1, "padding": 24, "spacing": 28, "background_color": "#000000"},
791
+ )
792
+
793
+ return ConnectScreen
794
+
795
+
796
+ class _LazyConnectScreen:
797
+ """Import-light proxy so ``from pythonnative.devclient import ConnectScreen`` stays cheap."""
798
+
799
+ _component: Any = None
800
+
801
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
802
+ if _LazyConnectScreen._component is None:
803
+ _LazyConnectScreen._component = _connect_screen()
804
+ return _LazyConnectScreen._component(*args, **kwargs)
805
+
806
+ def __getattr__(self, name: str) -> Any:
807
+ if _LazyConnectScreen._component is None:
808
+ _LazyConnectScreen._component = _connect_screen()
809
+ return getattr(_LazyConnectScreen._component, name)
810
+
811
+
812
+ ConnectScreen: Any = _LazyConnectScreen()
813
+ """Root component of ``--dev-client`` builds until the first sync arrives."""
814
+
815
+
816
+ def placeholder_main_source() -> str:
817
+ """Source of the ``app/main.py`` staged into ``--dev-client`` builds."""
818
+ return (
819
+ '"""Placeholder entry module for a PythonNative dev-client build.\n\n'
820
+ "The real app arrives from the dev server and shadows this file.\n"
821
+ '"""\n\n'
822
+ "from pythonnative.devclient import ConnectScreen as App\n\n"
823
+ '__all__ = ["App"]\n'
824
+ )