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.
- pythonnative/__init__.py +411 -0
- pythonnative/_ios_log.py +92 -0
- pythonnative/_yoga.cp313-win_amd64.pyd +0 -0
- pythonnative/alerts.py +236 -0
- pythonnative/animated.py +1687 -0
- pythonnative/animation_graph.py +78 -0
- pythonnative/appearance.py +124 -0
- pythonnative/bootstrap.py +100 -0
- pythonnative/bridge/__init__.py +302 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +170 -0
- pythonnative/bridge/commits.py +114 -0
- pythonnative/bridge/fake.py +243 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/bridge/web.py +489 -0
- pythonnative/cli/__init__.py +7 -0
- pythonnative/cli/pn.py +1214 -0
- pythonnative/component.py +271 -0
- pythonnative/components/__init__.py +111 -0
- pythonnative/components/_base.py +166 -0
- pythonnative/components/controls.py +521 -0
- pythonnative/components/layout.py +518 -0
- pythonnative/components/lists.py +785 -0
- pythonnative/components/media.py +204 -0
- pythonnative/components/overlays.py +107 -0
- pythonnative/components/pressable.py +252 -0
- pythonnative/components/structural.py +167 -0
- pythonnative/components/text.py +291 -0
- pythonnative/devclient.py +824 -0
- pythonnative/devserver/__init__.py +34 -0
- pythonnative/devserver/server.py +718 -0
- pythonnative/devserver/static/animation_graph.js +82 -0
- pythonnative/devserver/static/bridge.js +147 -0
- pythonnative/devserver/static/colors.js +70 -0
- pythonnative/devserver/static/host.js +552 -0
- pythonnative/devserver/static/index.html +60 -0
- pythonnative/devserver/static/layout.js +85 -0
- pythonnative/devserver/static/preview.css +415 -0
- pythonnative/devserver/static/renderer.js +1994 -0
- pythonnative/devserver/static/shell.js +345 -0
- pythonnative/devserver/static/yoga/LICENSE +21 -0
- pythonnative/devserver/static/yoga/binaries/yoga-wasm-base64-esm.js +75 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.d.ts +189 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.js +211 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.js.map +1 -0
- pythonnative/devserver/static/yoga/src/index.d.ts +12 -0
- pythonnative/devserver/static/yoga/src/index.js +16 -0
- pythonnative/devserver/static/yoga/src/index.js.map +1 -0
- pythonnative/devserver/static/yoga/src/load.d.ts +11 -0
- pythonnative/devserver/static/yoga/src/load.js +17 -0
- pythonnative/devserver/static/yoga/src/load.js.map +1 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.d.ts +155 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.js +125 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.js.map +1 -0
- pythonnative/devserver/watcher.py +267 -0
- pythonnative/devserver/ws.py +421 -0
- pythonnative/diagnostics.py +241 -0
- pythonnative/element.py +159 -0
- pythonnative/equality.py +19 -0
- pythonnative/events.py +231 -0
- pythonnative/gestures.py +1333 -0
- pythonnative/hooks.py +1621 -0
- pythonnative/hosts/__init__.py +63 -0
- pythonnative/hosts/base.py +596 -0
- pythonnative/hosts/native.py +302 -0
- pythonnative/hot_reload.py +585 -0
- pythonnative/layout.py +328 -0
- pythonnative/mutations.py +142 -0
- pythonnative/native/android/build.gradle +52 -0
- pythonnative/native/android/gradle.properties +3 -0
- pythonnative/native/android/settings.gradle +12 -0
- pythonnative/native/android/src/main/AndroidManifest.xml +11 -0
- pythonnative/native/android/src/main/cpp/CMakeLists.txt +9 -0
- pythonnative/native/android/src/main/cpp/yoga_jni.cpp +52 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeModules.kt +5 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeProps.kt +3705 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/PNContracts.kt +129 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/PNBridge.kt +261 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationGraph.kt +108 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +257 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/CommitState.kt +106 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +40 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +117 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +37 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +32 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +162 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +86 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScreenStackManager.kt +110 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +250 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +159 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/NativeLayout.kt +145 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/YogaNode.kt +29 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +61 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +158 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +188 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNAccessibilityDelegate.kt +48 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNBorderDrawable.kt +76 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNFrameLayout.kt +49 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PromiseTest.kt +89 -0
- pythonnative/native/ios/Package.swift +23 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimationGraph.swift +199 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimator.swift +312 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNBridge.swift +255 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNCommit.swift +104 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNRegistry.swift +142 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNTransaction.swift +166 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNButtonManagers.swift +207 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNImageManager.swift +222 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScreenStackManager.swift +107 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextInputManager.swift +297 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +155 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeModules.swift +1 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeProps.swift +3775 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/PNContracts.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Layout/PNLayout.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/HostModule.swift +80 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/PNNativeModule.swift +168 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNViewController.swift +237 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNAnimationGraphTests.swift +26 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNCommitTests.swift +86 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNManagerTests.swift +126 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNModuleTests.swift +111 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/native/yoga/LICENSE +21 -0
- pythonnative/native/yoga/Package.swift +9 -0
- pythonnative/native/yoga/VENDORED.md +2 -0
- pythonnative/native/yoga/include/PNStyle.h +10 -0
- pythonnative/native/yoga/include/module.modulemap +5 -0
- pythonnative/native/yoga/include/yoga/YGConfig.h +158 -0
- pythonnative/native/yoga/include/yoga/YGEnums.h +142 -0
- pythonnative/native/yoga/include/yoga/YGMacros.h +99 -0
- pythonnative/native/yoga/include/yoga/YGNode.h +304 -0
- pythonnative/native/yoga/include/yoga/YGNodeLayout.h +35 -0
- pythonnative/native/yoga/include/yoga/YGNodeStyle.h +130 -0
- pythonnative/native/yoga/include/yoga/YGPixelGrid.h +29 -0
- pythonnative/native/yoga/include/yoga/YGValue.h +84 -0
- pythonnative/native/yoga/include/yoga/Yoga.h +21 -0
- pythonnative/native/yoga/python.cpp +16 -0
- pythonnative/native/yoga/style.cpp +272 -0
- pythonnative/native/yoga/yoga/CMakeLists.txt +41 -0
- pythonnative/native/yoga/yoga/YGConfig.cpp +92 -0
- pythonnative/native/yoga/yoga/YGConfig.h +158 -0
- pythonnative/native/yoga/yoga/YGEnums.cpp +262 -0
- pythonnative/native/yoga/yoga/YGEnums.h +142 -0
- pythonnative/native/yoga/yoga/YGMacros.h +99 -0
- pythonnative/native/yoga/yoga/YGNode.cpp +366 -0
- pythonnative/native/yoga/yoga/YGNode.h +304 -0
- pythonnative/native/yoga/yoga/YGNodeLayout.cpp +92 -0
- pythonnative/native/yoga/yoga/YGNodeLayout.h +35 -0
- pythonnative/native/yoga/yoga/YGNodeStyle.cpp +405 -0
- pythonnative/native/yoga/yoga/YGNodeStyle.h +130 -0
- pythonnative/native/yoga/yoga/YGPixelGrid.cpp +22 -0
- pythonnative/native/yoga/yoga/YGPixelGrid.h +29 -0
- pythonnative/native/yoga/yoga/YGValue.cpp +20 -0
- pythonnative/native/yoga/yoga/YGValue.h +84 -0
- pythonnative/native/yoga/yoga/Yoga.h +21 -0
- pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.cpp +563 -0
- pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.h +41 -0
- pythonnative/native/yoga/yoga/algorithm/Align.h +72 -0
- pythonnative/native/yoga/yoga/algorithm/Baseline.cpp +78 -0
- pythonnative/native/yoga/yoga/algorithm/Baseline.h +21 -0
- pythonnative/native/yoga/yoga/algorithm/BoundAxis.h +79 -0
- pythonnative/native/yoga/yoga/algorithm/Cache.cpp +121 -0
- pythonnative/native/yoga/yoga/algorithm/Cache.h +30 -0
- pythonnative/native/yoga/yoga/algorithm/CalculateLayout.cpp +2428 -0
- pythonnative/native/yoga/yoga/algorithm/CalculateLayout.h +38 -0
- pythonnative/native/yoga/yoga/algorithm/FlexDirection.h +120 -0
- pythonnative/native/yoga/yoga/algorithm/FlexLine.cpp +124 -0
- pythonnative/native/yoga/yoga/algorithm/FlexLine.h +75 -0
- pythonnative/native/yoga/yoga/algorithm/PixelGrid.cpp +132 -0
- pythonnative/native/yoga/yoga/algorithm/PixelGrid.h +29 -0
- pythonnative/native/yoga/yoga/algorithm/SizingMode.h +73 -0
- pythonnative/native/yoga/yoga/algorithm/TrailingPosition.h +44 -0
- pythonnative/native/yoga/yoga/config/Config.cpp +135 -0
- pythonnative/native/yoga/yoga/config/Config.h +92 -0
- pythonnative/native/yoga/yoga/debug/AssertFatal.cpp +53 -0
- pythonnative/native/yoga/yoga/debug/AssertFatal.h +29 -0
- pythonnative/native/yoga/yoga/debug/Log.cpp +107 -0
- pythonnative/native/yoga/yoga/debug/Log.h +34 -0
- pythonnative/native/yoga/yoga/enums/Align.h +47 -0
- pythonnative/native/yoga/yoga/enums/BoxSizing.h +40 -0
- pythonnative/native/yoga/yoga/enums/Dimension.h +40 -0
- pythonnative/native/yoga/yoga/enums/Direction.h +41 -0
- pythonnative/native/yoga/yoga/enums/Display.h +41 -0
- pythonnative/native/yoga/yoga/enums/Edge.h +47 -0
- pythonnative/native/yoga/yoga/enums/Errata.h +41 -0
- pythonnative/native/yoga/yoga/enums/ExperimentalFeature.h +39 -0
- pythonnative/native/yoga/yoga/enums/FlexDirection.h +42 -0
- pythonnative/native/yoga/yoga/enums/Gutter.h +41 -0
- pythonnative/native/yoga/yoga/enums/Justify.h +44 -0
- pythonnative/native/yoga/yoga/enums/LogLevel.h +44 -0
- pythonnative/native/yoga/yoga/enums/MeasureMode.h +41 -0
- pythonnative/native/yoga/yoga/enums/NodeType.h +40 -0
- pythonnative/native/yoga/yoga/enums/Overflow.h +41 -0
- pythonnative/native/yoga/yoga/enums/PhysicalEdge.h +21 -0
- pythonnative/native/yoga/yoga/enums/PositionType.h +41 -0
- pythonnative/native/yoga/yoga/enums/Unit.h +42 -0
- pythonnative/native/yoga/yoga/enums/Wrap.h +41 -0
- pythonnative/native/yoga/yoga/enums/YogaEnums.h +85 -0
- pythonnative/native/yoga/yoga/event/event.cpp +87 -0
- pythonnative/native/yoga/yoga/event/event.h +130 -0
- pythonnative/native/yoga/yoga/module.modulemap +21 -0
- pythonnative/native/yoga/yoga/node/CachedMeasurement.h +53 -0
- pythonnative/native/yoga/yoga/node/LayoutResults.cpp +48 -0
- pythonnative/native/yoga/yoga/node/LayoutResults.h +122 -0
- pythonnative/native/yoga/yoga/node/LayoutableChildren.h +148 -0
- pythonnative/native/yoga/yoga/node/Node.cpp +443 -0
- pythonnative/native/yoga/yoga/node/Node.h +337 -0
- pythonnative/native/yoga/yoga/numeric/Comparison.h +81 -0
- pythonnative/native/yoga/yoga/numeric/FloatOptional.h +93 -0
- pythonnative/native/yoga/yoga/style/SmallValueBuffer.h +133 -0
- pythonnative/native/yoga/yoga/style/Style.h +757 -0
- pythonnative/native/yoga/yoga/style/StyleLength.h +107 -0
- pythonnative/native/yoga/yoga/style/StyleValueHandle.h +98 -0
- pythonnative/native/yoga/yoga/style/StyleValuePool.h +127 -0
- pythonnative/native_modules/__init__.py +124 -0
- pythonnative/native_modules/app_state.py +99 -0
- pythonnative/native_modules/battery.py +75 -0
- pythonnative/native_modules/biometrics.py +42 -0
- pythonnative/native_modules/camera.py +65 -0
- pythonnative/native_modules/clipboard.py +46 -0
- pythonnative/native_modules/fallback.py +324 -0
- pythonnative/native_modules/file_system.py +145 -0
- pythonnative/native_modules/haptics.py +75 -0
- pythonnative/native_modules/linking.py +123 -0
- pythonnative/native_modules/location.py +75 -0
- pythonnative/native_modules/net_info.py +112 -0
- pythonnative/native_modules/notifications.py +129 -0
- pythonnative/native_modules/permissions.py +108 -0
- pythonnative/native_modules/registry.py +526 -0
- pythonnative/native_modules/secure_store.py +52 -0
- pythonnative/native_modules/share.py +51 -0
- pythonnative/native_views/__init__.py +375 -0
- pythonnative/native_views/base.py +316 -0
- pythonnative/native_views/bridge_backend.py +277 -0
- pythonnative/navigation/__init__.py +103 -0
- pythonnative/navigation/container.py +154 -0
- pythonnative/navigation/handle.py +540 -0
- pythonnative/navigation/hooks.py +133 -0
- pythonnative/navigation/host.py +58 -0
- pythonnative/navigation/linking.py +200 -0
- pythonnative/navigation/navigators.py +659 -0
- pythonnative/navigation/screen.py +149 -0
- pythonnative/navigation/state.py +269 -0
- pythonnative/net.py +248 -0
- pythonnative/platform.py +166 -0
- pythonnative/platform_metrics.py +252 -0
- pythonnative/preview.py +287 -0
- pythonnative/profiling.py +101 -0
- pythonnative/project/__init__.py +68 -0
- pythonnative/project/android.py +557 -0
- pythonnative/project/builder.py +817 -0
- pythonnative/project/config.py +773 -0
- pythonnative/project/deps.py +719 -0
- pythonnative/project/devices.py +304 -0
- pythonnative/project/doctor.py +283 -0
- pythonnative/project/fingerprint.py +171 -0
- pythonnative/project/icons.py +247 -0
- pythonnative/project/ios.py +325 -0
- pythonnative/project/lockfile.py +100 -0
- pythonnative/project/permissions.py +361 -0
- pythonnative/project/plugins.py +519 -0
- pythonnative/project/runtime_assets.py +183 -0
- pythonnative/py.typed +0 -0
- pythonnative/query.py +153 -0
- pythonnative/reconciler/__init__.py +29 -0
- pythonnative/reconciler/boundaries.py +375 -0
- pythonnative/reconciler/children.py +88 -0
- pythonnative/reconciler/core.py +1165 -0
- pythonnative/reconciler/layout_pass.py +393 -0
- pythonnative/reconciler/vnode.py +266 -0
- pythonnative/refresh.py +55 -0
- pythonnative/runtime.py +306 -0
- pythonnative/scheduler.py +159 -0
- pythonnative/sdk/__init__.py +153 -0
- pythonnative/sdk/_components.py +427 -0
- pythonnative/sdk/builtins.py +91 -0
- pythonnative/sdk/codegen.py +149 -0
- pythonnative/sdk/module_codegen.py +184 -0
- pythonnative/sdk/schema.py +224 -0
- pythonnative/sdk/templates/contracts.kt +46 -0
- pythonnative/sdk/templates/contracts.swift +46 -0
- pythonnative/storage.py +184 -0
- pythonnative/style.py +841 -0
- pythonnative/suspense.py +183 -0
- pythonnative/templates/android_template/app/build.gradle +75 -0
- pythonnative/templates/android_template/app/proguard-rules.pro +21 -0
- pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +24 -0
- pythonnative/templates/android_template/app/src/debug/AndroidManifest.xml +7 -0
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +32 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +130 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -0
- pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
- pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
- pythonnative/templates/android_template/app/src/main/res/values/colors.xml +5 -0
- pythonnative/templates/android_template/app/src/main/res/values/strings.xml +5 -0
- pythonnative/templates/android_template/app/src/main/res/values/themes.xml +9 -0
- pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +7 -0
- pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +13 -0
- pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +19 -0
- pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +17 -0
- pythonnative/templates/android_template/build.gradle +10 -0
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +6 -0
- pythonnative/templates/android_template/gradle.properties +23 -0
- pythonnative/templates/android_template/gradlew +185 -0
- pythonnative/templates/android_template/gradlew.bat +89 -0
- pythonnative/templates/android_template/settings.gradle +17 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +51 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +6 -0
- pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +25 -0
- pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
- pythonnative/templates/ios_template/ios_template/Info.plist +28 -0
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +390 -0
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +61 -0
- pythonnative/templates/ios_template/ios_template/ViewController.swift +23 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +745 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +36 -0
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +41 -0
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +32 -0
- pythonnative/testing/__init__.py +53 -0
- pythonnative/testing/backend.py +276 -0
- pythonnative/testing/harness.py +369 -0
- pythonnative/utils.py +145 -0
- pythonnative-0.40.0.dist-info/DELVEWHEEL +2 -0
- pythonnative-0.40.0.dist-info/METADATA +150 -0
- pythonnative-0.40.0.dist-info/RECORD +418 -0
- pythonnative-0.40.0.dist-info/WHEEL +5 -0
- pythonnative-0.40.0.dist-info/entry_points.txt +2 -0
- pythonnative-0.40.0.dist-info/licenses/LICENSE +21 -0
- pythonnative-0.40.0.dist-info/top_level.txt +1 -0
- pythonnative.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Device and simulator discovery for ``pn devices`` / ``pn run --device``.
|
|
2
|
+
|
|
3
|
+
Enumerates the three kinds of targets a PythonNative app can run on:
|
|
4
|
+
|
|
5
|
+
- **Android devices and emulators** via ``adb devices -l``.
|
|
6
|
+
- **iOS Simulators** via ``xcrun simctl list devices --json``.
|
|
7
|
+
- **Physical iOS devices** via ``xcrun devicectl list devices`` (Xcode
|
|
8
|
+
15+; earlier Xcodes simply report no physical devices).
|
|
9
|
+
|
|
10
|
+
The shell-outs are isolated in ``list_*`` functions; the parsers are
|
|
11
|
+
pure functions over captured text/JSON so they can be unit tested
|
|
12
|
+
without any toolchain installed.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import tempfile
|
|
21
|
+
from dataclasses import asdict, dataclass
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Device",
|
|
27
|
+
"list_devices",
|
|
28
|
+
"list_android_devices",
|
|
29
|
+
"list_ios_simulators",
|
|
30
|
+
"list_ios_physical_devices",
|
|
31
|
+
"find_device",
|
|
32
|
+
"parse_adb_devices",
|
|
33
|
+
"parse_simctl_devices",
|
|
34
|
+
"parse_devicectl_devices",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Device:
|
|
40
|
+
"""One runnable target.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
platform: ``"android"`` or ``"ios"``.
|
|
44
|
+
kind: ``"device"``, ``"emulator"``, or ``"simulator"``.
|
|
45
|
+
identifier: The stable id used to target the device (adb
|
|
46
|
+
serial, simulator UDID, or devicectl identifier).
|
|
47
|
+
name: Human-readable model or simulator name.
|
|
48
|
+
os_version: OS version string when known.
|
|
49
|
+
state: ``"booted"``, ``"connected"``, ``"shutdown"``, or
|
|
50
|
+
``"offline"``.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
platform: str
|
|
54
|
+
kind: str
|
|
55
|
+
identifier: str
|
|
56
|
+
name: str
|
|
57
|
+
os_version: str = ""
|
|
58
|
+
state: str = ""
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def is_ready(self) -> bool:
|
|
62
|
+
"""Whether the target can be used right now (or booted on demand)."""
|
|
63
|
+
if self.kind == "simulator":
|
|
64
|
+
return self.state in ("booted", "shutdown")
|
|
65
|
+
return self.state in ("booted", "connected")
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
68
|
+
"""Return a JSON-serializable view of this target.
|
|
69
|
+
|
|
70
|
+
Derived from the dataclass fields so a field added later is
|
|
71
|
+
carried into the payload automatically, plus the computed
|
|
72
|
+
``is_ready`` that consumers would otherwise have to re-derive
|
|
73
|
+
from ``kind`` and ``state``.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The declared fields in declaration order, then ``is_ready``.
|
|
77
|
+
"""
|
|
78
|
+
return {**asdict(self), "is_ready": self.is_ready}
|
|
79
|
+
|
|
80
|
+
def format(self) -> str:
|
|
81
|
+
"""Return one aligned listing row for the CLI."""
|
|
82
|
+
os_part = f" ({self.os_version})" if self.os_version else ""
|
|
83
|
+
return f" {self.identifier:<40} {self.kind:<10} {self.state:<10} {self.name}{os_part}"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ======================================================================
|
|
87
|
+
# Android (adb)
|
|
88
|
+
# ======================================================================
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def parse_adb_devices(output: str) -> List[Device]:
|
|
92
|
+
"""Parse ``adb devices -l`` output into devices.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
output: Raw stdout from ``adb devices -l``.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Android devices and emulators (offline entries included, with
|
|
99
|
+
``state="offline"``).
|
|
100
|
+
"""
|
|
101
|
+
devices: List[Device] = []
|
|
102
|
+
for line in output.splitlines():
|
|
103
|
+
line = line.strip()
|
|
104
|
+
if not line or line.startswith("List of devices"):
|
|
105
|
+
continue
|
|
106
|
+
parts = line.split()
|
|
107
|
+
if len(parts) < 2:
|
|
108
|
+
continue
|
|
109
|
+
serial, state = parts[0], parts[1]
|
|
110
|
+
if state == "unauthorized":
|
|
111
|
+
state = "offline"
|
|
112
|
+
elif state == "device":
|
|
113
|
+
state = "connected"
|
|
114
|
+
model = ""
|
|
115
|
+
for token in parts[2:]:
|
|
116
|
+
if token.startswith("model:"):
|
|
117
|
+
model = token[len("model:") :].replace("_", " ")
|
|
118
|
+
kind = "emulator" if serial.startswith("emulator-") else "device"
|
|
119
|
+
devices.append(
|
|
120
|
+
Device(
|
|
121
|
+
platform="android",
|
|
122
|
+
kind=kind,
|
|
123
|
+
identifier=serial,
|
|
124
|
+
name=model or serial,
|
|
125
|
+
state=state if state in ("connected", "offline") else "offline",
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
return devices
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def list_android_devices() -> List[Device]:
|
|
132
|
+
"""Return connected Android devices/emulators (empty without adb)."""
|
|
133
|
+
try:
|
|
134
|
+
result = subprocess.run(["adb", "devices", "-l"], check=False, capture_output=True, text=True)
|
|
135
|
+
except FileNotFoundError:
|
|
136
|
+
return []
|
|
137
|
+
return parse_adb_devices(result.stdout or "")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ======================================================================
|
|
141
|
+
# iOS Simulators (simctl)
|
|
142
|
+
# ======================================================================
|
|
143
|
+
|
|
144
|
+
_RUNTIME_RE = re.compile(r"iOS[.-](\d+)[.-](\d+)")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def parse_simctl_devices(data: Dict[str, Any]) -> List[Device]:
|
|
148
|
+
"""Parse ``simctl list devices --json`` payload into devices.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
data: The decoded JSON payload.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Available iOS Simulators (unavailable ones are skipped).
|
|
155
|
+
"""
|
|
156
|
+
devices: List[Device] = []
|
|
157
|
+
for runtime, entries in (data.get("devices") or {}).items():
|
|
158
|
+
match = _RUNTIME_RE.search(runtime)
|
|
159
|
+
os_version = f"iOS {match.group(1)}.{match.group(2)}" if match else ""
|
|
160
|
+
if "iOS" not in runtime:
|
|
161
|
+
continue
|
|
162
|
+
for entry in entries or []:
|
|
163
|
+
if not entry.get("isAvailable") or not entry.get("udid"):
|
|
164
|
+
continue
|
|
165
|
+
devices.append(
|
|
166
|
+
Device(
|
|
167
|
+
platform="ios",
|
|
168
|
+
kind="simulator",
|
|
169
|
+
identifier=str(entry["udid"]),
|
|
170
|
+
name=str(entry.get("name") or entry["udid"]),
|
|
171
|
+
os_version=os_version,
|
|
172
|
+
state="booted" if entry.get("state") == "Booted" else "shutdown",
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
return devices
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def list_ios_simulators() -> List[Device]:
|
|
179
|
+
"""Return available iOS Simulators (empty without Xcode)."""
|
|
180
|
+
try:
|
|
181
|
+
result = subprocess.run(
|
|
182
|
+
["xcrun", "simctl", "list", "devices", "available", "--json"],
|
|
183
|
+
check=False,
|
|
184
|
+
capture_output=True,
|
|
185
|
+
text=True,
|
|
186
|
+
)
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
return []
|
|
189
|
+
try:
|
|
190
|
+
data = json.loads(result.stdout or "{}")
|
|
191
|
+
except json.JSONDecodeError:
|
|
192
|
+
return []
|
|
193
|
+
return parse_simctl_devices(data)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ======================================================================
|
|
197
|
+
# Physical iOS devices (devicectl)
|
|
198
|
+
# ======================================================================
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def parse_devicectl_devices(data: Dict[str, Any]) -> List[Device]:
|
|
202
|
+
"""Parse ``devicectl list devices --json-output`` payload into devices.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
data: The decoded JSON payload.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Physical iOS devices.
|
|
209
|
+
"""
|
|
210
|
+
devices: List[Device] = []
|
|
211
|
+
for entry in (data.get("result") or {}).get("devices") or []:
|
|
212
|
+
props = entry.get("deviceProperties") or {}
|
|
213
|
+
hardware = entry.get("hardwareProperties") or {}
|
|
214
|
+
connection = entry.get("connectionProperties") or {}
|
|
215
|
+
identifier = str(entry.get("identifier") or "")
|
|
216
|
+
if not identifier:
|
|
217
|
+
continue
|
|
218
|
+
state = "connected" if connection.get("tunnelState") in ("connected", "available") else "offline"
|
|
219
|
+
os_version = props.get("osVersionNumber") or ""
|
|
220
|
+
devices.append(
|
|
221
|
+
Device(
|
|
222
|
+
platform="ios",
|
|
223
|
+
kind="device",
|
|
224
|
+
identifier=identifier,
|
|
225
|
+
name=str(props.get("name") or hardware.get("marketingName") or identifier),
|
|
226
|
+
os_version=f"iOS {os_version}" if os_version else "",
|
|
227
|
+
state=state,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
return devices
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def list_ios_physical_devices() -> List[Device]:
|
|
234
|
+
"""Return physical iOS devices (empty without Xcode 15+)."""
|
|
235
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
236
|
+
out_path = Path(tmp) / "devices.json"
|
|
237
|
+
try:
|
|
238
|
+
subprocess.run(
|
|
239
|
+
["xcrun", "devicectl", "list", "devices", "--json-output", str(out_path)],
|
|
240
|
+
check=False,
|
|
241
|
+
capture_output=True,
|
|
242
|
+
text=True,
|
|
243
|
+
timeout=30,
|
|
244
|
+
)
|
|
245
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
246
|
+
return []
|
|
247
|
+
if not out_path.is_file():
|
|
248
|
+
return []
|
|
249
|
+
try:
|
|
250
|
+
data = json.loads(out_path.read_text(encoding="utf-8"))
|
|
251
|
+
except (json.JSONDecodeError, OSError):
|
|
252
|
+
return []
|
|
253
|
+
return parse_devicectl_devices(data)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ======================================================================
|
|
257
|
+
# Aggregation / selection
|
|
258
|
+
# ======================================================================
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def list_devices(platform: Optional[str] = None) -> List[Device]:
|
|
262
|
+
"""Return all known targets, optionally restricted to one platform.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
platform: ``"android"``, ``"ios"``, or ``None`` for both.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Devices ordered: Android first, then physical iOS devices, then
|
|
269
|
+
simulators (booted simulators before shutdown ones).
|
|
270
|
+
"""
|
|
271
|
+
devices: List[Device] = []
|
|
272
|
+
if platform in (None, "android"):
|
|
273
|
+
devices.extend(list_android_devices())
|
|
274
|
+
if platform in (None, "ios"):
|
|
275
|
+
devices.extend(list_ios_physical_devices())
|
|
276
|
+
simulators = list_ios_simulators()
|
|
277
|
+
simulators.sort(key=lambda dev: (dev.state != "booted", dev.name))
|
|
278
|
+
devices.extend(simulators)
|
|
279
|
+
return devices
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def find_device(devices: List[Device], query: str) -> Optional[Device]:
|
|
283
|
+
"""Resolve ``query`` against ``devices`` (exact id, then name match).
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
devices: Candidate devices.
|
|
287
|
+
query: An identifier, exact name, or case-insensitive name
|
|
288
|
+
substring.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
The best match, or ``None``. Ready devices win ties.
|
|
292
|
+
"""
|
|
293
|
+
ready_first = sorted(devices, key=lambda dev: not dev.is_ready)
|
|
294
|
+
for device in ready_first:
|
|
295
|
+
if device.identifier == query:
|
|
296
|
+
return device
|
|
297
|
+
lowered = query.lower()
|
|
298
|
+
for device in ready_first:
|
|
299
|
+
if device.name.lower() == lowered:
|
|
300
|
+
return device
|
|
301
|
+
for device in ready_first:
|
|
302
|
+
if lowered in device.name.lower():
|
|
303
|
+
return device
|
|
304
|
+
return None
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Environment diagnostics for ``pn doctor``.
|
|
2
|
+
|
|
3
|
+
Inspects the local toolchain and the project's ``pythonnative.toml`` and
|
|
4
|
+
reports what's ready and what's missing for building on each platform,
|
|
5
|
+
analogous to ``flutter doctor`` / ``npx react-native doctor``. The checks
|
|
6
|
+
are deliberately read-only and fast; they shell out only to ask tools for
|
|
7
|
+
their versions.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import List, Optional
|
|
18
|
+
|
|
19
|
+
from . import icons
|
|
20
|
+
from .config import SUPPORTED_PYTHON_VERSIONS, AppConfig, ConfigError
|
|
21
|
+
from .runtime_assets import PINNED_ASSETS
|
|
22
|
+
|
|
23
|
+
OK = "ok"
|
|
24
|
+
WARN = "warn"
|
|
25
|
+
ERROR = "error"
|
|
26
|
+
INFO = "info"
|
|
27
|
+
|
|
28
|
+
_SYMBOLS = {OK: "[ok]", WARN: "[!]", ERROR: "[x]", INFO: "[i]"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class CheckResult:
|
|
33
|
+
"""The outcome of a single diagnostic check.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
name: Short label for the thing checked.
|
|
37
|
+
level: One of ``"ok"``, ``"warn"``, ``"error"``, ``"info"``.
|
|
38
|
+
detail: Human-readable detail / remediation hint.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
level: str
|
|
43
|
+
detail: str = ""
|
|
44
|
+
|
|
45
|
+
def format(self) -> str:
|
|
46
|
+
"""Return a single aligned line for terminal output."""
|
|
47
|
+
symbol = _SYMBOLS.get(self.level, "[?]")
|
|
48
|
+
suffix = f": {self.detail}" if self.detail else ""
|
|
49
|
+
return f" {symbol} {self.name}{suffix}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _which_version(tool: str, version_args: List[str]) -> Optional[str]:
|
|
53
|
+
path = shutil.which(tool)
|
|
54
|
+
if not path:
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
out = subprocess.run([tool, *version_args], capture_output=True, text=True, timeout=20)
|
|
58
|
+
except Exception:
|
|
59
|
+
return path
|
|
60
|
+
text = (out.stdout or out.stderr or "").strip().splitlines()
|
|
61
|
+
return text[0] if text else path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_python_for(python_version: str) -> Optional[str]:
|
|
65
|
+
"""Locate an interpreter matching ``python_version`` (e.g. ``python3.13``) on ``PATH``.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
python_version: CPython ``major.minor``.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The interpreter path, or ``None`` when none is installed.
|
|
72
|
+
"""
|
|
73
|
+
host = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
74
|
+
if host == python_version:
|
|
75
|
+
return sys.executable
|
|
76
|
+
return shutil.which(f"python{python_version}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def check_common(config: Optional[AppConfig] = None) -> List[CheckResult]:
|
|
80
|
+
"""Run platform-agnostic checks (interpreters, requirements, optional dependencies).
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
config: The loaded app config, or ``None`` if unavailable.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Check results for the host and build Pythons, the declared
|
|
87
|
+
requirements, and optional dependencies.
|
|
88
|
+
"""
|
|
89
|
+
results: List[CheckResult] = []
|
|
90
|
+
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
91
|
+
if py_version in SUPPORTED_PYTHON_VERSIONS:
|
|
92
|
+
results.append(CheckResult("Host Python", OK, f"{sys.version.split()[0]}"))
|
|
93
|
+
else:
|
|
94
|
+
results.append(
|
|
95
|
+
CheckResult(
|
|
96
|
+
"Host Python",
|
|
97
|
+
WARN,
|
|
98
|
+
f"{py_version} (PythonNative targets {', '.join(SUPPORTED_PYTHON_VERSIONS)})",
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if config is not None:
|
|
103
|
+
# pip cross-resolves iOS wheels from any interpreter, but Chaquopy
|
|
104
|
+
# needs a matching python3.X on the build machine whenever there
|
|
105
|
+
# are requirements, and release bytecode is version-specific.
|
|
106
|
+
build_python = build_python_for(config.python_version)
|
|
107
|
+
label = f"Build Python {config.python_version} (matches app.python_version)"
|
|
108
|
+
if build_python:
|
|
109
|
+
results.append(CheckResult(label, OK, build_python))
|
|
110
|
+
else:
|
|
111
|
+
level = WARN if config.requirements else INFO
|
|
112
|
+
results.append(
|
|
113
|
+
CheckResult(
|
|
114
|
+
label,
|
|
115
|
+
level,
|
|
116
|
+
f"python{config.python_version} not found on PATH; Android builds with [requirements] and "
|
|
117
|
+
"release bytecode compilation need it (e.g. uv python install "
|
|
118
|
+
f"{config.python_version})",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
if config.requirements:
|
|
122
|
+
count = len(config.requirements)
|
|
123
|
+
results.append(
|
|
124
|
+
CheckResult(
|
|
125
|
+
"Requirements",
|
|
126
|
+
INFO,
|
|
127
|
+
f"{count} package(s) declared; run 'pn deps' to check device wheel availability",
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if icons.pillow_available():
|
|
132
|
+
results.append(CheckResult("Pillow (icon/splash generation)", OK))
|
|
133
|
+
else:
|
|
134
|
+
results.append(
|
|
135
|
+
CheckResult(
|
|
136
|
+
"Pillow (icon/splash generation)",
|
|
137
|
+
WARN,
|
|
138
|
+
"not installed; run: pip install 'pythonnative[build]'",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return results
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def check_android(config: Optional[AppConfig]) -> List[CheckResult]:
|
|
145
|
+
"""Run Android toolchain and signing checks.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
config: The loaded app config, or ``None`` if unavailable.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Android-specific check results.
|
|
152
|
+
"""
|
|
153
|
+
results: List[CheckResult] = []
|
|
154
|
+
adb = _which_version("adb", ["--version"])
|
|
155
|
+
results.append(CheckResult("adb (Android platform-tools)", OK if adb else WARN, adb or "not found on PATH"))
|
|
156
|
+
|
|
157
|
+
java_home = shutil.which("java")
|
|
158
|
+
import os
|
|
159
|
+
|
|
160
|
+
if os.environ.get("JAVA_HOME") or java_home:
|
|
161
|
+
results.append(CheckResult("Java (JDK 17 recommended)", OK, os.environ.get("JAVA_HOME") or java_home or ""))
|
|
162
|
+
else:
|
|
163
|
+
results.append(CheckResult("Java (JDK 17 recommended)", WARN, "JAVA_HOME not set and 'java' not on PATH"))
|
|
164
|
+
|
|
165
|
+
if config is not None:
|
|
166
|
+
signing = config.android.signing
|
|
167
|
+
if signing.is_configured:
|
|
168
|
+
keystore = config.resolve_path(signing.keystore) if signing.keystore else None
|
|
169
|
+
if keystore and keystore.is_file():
|
|
170
|
+
results.append(CheckResult("Android release keystore", OK, str(keystore)))
|
|
171
|
+
else:
|
|
172
|
+
results.append(CheckResult("Android release keystore", ERROR, f"not found: {keystore}"))
|
|
173
|
+
missing = [env for env in (signing.store_password_env, signing.key_password_env) if not os.environ.get(env)]
|
|
174
|
+
if missing:
|
|
175
|
+
results.append(CheckResult("Android signing passwords", WARN, f"unset env: {', '.join(missing)}"))
|
|
176
|
+
else:
|
|
177
|
+
results.append(
|
|
178
|
+
CheckResult(
|
|
179
|
+
"Android release signing",
|
|
180
|
+
INFO,
|
|
181
|
+
"not configured; release builds will be unsigned (set [android.signing])",
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
return results
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def check_ios(config: Optional[AppConfig]) -> List[CheckResult]:
|
|
188
|
+
"""Run iOS toolchain and signing checks.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
config: The loaded app config, or ``None`` if unavailable.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
iOS-specific check results.
|
|
195
|
+
"""
|
|
196
|
+
results: List[CheckResult] = []
|
|
197
|
+
if sys.platform != "darwin":
|
|
198
|
+
results.append(CheckResult("macOS (required for iOS)", ERROR, f"this is {sys.platform}; iOS builds need macOS"))
|
|
199
|
+
return results
|
|
200
|
+
|
|
201
|
+
xcodebuild = _which_version("xcodebuild", ["-version"])
|
|
202
|
+
results.append(CheckResult("Xcode (xcodebuild)", OK if xcodebuild else ERROR, xcodebuild or "not found on PATH"))
|
|
203
|
+
simctl = shutil.which("xcrun")
|
|
204
|
+
results.append(CheckResult("xcrun simctl (Simulators)", OK if simctl else WARN, simctl or "not found on PATH"))
|
|
205
|
+
|
|
206
|
+
if config is not None:
|
|
207
|
+
if config.python_version not in PINNED_ASSETS:
|
|
208
|
+
supported = ", ".join(sorted(PINNED_ASSETS))
|
|
209
|
+
results.append(
|
|
210
|
+
CheckResult(
|
|
211
|
+
"iOS embedded Python",
|
|
212
|
+
WARN,
|
|
213
|
+
f"app.python_version={config.python_version}; pinned iOS builds exist for {supported}",
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
if config.ios.development_team:
|
|
217
|
+
results.append(CheckResult("iOS development team", OK, config.ios.development_team))
|
|
218
|
+
else:
|
|
219
|
+
results.append(
|
|
220
|
+
CheckResult(
|
|
221
|
+
"iOS development team",
|
|
222
|
+
INFO,
|
|
223
|
+
"not set; required for device builds (set [ios].development_team)",
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
return results
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def check_config(project_root: Path) -> tuple[Optional[AppConfig], List[CheckResult]]:
|
|
230
|
+
"""Load and validate the project config, returning it with a result.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
project_root: Directory expected to contain ``pythonnative.toml``.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
A tuple of the loaded config (or ``None``) and the check results.
|
|
237
|
+
"""
|
|
238
|
+
results: List[CheckResult] = []
|
|
239
|
+
try:
|
|
240
|
+
config = AppConfig.load(project_root)
|
|
241
|
+
except ConfigError as exc:
|
|
242
|
+
results.append(CheckResult("pythonnative.toml", ERROR, str(exc).splitlines()[0]))
|
|
243
|
+
return None, results
|
|
244
|
+
results.append(CheckResult("pythonnative.toml", OK, f"{config.app_id} (v{config.version})"))
|
|
245
|
+
return config, results
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def run_doctor(project_root: Path, *, platform: Optional[str] = None) -> List[CheckResult]:
|
|
249
|
+
"""Run all diagnostics for ``project_root``.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
project_root: The project directory.
|
|
253
|
+
platform: Restrict checks to ``"android"`` or ``"ios"``; ``None``
|
|
254
|
+
checks both.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
All check results in display order.
|
|
258
|
+
"""
|
|
259
|
+
config, results = check_config(project_root)
|
|
260
|
+
results.extend(check_common(config))
|
|
261
|
+
if platform in (None, "android"):
|
|
262
|
+
results.extend(check_android(config))
|
|
263
|
+
if platform in (None, "ios"):
|
|
264
|
+
results.extend(check_ios(config))
|
|
265
|
+
return results
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def worst_level(results: List[CheckResult]) -> str:
|
|
269
|
+
"""Return the most severe level among ``results``.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
results: The diagnostic results.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
``"error"`` if any error, else ``"warn"`` if any warning, else
|
|
276
|
+
``"ok"``.
|
|
277
|
+
"""
|
|
278
|
+
levels = {result.level for result in results}
|
|
279
|
+
if ERROR in levels:
|
|
280
|
+
return ERROR
|
|
281
|
+
if WARN in levels:
|
|
282
|
+
return WARN
|
|
283
|
+
return OK
|