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,369 @@
|
|
|
1
|
+
"""Render components into a fake backend and query the result.
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
import pythonnative as pn
|
|
5
|
+
from pythonnative.testing import render
|
|
6
|
+
|
|
7
|
+
def test_counter():
|
|
8
|
+
result = render(Counter())
|
|
9
|
+
assert result.get_by_text("Count: 0")
|
|
10
|
+
result.press(result.get_by_text("+"))
|
|
11
|
+
assert result.get_by_text("Count: 1")
|
|
12
|
+
```
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any, Callable, Dict, Generic, List, Optional, Pattern, Tuple, TypeVar, Union
|
|
19
|
+
|
|
20
|
+
from ..element import Element, Node
|
|
21
|
+
from ..events import dispatch_event, get_event_registry
|
|
22
|
+
from .backend import FakeBackend, FakeView
|
|
23
|
+
|
|
24
|
+
__all__ = ["FakeHost", "HookResult", "RenderResult", "render", "render_hook", "settle"]
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T")
|
|
27
|
+
Matcher = Union[str, Pattern[str], Callable[[Optional[str]], bool]]
|
|
28
|
+
Target = Union[FakeView, int]
|
|
29
|
+
|
|
30
|
+
DEFAULT_VIEWPORT: Tuple[float, float] = (390.0, 844.0)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _matches(value: Optional[str], matcher: Matcher, *, exact: bool) -> bool:
|
|
34
|
+
if callable(matcher) and not isinstance(matcher, (str, re.Pattern)):
|
|
35
|
+
return bool(matcher(value))
|
|
36
|
+
if value is None:
|
|
37
|
+
return False
|
|
38
|
+
if isinstance(matcher, re.Pattern):
|
|
39
|
+
return matcher.search(value) is not None
|
|
40
|
+
return value == matcher if exact else matcher in value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def settle(reconciler: Any = None, timeout: float = 1.0) -> None:
|
|
44
|
+
"""Pump the framework loop and flush pending renders until everything is idle.
|
|
45
|
+
|
|
46
|
+
Use after triggering async work (``use_resource``, ``use_query``,
|
|
47
|
+
transitions, coroutines started by handlers) so assertions see the
|
|
48
|
+
settled tree.
|
|
49
|
+
"""
|
|
50
|
+
from .. import runtime
|
|
51
|
+
|
|
52
|
+
for _ in range(50):
|
|
53
|
+
idle = runtime.drain(timeout)
|
|
54
|
+
if reconciler is not None:
|
|
55
|
+
reconciler.flush_dirty()
|
|
56
|
+
if reconciler.transitions.pending:
|
|
57
|
+
reconciler.transitions.flush()
|
|
58
|
+
continue
|
|
59
|
+
if idle and (reconciler is None or not reconciler._has_dirty_work()):
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FakeHost:
|
|
64
|
+
"""Application lifecycle and restoration host for navigation tests.
|
|
65
|
+
|
|
66
|
+
Pass as ``render(..., host=FakeHost())`` to provide restoration state.
|
|
67
|
+
Screens remain in the logical tree. ``set_focused`` simulates the
|
|
68
|
+
application entering or leaving the foreground.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, initial_state: Optional[Dict[str, Any]] = None) -> None:
|
|
72
|
+
self.is_focused = True
|
|
73
|
+
self.initial_state = initial_state
|
|
74
|
+
self.options: List[Dict[str, Any]] = []
|
|
75
|
+
self._focus_listeners: List[Callable[[bool], None]] = []
|
|
76
|
+
|
|
77
|
+
def initial_navigation_state(self) -> Optional[Dict[str, Any]]:
|
|
78
|
+
"""Return the ``initial_state`` the host was constructed with (``None`` for a fresh root)."""
|
|
79
|
+
return self.initial_state
|
|
80
|
+
|
|
81
|
+
def set_screen_options(self, options: Dict[str, Any]) -> None:
|
|
82
|
+
"""Append a copy of ``options`` to ``options`` (see the ``title`` property)."""
|
|
83
|
+
self.options.append(dict(options))
|
|
84
|
+
|
|
85
|
+
def add_focus_listener(self, callback: Callable[[bool], None]) -> Callable[[], None]:
|
|
86
|
+
"""Register a focus callback fired by ``set_focused``; returns an unsubscribe callable."""
|
|
87
|
+
self._focus_listeners.append(callback)
|
|
88
|
+
return lambda: self._focus_listeners.remove(callback)
|
|
89
|
+
|
|
90
|
+
def set_focused(self, focused: bool) -> None:
|
|
91
|
+
"""Simulate the platform covering (``False``) or revealing (``True``) the screen."""
|
|
92
|
+
self.is_focused = focused
|
|
93
|
+
for cb in list(self._focus_listeners):
|
|
94
|
+
cb(focused)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def title(self) -> Optional[str]:
|
|
98
|
+
"""The most recent ``title`` passed to ``set_screen_options``."""
|
|
99
|
+
for options in reversed(self.options):
|
|
100
|
+
if "title" in options:
|
|
101
|
+
return options["title"]
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RenderResult:
|
|
106
|
+
"""Handle to a mounted tree: queries, events, re-render, unmount.
|
|
107
|
+
|
|
108
|
+
Query methods come in three flavors, mirroring Testing Library:
|
|
109
|
+
``get_by_*`` returns exactly one match or raises ``LookupError``
|
|
110
|
+
(with the tree dumped in the message), ``query_by_*`` returns the
|
|
111
|
+
match or ``None``, ``get_all_by_*`` returns every match. Matchers
|
|
112
|
+
are exact strings, compiled regexes, or predicates. Views inside a
|
|
113
|
+
``display: "none"`` subtree (inactive tabs, covered stack screens)
|
|
114
|
+
are skipped unless ``hidden=True`` is passed.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, reconciler: Any, backend: FakeBackend, wrap: Callable[[Node], Element]) -> None:
|
|
118
|
+
self.reconciler = reconciler
|
|
119
|
+
self.backend = backend
|
|
120
|
+
self._wrap = wrap
|
|
121
|
+
|
|
122
|
+
# -- tree -----------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def root(self) -> Optional[FakeView]:
|
|
126
|
+
"""The root native view (``None`` after unmount)."""
|
|
127
|
+
return self.reconciler.root_view()
|
|
128
|
+
|
|
129
|
+
def views(self, *, hidden: bool = False) -> List[FakeView]:
|
|
130
|
+
"""Every live view, root first (includes detached ``Portal`` overlays)."""
|
|
131
|
+
root = self.root
|
|
132
|
+
out = list(root.walk(include_hidden=hidden)) if root is not None else []
|
|
133
|
+
seen = {id(v) for v in out}
|
|
134
|
+
for view in self.backend.views.values():
|
|
135
|
+
if view.parent is None and view is not root and id(view) not in seen:
|
|
136
|
+
out.extend(view.walk(include_hidden=hidden))
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
def dump(self) -> str:
|
|
140
|
+
"""Indented text rendering of the live tree."""
|
|
141
|
+
root = self.root
|
|
142
|
+
return root.dump() if root is not None else "<unmounted>"
|
|
143
|
+
|
|
144
|
+
def text(self, *, hidden: bool = False) -> List[str]:
|
|
145
|
+
"""Visible strings in document order."""
|
|
146
|
+
return [v.text for v in self.views(hidden=hidden) if v.text is not None]
|
|
147
|
+
|
|
148
|
+
# -- queries --------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
def _all(self, predicate: Callable[[FakeView], bool], hidden: bool = False) -> List[FakeView]:
|
|
151
|
+
return [v for v in self.views(hidden=hidden) if predicate(v)]
|
|
152
|
+
|
|
153
|
+
def _one(self, kind: str, matcher: Any, found: List[FakeView]) -> FakeView:
|
|
154
|
+
if len(found) == 1:
|
|
155
|
+
return found[0]
|
|
156
|
+
detail = "no matches" if not found else f"{len(found)} matches: {found}"
|
|
157
|
+
raise LookupError(f"get_by_{kind}({matcher!r}): {detail}\n\n{self.dump()}")
|
|
158
|
+
|
|
159
|
+
def get_all_by_text(self, matcher: Matcher, *, exact: bool = True, hidden: bool = False) -> List[FakeView]:
|
|
160
|
+
"""Return every view whose visible text matches ``matcher`` (``exact=False`` matches substrings)."""
|
|
161
|
+
return self._all(lambda v: _matches(v.text, matcher, exact=exact), hidden)
|
|
162
|
+
|
|
163
|
+
def get_by_text(self, matcher: Matcher, *, exact: bool = True, hidden: bool = False) -> FakeView:
|
|
164
|
+
"""Return the single view whose visible text matches ``matcher``; raise ``LookupError`` otherwise."""
|
|
165
|
+
return self._one("text", matcher, self.get_all_by_text(matcher, exact=exact, hidden=hidden))
|
|
166
|
+
|
|
167
|
+
def query_by_text(self, matcher: Matcher, *, exact: bool = True, hidden: bool = False) -> Optional[FakeView]:
|
|
168
|
+
"""Return the first view whose visible text matches ``matcher``, or ``None``."""
|
|
169
|
+
found = self.get_all_by_text(matcher, exact=exact, hidden=hidden)
|
|
170
|
+
return found[0] if found else None
|
|
171
|
+
|
|
172
|
+
def get_all_by_test_id(self, matcher: Matcher, *, hidden: bool = False) -> List[FakeView]:
|
|
173
|
+
"""Return every view whose ``test_id`` prop matches ``matcher``."""
|
|
174
|
+
return self._all(lambda v: _matches(v.test_id, matcher, exact=True), hidden)
|
|
175
|
+
|
|
176
|
+
def get_by_test_id(self, matcher: Matcher, *, hidden: bool = False) -> FakeView:
|
|
177
|
+
"""Return the single view whose ``test_id`` prop matches ``matcher``; raise ``LookupError`` otherwise."""
|
|
178
|
+
return self._one("test_id", matcher, self.get_all_by_test_id(matcher, hidden=hidden))
|
|
179
|
+
|
|
180
|
+
def query_by_test_id(self, matcher: Matcher, *, hidden: bool = False) -> Optional[FakeView]:
|
|
181
|
+
"""Return the first view whose ``test_id`` prop matches ``matcher``, or ``None``."""
|
|
182
|
+
found = self.get_all_by_test_id(matcher, hidden=hidden)
|
|
183
|
+
return found[0] if found else None
|
|
184
|
+
|
|
185
|
+
def get_all_by_label(self, matcher: Matcher, *, hidden: bool = False) -> List[FakeView]:
|
|
186
|
+
"""Return every view whose ``accessibility_label`` prop matches ``matcher``."""
|
|
187
|
+
return self._all(lambda v: _matches(v.label, matcher, exact=True), hidden)
|
|
188
|
+
|
|
189
|
+
def get_by_label(self, matcher: Matcher, *, hidden: bool = False) -> FakeView:
|
|
190
|
+
"""Return the single view whose ``accessibility_label`` matches ``matcher``; raise ``LookupError`` if not."""
|
|
191
|
+
return self._one("label", matcher, self.get_all_by_label(matcher, hidden=hidden))
|
|
192
|
+
|
|
193
|
+
def query_by_label(self, matcher: Matcher, *, hidden: bool = False) -> Optional[FakeView]:
|
|
194
|
+
"""Return the first view whose ``accessibility_label`` prop matches ``matcher``, or ``None``."""
|
|
195
|
+
found = self.get_all_by_label(matcher, hidden=hidden)
|
|
196
|
+
return found[0] if found else None
|
|
197
|
+
|
|
198
|
+
def get_all_by_type(self, type_name: str, *, hidden: bool = False) -> List[FakeView]:
|
|
199
|
+
"""Return every view of native type ``type_name`` (for example ``"Text"``)."""
|
|
200
|
+
return self._all(lambda v: v.type_name == type_name, hidden)
|
|
201
|
+
|
|
202
|
+
def get_by_type(self, type_name: str, *, hidden: bool = False) -> FakeView:
|
|
203
|
+
"""Return the single view of native type ``type_name``; raise ``LookupError`` otherwise."""
|
|
204
|
+
return self._one("type", type_name, self.get_all_by_type(type_name, hidden=hidden))
|
|
205
|
+
|
|
206
|
+
def query_by_type(self, type_name: str, *, hidden: bool = False) -> Optional[FakeView]:
|
|
207
|
+
"""Return the first view of native type ``type_name``, or ``None``."""
|
|
208
|
+
found = self.get_all_by_type(type_name, hidden=hidden)
|
|
209
|
+
return found[0] if found else None
|
|
210
|
+
|
|
211
|
+
# -- events ---------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
def fire(self, target: Target, event: str, *args: Any) -> None:
|
|
214
|
+
"""Dispatch ``event`` (an ``on_*`` prop name) to ``target`` and settle.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
LookupError: If no handler is registered for the event.
|
|
218
|
+
"""
|
|
219
|
+
tag = target.tag if isinstance(target, FakeView) else int(target)
|
|
220
|
+
if not get_event_registry().has(tag, event):
|
|
221
|
+
raise LookupError(f"fire({target!r}, {event!r}): no handler registered\n\n{self.dump()}")
|
|
222
|
+
dispatch_event(tag, event, *args)
|
|
223
|
+
self.settle()
|
|
224
|
+
|
|
225
|
+
def press(self, target: Target) -> None:
|
|
226
|
+
"""Fire ``on_press`` on ``target``."""
|
|
227
|
+
self.fire(target, "on_press")
|
|
228
|
+
|
|
229
|
+
def change_text(self, target: Target, value: str) -> None:
|
|
230
|
+
"""Fire ``on_change`` on a ``TextInput``."""
|
|
231
|
+
self.fire(target, "on_change", value)
|
|
232
|
+
|
|
233
|
+
def back(self) -> bool:
|
|
234
|
+
"""Simulate the system back action; returns whether a handler consumed it."""
|
|
235
|
+
consumed = bool(self.reconciler.dispatch_back_press())
|
|
236
|
+
self.settle()
|
|
237
|
+
return consumed
|
|
238
|
+
|
|
239
|
+
# -- lifecycle ------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def settle(self, timeout: float = 1.0) -> None:
|
|
242
|
+
"""Flush pending renders and async work (see [`settle`][pythonnative.testing.settle])."""
|
|
243
|
+
settle(self.reconciler, timeout)
|
|
244
|
+
|
|
245
|
+
def rerender(self, element: Node) -> None:
|
|
246
|
+
"""Reconcile a new root element (new props from outside the tree)."""
|
|
247
|
+
self.reconciler.reconcile(self._wrap(element))
|
|
248
|
+
self.settle()
|
|
249
|
+
|
|
250
|
+
def unmount(self) -> None:
|
|
251
|
+
"""Unmount the tree, running effect cleanups and destroying every view."""
|
|
252
|
+
self.reconciler.unmount()
|
|
253
|
+
|
|
254
|
+
def __enter__(self) -> "RenderResult":
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
def __exit__(self, *exc: Any) -> None:
|
|
258
|
+
self.unmount()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def render(
|
|
262
|
+
element: Node,
|
|
263
|
+
*,
|
|
264
|
+
viewport: Optional[Tuple[float, float]] = DEFAULT_VIEWPORT,
|
|
265
|
+
backend: Optional[FakeBackend] = None,
|
|
266
|
+
host: Optional[FakeHost] = None,
|
|
267
|
+
settle_first: bool = True,
|
|
268
|
+
) -> RenderResult:
|
|
269
|
+
"""Mount ``element`` into a [`FakeBackend`][pythonnative.testing.FakeBackend].
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
element: The element (or component call) to render.
|
|
273
|
+
viewport: Size for the layout pass; ``None`` skips layout.
|
|
274
|
+
backend: Reuse an existing backend (defaults to a fresh one).
|
|
275
|
+
host: Render under a [`HostRoot`][pythonnative.navigation.host.HostRoot]
|
|
276
|
+
with this fake host for focus and restoration state.
|
|
277
|
+
settle_first: Drain async work and effects before returning.
|
|
278
|
+
"""
|
|
279
|
+
from ..reconciler import Reconciler
|
|
280
|
+
|
|
281
|
+
backend = backend if backend is not None else FakeBackend()
|
|
282
|
+
reconciler = Reconciler(backend)
|
|
283
|
+
if viewport is not None:
|
|
284
|
+
reconciler.set_viewport_size(float(viewport[0]), float(viewport[1]))
|
|
285
|
+
|
|
286
|
+
def wrap(node: Node) -> Element:
|
|
287
|
+
if host is not None:
|
|
288
|
+
from ..navigation.host import HostRoot
|
|
289
|
+
|
|
290
|
+
return HostRoot(node, host=host)
|
|
291
|
+
if isinstance(node, Element):
|
|
292
|
+
return node
|
|
293
|
+
from ..components import Fragment
|
|
294
|
+
|
|
295
|
+
return Fragment(*(node if isinstance(node, (list, tuple)) else [node]))
|
|
296
|
+
|
|
297
|
+
reconciler.mount(wrap(element))
|
|
298
|
+
result = RenderResult(reconciler, backend, wrap)
|
|
299
|
+
if settle_first:
|
|
300
|
+
result.settle()
|
|
301
|
+
return result
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class HookResult(Generic[T]):
|
|
305
|
+
"""Handle returned by [`render_hook`][pythonnative.testing.render_hook].
|
|
306
|
+
|
|
307
|
+
Attributes:
|
|
308
|
+
current: The hook's most recent return value.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
def __init__(self, result: RenderResult, box: Dict[str, Any], rerender: Callable[..., None]) -> None:
|
|
312
|
+
self._result = result
|
|
313
|
+
self._box = box
|
|
314
|
+
self._rerender = rerender
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def current(self) -> T:
|
|
318
|
+
"""The value the hook returned on its most recent render."""
|
|
319
|
+
return self._box["value"]
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def render_count(self) -> int:
|
|
323
|
+
"""How many times the hook has run since ``render_hook`` was called."""
|
|
324
|
+
return self._box["renders"]
|
|
325
|
+
|
|
326
|
+
def act(self, fn: Callable[[], Any]) -> None:
|
|
327
|
+
"""Run ``fn`` (typically a state setter) and settle."""
|
|
328
|
+
fn()
|
|
329
|
+
self._result.settle()
|
|
330
|
+
|
|
331
|
+
def rerender(self, *args: Any, **kwargs: Any) -> None:
|
|
332
|
+
"""Re-run the hook with new arguments."""
|
|
333
|
+
self._rerender(*args, **kwargs)
|
|
334
|
+
|
|
335
|
+
def settle(self) -> None:
|
|
336
|
+
"""Flush pending renders and async work started by the hook."""
|
|
337
|
+
self._result.settle()
|
|
338
|
+
|
|
339
|
+
def unmount(self) -> None:
|
|
340
|
+
"""Unmount the harness component, running the hook's effect cleanups."""
|
|
341
|
+
self._result.unmount()
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def render_hook(hook: Callable[..., T], *args: Any, host: Optional[FakeHost] = None, **kwargs: Any) -> HookResult[T]:
|
|
345
|
+
"""Run ``hook(*args, **kwargs)`` inside a throwaway component.
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
result = render_hook(lambda: pn.use_state(0))
|
|
349
|
+
value, set_value = result.current
|
|
350
|
+
result.act(lambda: set_value(5))
|
|
351
|
+
assert result.current[0] == 5
|
|
352
|
+
```
|
|
353
|
+
"""
|
|
354
|
+
from ..component import component
|
|
355
|
+
|
|
356
|
+
box: Dict[str, Any] = {"value": None, "renders": 0}
|
|
357
|
+
|
|
358
|
+
@component
|
|
359
|
+
def Harness(*, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> None:
|
|
360
|
+
box["value"] = hook(*args, **kwargs)
|
|
361
|
+
box["renders"] += 1
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
result = render(Harness(args=tuple(args), kwargs=dict(kwargs)), host=host)
|
|
365
|
+
|
|
366
|
+
def rerender(*new_args: Any, **new_kwargs: Any) -> None:
|
|
367
|
+
result.rerender(Harness(args=tuple(new_args) or tuple(args), kwargs={**kwargs, **new_kwargs}))
|
|
368
|
+
|
|
369
|
+
return HookResult(result, box, rerender)
|
pythonnative/utils.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Platform detection and shared helpers.
|
|
2
|
+
|
|
3
|
+
This module is imported early by most other modules, so it avoids
|
|
4
|
+
importing platform-specific packages at module level. The detection
|
|
5
|
+
results are cached the first time
|
|
6
|
+
[`IS_ANDROID`][pythonnative.utils.IS_ANDROID],
|
|
7
|
+
[`IS_IOS`][pythonnative.utils.IS_IOS], and
|
|
8
|
+
[`IS_WEB`][pythonnative.utils.IS_WEB] are read.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
IS_ANDROID: `True` when running inside an Android process
|
|
12
|
+
(`sys.platform == "android"` on the embedded CPython 3.13+, or
|
|
13
|
+
Chaquopy's `java` module imports successfully).
|
|
14
|
+
IS_IOS: `True` when running inside an iOS app bundle
|
|
15
|
+
(`sys.platform == "ios"` on the embedded CPython 3.13+, or the
|
|
16
|
+
explicit `PN_PLATFORM=ios` override).
|
|
17
|
+
IS_WEB: `True` when running the browser preview (signaled by
|
|
18
|
+
`PN_PLATFORM=web`, set by ``pn preview`` / ``pn start``). The
|
|
19
|
+
reconciler then commits through the bridge to a browser page
|
|
20
|
+
instead of to Swift or Kotlin.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
# ======================================================================
|
|
28
|
+
# Platform detection
|
|
29
|
+
# ======================================================================
|
|
30
|
+
|
|
31
|
+
_is_android: Optional[bool] = None
|
|
32
|
+
_is_ios: Optional[bool] = None
|
|
33
|
+
_is_web: Optional[bool] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _detect_android() -> bool:
|
|
37
|
+
"""Return whether we're running inside an Android process.
|
|
38
|
+
|
|
39
|
+
CPython 3.13+ reports ``sys.platform == "android"`` (PEP 738); the
|
|
40
|
+
Chaquopy ``java`` module import is kept as a secondary signal.
|
|
41
|
+
"""
|
|
42
|
+
if sys.platform == "android":
|
|
43
|
+
return True
|
|
44
|
+
try:
|
|
45
|
+
from java import jclass # noqa: F401
|
|
46
|
+
|
|
47
|
+
return True
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _detect_ios() -> bool:
|
|
54
|
+
"""Detect whether we're running inside an iOS app bundle.
|
|
55
|
+
|
|
56
|
+
Signals, in priority order:
|
|
57
|
+
|
|
58
|
+
- `sys.platform == "ios"`: CPython 3.13+ reports this on both
|
|
59
|
+
devices and the Simulator (PEP 730), so it is the canonical
|
|
60
|
+
signal for the embedded runtime.
|
|
61
|
+
- Explicit `PN_PLATFORM=ios` env var: set by the iOS template before
|
|
62
|
+
Python starts as a belt-and-braces override, and by tests.
|
|
63
|
+
|
|
64
|
+
Running on macOS is never enough on its own: the browser preview
|
|
65
|
+
and unit tests run there and must not be mistaken for iOS.
|
|
66
|
+
"""
|
|
67
|
+
if sys.platform == "ios":
|
|
68
|
+
return True
|
|
69
|
+
return os.environ.get("PN_PLATFORM") == "ios"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _detect_web() -> bool:
|
|
73
|
+
"""Detect whether we're running the browser preview.
|
|
74
|
+
|
|
75
|
+
The only signal is the explicit ``PN_PLATFORM=web`` env var, set by
|
|
76
|
+
``pn preview`` / ``pn start`` before importing PythonNative. Web is
|
|
77
|
+
a *development* target: the app renders in a browser tab through
|
|
78
|
+
the bridge protocol so the inner dev loop doesn't require a device
|
|
79
|
+
build. Off-device unit tests deliberately leave this flag ``False``
|
|
80
|
+
so they keep using an injected fake backend and
|
|
81
|
+
``Platform.OS == "test"``.
|
|
82
|
+
"""
|
|
83
|
+
return os.environ.get("PN_PLATFORM") == "web"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _ensure_platform_detection() -> None:
|
|
87
|
+
"""Populate `_is_android` / `_is_ios` / `_is_web` once, then reuse."""
|
|
88
|
+
global _is_android, _is_ios, _is_web
|
|
89
|
+
if _is_android is None:
|
|
90
|
+
_is_android = _detect_android()
|
|
91
|
+
if _is_ios is None:
|
|
92
|
+
_is_ios = (not _is_android) and _detect_ios()
|
|
93
|
+
if _is_web is None:
|
|
94
|
+
_is_web = (not _is_android) and (not _is_ios) and _detect_web()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_is_android() -> bool:
|
|
98
|
+
"""Return the cached Android-detection result."""
|
|
99
|
+
_ensure_platform_detection()
|
|
100
|
+
assert _is_android is not None
|
|
101
|
+
return _is_android
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _get_is_ios() -> bool:
|
|
105
|
+
"""Return the cached iOS-detection result."""
|
|
106
|
+
_ensure_platform_detection()
|
|
107
|
+
assert _is_ios is not None
|
|
108
|
+
return _is_ios
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_is_web() -> bool:
|
|
112
|
+
"""Return the cached web-detection result."""
|
|
113
|
+
_ensure_platform_detection()
|
|
114
|
+
assert _is_web is not None
|
|
115
|
+
return _is_web
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
IS_ANDROID: bool = _get_is_android()
|
|
119
|
+
"""``True`` when running inside an Android process.
|
|
120
|
+
|
|
121
|
+
The flag is computed once at import time, from ``sys.platform ==
|
|
122
|
+
"android"`` or a successful import of Chaquopy's `java` module.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
IS_IOS: bool = _get_is_ios()
|
|
126
|
+
"""``True`` when running inside an iOS app bundle.
|
|
127
|
+
|
|
128
|
+
The flag is computed once at import time, from ``sys.platform ==
|
|
129
|
+
"ios"`` or the explicit `PN_PLATFORM=ios` override.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
IS_WEB: bool = _get_is_web()
|
|
133
|
+
"""``True`` when running the browser preview.
|
|
134
|
+
|
|
135
|
+
Set by ``pn preview`` / ``pn start`` via ``PN_PLATFORM=web``. Mutually
|
|
136
|
+
exclusive with `IS_ANDROID` / `IS_IOS`. Off-device unit tests leave
|
|
137
|
+
this ``False`` and inject a fake backend instead.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
IS_NATIVE: bool = IS_ANDROID or IS_IOS or IS_WEB
|
|
141
|
+
"""``True`` whenever the reconciler commits through the bridge.
|
|
142
|
+
|
|
143
|
+
All three of iOS, Android, and the browser preview render through a
|
|
144
|
+
bridge transport; only headless tests have no native side at all.
|
|
145
|
+
"""
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Version: 1.13.1
|
|
2
|
+
Arguments: ['C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-7660rwxu\\cp313-win_amd64\\build\\venv\\Scripts\\delvewheel', 'repair', '-w', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-7660rwxu\\cp313-win_amd64\\repaired_wheel', '-v', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-7660rwxu\\cp313-win_amd64\\built_wheel\\pythonnative-0.40.0-cp313-cp313-win_amd64.whl']
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythonnative
|
|
3
|
+
Version: 0.40.0
|
|
4
|
+
Summary: Cross-platform native UI toolkit for Android and iOS
|
|
5
|
+
Author: Owen Carey
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Owen Carey
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in
|
|
18
|
+
all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
26
|
+
THE SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/pythonnative/pythonnative
|
|
29
|
+
Project-URL: Repository, https://github.com/pythonnative/pythonnative
|
|
30
|
+
Project-URL: Issues, https://github.com/pythonnative/pythonnative/issues
|
|
31
|
+
Project-URL: Documentation, https://pythonnative.com/
|
|
32
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
39
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
40
|
+
Requires-Python: >=3.13
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: requests>=2.31.0
|
|
44
|
+
Provides-Extra: build
|
|
45
|
+
Requires-Dist: Pillow>=10.0; extra == "build"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<img src="docs/assets/banner.jpg" alt="PythonNative" width="800" />
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<p align="center">
|
|
53
|
+
<em>Build native Android and iOS apps in Python.</em>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<a href="https://github.com/pythonnative/pythonnative/actions/workflows/ci.yml"><img src="https://github.com/pythonnative/pythonnative/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
58
|
+
<a href="https://github.com/pythonnative/pythonnative/actions/workflows/release.yml"><img src="https://github.com/pythonnative/pythonnative/actions/workflows/release.yml/badge.svg" alt="Release" /></a>
|
|
59
|
+
<a href="https://pypi.org/project/pythonnative/"><img src="https://img.shields.io/pypi/v/pythonnative" alt="PyPI Version" /></a>
|
|
60
|
+
<a href="https://pypi.org/project/pythonnative/"><img src="https://img.shields.io/pypi/pyversions/pythonnative" alt="Python Versions" /></a>
|
|
61
|
+
<a href="LICENSE"><img src="https://img.shields.io/pypi/l/pythonnative" alt="License: MIT" /></a>
|
|
62
|
+
<a href="https://pythonnative.com/"><img src="https://img.shields.io/website?url=https%3A%2F%2Fpythonnative.com&label=docs" alt="Docs" /></a>
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<p align="center">
|
|
66
|
+
<a href="https://pythonnative.com/">Documentation</a> ·
|
|
67
|
+
<a href="https://pythonnative.com/getting-started/">Getting Started</a> ·
|
|
68
|
+
<a href="https://pythonnative.com/examples/">Examples</a> ·
|
|
69
|
+
<a href="CONTRIBUTING.md">Contributing</a>
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Overview
|
|
75
|
+
|
|
76
|
+
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation on top of a **native rendering core** written in Swift and Kotlin. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
|
|
77
|
+
|
|
78
|
+
## Features
|
|
79
|
+
|
|
80
|
+
- **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
|
|
81
|
+
- **Rich component library:** 25+ built-in components backed by real native widgets: `TextInput`, `Image` / `ImageBackground`, `ScrollView`, `FlatList` / `SectionList`, `Modal`, `Pressable` / `TouchableOpacity`, `Switch` / `Checkbox`, `Slider`, `SegmentedControl`, `Picker`, `DatePicker`, `ProgressBar` / `ActivityIndicator`, `WebView`, and more.
|
|
82
|
+
- **Device APIs:** Cross-platform modules for `Camera`, `Location`, `FileSystem`, `Notifications`, `Clipboard`, `Share`, `Linking`, `Permissions`, `AppState`, `NetInfo`, `SecureStore`, `Battery`, `Haptics` / `Vibration`, and `Biometrics`, plus reactive `use_app_state` and `use_net_info` hooks.
|
|
83
|
+
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect` / `use_layout_effect`, refs and imperative handles with `use_ref` / `use_imperative_handle`, and navigation with `use_navigation`, all through one consistent pattern. Components can return a single element, a list of siblings, or `None`, and `Fragment`, `Portal`, and reactive `Provider` context work the way they do in React.
|
|
84
|
+
- **Async-first rendering:** One `asyncio` event loop runs the whole framework on the platform's main thread. Components can be `async def` and `await` data directly in the body; `Suspense` boundaries show declarative loading states while they wait. `use_resource` fetches during render, `use_effect` accepts coroutine callbacks (cancelled automatically on unmount), `lazy` code-splits components, and `use_transition` / `use_deferred_value` keep the UI responsive during expensive updates.
|
|
85
|
+
- **Developer feedback that finds your bugs:** In dev mode (`pn start`, `pn preview`, debug builds from `pn run`, or `PN_DEV=1`), uncaught errors from renders, effects, and event handlers show a full-screen RedBox with the traceback; unknown style keys and duplicate list keys print "did you mean" warnings; and conditional hooks raise a `HookOrderError` at the source instead of silently cross-wiring state.
|
|
86
|
+
- **Typed `style` prop and theme:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`, and read design tokens from a typed, immutable `pn.Theme` via `use_theme()` that follows light and dark mode by default.
|
|
87
|
+
- **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
|
|
88
|
+
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation. Each commit lands as **one batched transaction** of mutation ops, and event callbacks are routed through a tag-based registry so re-renders that only change closures cost zero native calls. State updates re-render **locally**: only the component whose state changed (and its subtree) re-runs, and unchanged leaves reuse cached intrinsic measurements, so deep UIs stay responsive instead of re-rendering the whole app from the root on every tap.
|
|
89
|
+
- **Native-driven animations:** The `Animated` API (timing / spring / decay / loop / stagger, awaitable or fire-and-forget) hands animations to Core Animation and `ViewPropertyAnimator` whenever possible, so no Python code runs per frame; a pure-Python ticker covers the rest. `interpolate`, arithmetic operators on animated nodes, `Animated.event` scroll binding, and `diff_clamp` cover the scroll-driven patterns (collapsing headers, parallax) that define native feel.
|
|
90
|
+
- **Native gesture system:** Attach `Tap`, `LongPress`, `Pan`, `Swipe`, `Fling`, `Pinch`, and `Rotation` recognizers to any view via the `gestures=` prop, backed by `UIGestureRecognizer` on iOS and a unit-testable pure-Python arbiter on Android and in the browser preview. Compose them with `Race`, `Exclusive`, and `Simultaneous` for cross-gesture arbitration (single vs. double tap, drag vs. long press).
|
|
91
|
+
- **Virtualized lists:** `FlatList` / `SectionList` window their rows in Python over the platform scroll view: uniform, exact, or measured variable heights, grids, headers/footers, infinite scroll, and an imperative scroll controller, identical on every platform.
|
|
92
|
+
- **Native rendering core:** each commit is one serialized transaction applied by Swift and Kotlin component managers, the same shape as React Native's Fabric. Device APIs are native modules registered by name (TurboModules-style), callable from thin Python facades.
|
|
93
|
+
- **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group.
|
|
94
|
+
- **Metro-style dev loop:** `pn start` runs one dev server that serves the browser preview and every connected debug build. Save a file and each client Fast Refreshes in place, keeping component state; their `print` output and tracebacks stream back into the same terminal. `pn run android` / `pn run ios` bakes the server URL into a debug build and rebuilds the native project only when a native input actually changed, so relaunching after a Python edit takes seconds, not minutes.
|
|
95
|
+
- **PyPI packages, binary wheels included:** List requirements in `pythonnative.toml` and the CLI resolves them for the *device* (iOS wheels via PEP 730 and BeeWare's index, Android wheels via PEP 738 and Chaquopy), bundling numpy, Pillow, cryptography, and friends alongside pure-Python packages. `pn deps` reports what each target resolves to before you build, and a weekly-checked compatibility matrix keeps the docs honest.
|
|
96
|
+
- **Browser preview:** `pn preview` renders your app in a browser tab inside a phone frame (pick a device, rotate, toggle dark mode) with Fast Refresh on every save. The page is a bridge peer exactly like the Swift and Kotlin runtimes, so the reconciler, hooks, layout engine, navigation, and screen host are the same code that ships to the phone; only the leaf widgets are DOM elements.
|
|
97
|
+
- **Native-backed navigation:** Declarative `Stack`, `Tab`, and `Drawer` navigators inspired by React Navigation. The root stack drives the platform's native navigation controller (`UINavigationController` on iOS, AndroidX Navigation Component on Android), so transitions, back gestures, and the hardware back button match what users expect; `use_back_handler` intercepts the back action when a screen needs to.
|
|
98
|
+
- **Dev client:** Build a shell app once with `pn run ios --dev-client` and point it at any project's dev server from a connect screen, the way Expo Go does; physical phones on the same Wi-Fi get the same Fast Refresh as simulators.
|
|
99
|
+
- **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
|
|
100
|
+
|
|
101
|
+
## Quick Start
|
|
102
|
+
|
|
103
|
+
### Installation
|
|
104
|
+
|
|
105
|
+
Requires Python 3.13 or newer.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install pythonnative
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Usage
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import pythonnative as pn
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pn.component
|
|
118
|
+
def App():
|
|
119
|
+
count, set_count = pn.use_state(0)
|
|
120
|
+
return pn.Column(
|
|
121
|
+
pn.Text(f"Count: {count}", style=pn.style(font_size=24, bold=True)),
|
|
122
|
+
pn.Button(
|
|
123
|
+
"Tap me",
|
|
124
|
+
on_press=lambda: set_count(count + 1),
|
|
125
|
+
),
|
|
126
|
+
style=pn.style(spacing=12, padding=16),
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Develop
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pn init my-app && cd my-app
|
|
134
|
+
pn preview # dev server + browser preview with Fast Refresh
|
|
135
|
+
pn run ios # in another terminal: debug build that connects to the same server
|
|
136
|
+
pn run android
|
|
137
|
+
pn build ios # standalone release artifacts
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Documentation
|
|
141
|
+
|
|
142
|
+
Visit [pythonnative.com](https://pythonnative.com/) for the full documentation, including getting started guides, platform-specific instructions for Android and iOS, API reference, and working examples.
|
|
143
|
+
|
|
144
|
+
## Contributing
|
|
145
|
+
|
|
146
|
+
Contributions are welcome. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, coding standards, and guidelines for submitting pull requests.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
[MIT](LICENSE)
|