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,585 @@
|
|
|
1
|
+
"""Device-side module reloading for Fast Refresh.
|
|
2
|
+
|
|
3
|
+
The dev server (``pythonnative.devserver``) watches the project's
|
|
4
|
+
``app/`` directory and tells every connected dev client which files
|
|
5
|
+
changed. This module is the client's other half: it re-executes the
|
|
6
|
+
changed modules with ``importlib`` and refreshes every mounted screen.
|
|
7
|
+
|
|
8
|
+
Two strategies share the surface:
|
|
9
|
+
|
|
10
|
+
- **Fast Refresh** (default): after reloading the changed modules the
|
|
11
|
+
reconciler tree is walked and every component function whose module
|
|
12
|
+
was reloaded is swapped in place. Hook state, navigation state, and
|
|
13
|
+
even scroll positions survive because the underlying ``VNode``
|
|
14
|
+
objects are reused; the next render simply calls the new function
|
|
15
|
+
bodies through the old slots.
|
|
16
|
+
- **Full remount**: when the in-place swap fails (e.g. the new module
|
|
17
|
+
raised at import time, or a render exception bubbled out while
|
|
18
|
+
running the new function), the host falls back to building a
|
|
19
|
+
brand-new reconciler tree. State is lost but the app keeps running.
|
|
20
|
+
|
|
21
|
+
[`apply_reload`][pythonnative.hot_reload.apply_reload] is the single
|
|
22
|
+
entry point: it reloads once per process (several screens may be
|
|
23
|
+
mounted) and then refreshes each live host.
|
|
24
|
+
|
|
25
|
+
On device, sources arrive in a writable **overlay** directory that
|
|
26
|
+
shadows the app bundle (see
|
|
27
|
+
[`configure_dev_environment`][pythonnative.hot_reload.configure_dev_environment]);
|
|
28
|
+
under ``pn preview`` the project directory itself is on ``sys.path``
|
|
29
|
+
and there is no overlay.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import importlib
|
|
35
|
+
import importlib.util
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
import threading
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"DEV_ROOT_DIR",
|
|
44
|
+
"ModuleReloader",
|
|
45
|
+
"ReloadResult",
|
|
46
|
+
"apply_reload",
|
|
47
|
+
"configure_dev_environment",
|
|
48
|
+
"overlay_root",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
DEV_ROOT_DIR = "pythonnative_dev"
|
|
52
|
+
"""Name of the writable on-device directory that shadows bundled app code."""
|
|
53
|
+
|
|
54
|
+
_OVERLAY_ENV = "PYTHONNATIVE_HOT_RELOAD_ROOT"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def configure_dev_environment(writable_root: str, server_url: Optional[str] = None) -> str:
|
|
58
|
+
"""Create and prioritize the writable source overlay.
|
|
59
|
+
|
|
60
|
+
The returned directory is inserted at the front of `sys.path`, so a
|
|
61
|
+
synced `app/main.py` shadows the copy bundled into the native
|
|
62
|
+
application. Debug templates call this before importing user code.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
writable_root: Platform data directory that the app can write to
|
|
66
|
+
(Android `filesDir`, iOS `Documents`, or a test directory).
|
|
67
|
+
server_url: The dev server this launch should connect to, when
|
|
68
|
+
the launcher passed one (``pn run`` does, through a launch
|
|
69
|
+
environment variable on iOS and an intent extra on
|
|
70
|
+
Android). It is exported as ``PN_DEV_SERVER`` so the dev
|
|
71
|
+
client picks it up; a remembered server is used otherwise.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Absolute path to the overlay root.
|
|
75
|
+
"""
|
|
76
|
+
dev_root = os.path.abspath(os.path.join(writable_root, DEV_ROOT_DIR))
|
|
77
|
+
os.makedirs(os.path.join(dev_root, "app"), exist_ok=True)
|
|
78
|
+
if dev_root in sys.path:
|
|
79
|
+
sys.path.remove(dev_root)
|
|
80
|
+
sys.path.insert(0, dev_root)
|
|
81
|
+
os.environ[_OVERLAY_ENV] = dev_root
|
|
82
|
+
if server_url:
|
|
83
|
+
os.environ["PN_DEV_SERVER"] = str(server_url)
|
|
84
|
+
return dev_root
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def overlay_root() -> Optional[str]:
|
|
88
|
+
"""The overlay directory configured for this process, if any."""
|
|
89
|
+
return os.environ.get(_OVERLAY_ENV) or None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _overlay_module_path(module_name: str) -> Optional[str]:
|
|
93
|
+
dev_root = overlay_root()
|
|
94
|
+
if not dev_root:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
rel_parts = module_name.split(".")
|
|
98
|
+
module_path = os.path.join(dev_root, *rel_parts) + ".py"
|
|
99
|
+
if os.path.exists(module_path):
|
|
100
|
+
return module_path
|
|
101
|
+
|
|
102
|
+
package_path = os.path.join(dev_root, *rel_parts, "__init__.py")
|
|
103
|
+
if os.path.exists(package_path):
|
|
104
|
+
return package_path
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ======================================================================
|
|
110
|
+
# Module reloader
|
|
111
|
+
# ======================================================================
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ModuleReloader:
|
|
115
|
+
"""Reload changed Python modules and rewrite mounted trees to match.
|
|
116
|
+
|
|
117
|
+
All methods are static; the class is a namespace. The tree-rewrite
|
|
118
|
+
helpers (``build_replacement_map``, ``swap_components_in_tree``,
|
|
119
|
+
``refresh_in_place``) are what make Fast Refresh state-preserving.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
_reload_lock = threading.Lock()
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def reload_module(module_name: str) -> bool:
|
|
126
|
+
"""Reload a single module by its dotted name.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
module_name: Dotted module name (e.g., `"app.main"`).
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
`True` if the module imported successfully from the current
|
|
133
|
+
`sys.path`; `False` otherwise (the previous module object is
|
|
134
|
+
restored so the app keeps running).
|
|
135
|
+
"""
|
|
136
|
+
previous = sys.modules.get(module_name)
|
|
137
|
+
try:
|
|
138
|
+
importlib.invalidate_caches()
|
|
139
|
+
overlay_path = _overlay_module_path(module_name)
|
|
140
|
+
if overlay_path is not None:
|
|
141
|
+
spec = importlib.util.spec_from_file_location(module_name, overlay_path)
|
|
142
|
+
if spec is None or spec.loader is None:
|
|
143
|
+
return False
|
|
144
|
+
module = importlib.util.module_from_spec(spec)
|
|
145
|
+
sys.modules[module_name] = module
|
|
146
|
+
spec.loader.exec_module(module)
|
|
147
|
+
else:
|
|
148
|
+
sys.modules.pop(module_name, None)
|
|
149
|
+
importlib.import_module(module_name)
|
|
150
|
+
return True
|
|
151
|
+
except Exception:
|
|
152
|
+
if previous is not None:
|
|
153
|
+
sys.modules[module_name] = previous
|
|
154
|
+
else:
|
|
155
|
+
sys.modules.pop(module_name, None)
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def reload_modules(module_names: Sequence[str]) -> List[str]:
|
|
160
|
+
"""Reload ``module_names`` in order, returning the names that succeeded."""
|
|
161
|
+
importlib.invalidate_caches()
|
|
162
|
+
reloaded: List[str] = []
|
|
163
|
+
seen: set[str] = set()
|
|
164
|
+
with ModuleReloader._reload_lock:
|
|
165
|
+
for module_name in module_names:
|
|
166
|
+
if not module_name or module_name in seen:
|
|
167
|
+
continue
|
|
168
|
+
seen.add(module_name)
|
|
169
|
+
if ModuleReloader.reload_module(module_name):
|
|
170
|
+
reloaded.append(module_name)
|
|
171
|
+
return reloaded
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def reload_module_strict(module_name: str) -> None:
|
|
175
|
+
"""Reload one module, propagating the import error instead of swallowing it.
|
|
176
|
+
|
|
177
|
+
Used by the dev client so a syntax error in a saved file shows
|
|
178
|
+
up in the RedBox and the terminal rather than as a silent
|
|
179
|
+
"nothing reloaded".
|
|
180
|
+
"""
|
|
181
|
+
previous = sys.modules.get(module_name)
|
|
182
|
+
try:
|
|
183
|
+
importlib.invalidate_caches()
|
|
184
|
+
overlay_path = _overlay_module_path(module_name)
|
|
185
|
+
if overlay_path is not None:
|
|
186
|
+
spec = importlib.util.spec_from_file_location(module_name, overlay_path)
|
|
187
|
+
if spec is None or spec.loader is None:
|
|
188
|
+
raise ImportError(f"cannot load {module_name} from {overlay_path}")
|
|
189
|
+
module = importlib.util.module_from_spec(spec)
|
|
190
|
+
sys.modules[module_name] = module
|
|
191
|
+
spec.loader.exec_module(module)
|
|
192
|
+
else:
|
|
193
|
+
sys.modules.pop(module_name, None)
|
|
194
|
+
importlib.import_module(module_name)
|
|
195
|
+
except Exception:
|
|
196
|
+
if previous is not None:
|
|
197
|
+
sys.modules[module_name] = previous
|
|
198
|
+
else:
|
|
199
|
+
sys.modules.pop(module_name, None)
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def expand_reload_targets(changed_modules: Sequence[str], component_path: str) -> List[str]:
|
|
204
|
+
"""Expand a set of changed modules into the full reload order.
|
|
205
|
+
|
|
206
|
+
When a user edits ``app/screens/home.py``, only that module is
|
|
207
|
+
reported. But the entry-point module ``app.main`` has bindings
|
|
208
|
+
like ``from app.screens.home import HomeScreen`` that need to be
|
|
209
|
+
re-evaluated against the freshly-loaded ``app.screens.home``;
|
|
210
|
+
likewise other user-app modules may carry transitive bindings
|
|
211
|
+
(e.g. through a shared ``app/theme.py``) that go stale if only
|
|
212
|
+
the changed file is reloaded.
|
|
213
|
+
|
|
214
|
+
The order is:
|
|
215
|
+
|
|
216
|
+
1. Explicitly changed modules first (in the order given), so
|
|
217
|
+
their fresh source replaces the cached version in
|
|
218
|
+
``sys.modules`` before any dependent modules re-execute.
|
|
219
|
+
2. All other currently-imported modules under the entry-point's
|
|
220
|
+
top-level package, deepest first. The depth heuristic biases
|
|
221
|
+
toward leaves so re-executing a screen file picks up the
|
|
222
|
+
newest shared utilities before the file that imports it does.
|
|
223
|
+
3. The entry-point module itself, last, so its
|
|
224
|
+
``from ... import`` bindings rebind against everything that
|
|
225
|
+
was refreshed in steps 1 and 2.
|
|
226
|
+
|
|
227
|
+
Modules outside the entry-point's top-level package
|
|
228
|
+
(``pythonnative.*``, stdlib, third-party) are never included;
|
|
229
|
+
framework code is not reloaded.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
changed_modules: Modules reported as changed (dotted form).
|
|
233
|
+
component_path: The host's entry-point identifier, either a
|
|
234
|
+
module path (``"app.main"``) or a dotted attribute path
|
|
235
|
+
(``"app.main.RootScreen"``).
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The ordered list of modules to feed to
|
|
239
|
+
[`reload_modules`][pythonnative.hot_reload.ModuleReloader.reload_modules].
|
|
240
|
+
"""
|
|
241
|
+
entry_module: Optional[str] = None
|
|
242
|
+
if component_path in sys.modules:
|
|
243
|
+
entry_module = component_path
|
|
244
|
+
elif "." in component_path:
|
|
245
|
+
parent = component_path.rsplit(".", 1)[0]
|
|
246
|
+
if parent in sys.modules:
|
|
247
|
+
entry_module = parent
|
|
248
|
+
|
|
249
|
+
app_prefix: Optional[str] = None
|
|
250
|
+
if entry_module:
|
|
251
|
+
app_prefix = entry_module.split(".")[0]
|
|
252
|
+
else:
|
|
253
|
+
for m in changed_modules:
|
|
254
|
+
if m:
|
|
255
|
+
app_prefix = m.split(".")[0]
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
app_modules: Set[str] = set()
|
|
259
|
+
if app_prefix:
|
|
260
|
+
for name in list(sys.modules):
|
|
261
|
+
if name == app_prefix or name.startswith(app_prefix + "."):
|
|
262
|
+
app_modules.add(name)
|
|
263
|
+
|
|
264
|
+
ordered: List[str] = []
|
|
265
|
+
seen: Set[str] = set()
|
|
266
|
+
for m in changed_modules:
|
|
267
|
+
if m and m not in seen:
|
|
268
|
+
ordered.append(m)
|
|
269
|
+
seen.add(m)
|
|
270
|
+
|
|
271
|
+
others = [m for m in app_modules if m not in seen and m != entry_module]
|
|
272
|
+
others.sort(key=lambda m: (-m.count("."), m))
|
|
273
|
+
for m in others:
|
|
274
|
+
ordered.append(m)
|
|
275
|
+
seen.add(m)
|
|
276
|
+
|
|
277
|
+
if entry_module:
|
|
278
|
+
if entry_module in seen:
|
|
279
|
+
ordered.remove(entry_module)
|
|
280
|
+
ordered.append(entry_module)
|
|
281
|
+
|
|
282
|
+
return ordered
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def file_to_module(file_path: str, base_dir: str = "") -> Optional[str]:
|
|
286
|
+
"""Convert a file path to a dotted module name.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
file_path: Path to a `.py` file (absolute or relative).
|
|
290
|
+
base_dir: Base directory that names should be relative to.
|
|
291
|
+
If empty, `file_path` is treated as already relative.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
The dotted module name (e.g., `"app.screens.home"`), or
|
|
295
|
+
`None` for an empty path.
|
|
296
|
+
"""
|
|
297
|
+
rel = os.path.relpath(file_path, base_dir) if base_dir else file_path
|
|
298
|
+
rel = rel.replace("\\", os.sep).replace("/", os.sep).lstrip(os.sep)
|
|
299
|
+
if rel.endswith(".py"):
|
|
300
|
+
rel = rel[:-3]
|
|
301
|
+
parts = rel.replace(os.sep, ".").split(".")
|
|
302
|
+
if parts[-1] == "__init__":
|
|
303
|
+
parts = parts[:-1]
|
|
304
|
+
return ".".join(parts) if parts else None
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def modules_from_files(file_paths: Sequence[str], base_dir: str = "") -> List[str]:
|
|
308
|
+
"""Convert Python source paths to importable module names."""
|
|
309
|
+
modules: List[str] = []
|
|
310
|
+
for file_path in file_paths:
|
|
311
|
+
module = ModuleReloader.file_to_module(file_path, base_dir=base_dir)
|
|
312
|
+
if module is not None:
|
|
313
|
+
modules.append(module)
|
|
314
|
+
return modules
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def find_replacement_function(old_fn: Any) -> Optional[Any]:
|
|
318
|
+
"""Locate a function's post-reload counterpart by qualname.
|
|
319
|
+
|
|
320
|
+
[`Component`][pythonnative.Component] objects forward
|
|
321
|
+
``__module__`` / ``__qualname__`` from the render function they
|
|
322
|
+
wrap, so the reconciler's stored ``element.type`` carries the
|
|
323
|
+
information needed to re-resolve after a module reload.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
old_fn: The function captured in an
|
|
327
|
+
[`Element`][pythonnative.Element]'s ``type`` slot.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
The reloaded module's matching function, ``None`` if no
|
|
331
|
+
replacement was found, or the original function itself
|
|
332
|
+
when the module has not been reloaded (so callers can
|
|
333
|
+
skip the swap).
|
|
334
|
+
"""
|
|
335
|
+
module_name = getattr(old_fn, "__module__", None)
|
|
336
|
+
qualname = getattr(old_fn, "__qualname__", None) or getattr(old_fn, "__name__", None)
|
|
337
|
+
if not module_name or not qualname:
|
|
338
|
+
return None
|
|
339
|
+
if "<locals>" in qualname:
|
|
340
|
+
return None # nested functions are not addressable from the module surface
|
|
341
|
+
|
|
342
|
+
module = sys.modules.get(module_name)
|
|
343
|
+
if module is None:
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
obj: Any = module
|
|
347
|
+
for part in qualname.split("."):
|
|
348
|
+
obj = getattr(obj, part, None)
|
|
349
|
+
if obj is None:
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
if obj is old_fn:
|
|
353
|
+
return None
|
|
354
|
+
return obj
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def build_replacement_map(reconciler: Any, reloaded_modules: Iterable[str]) -> Dict[Any, Any]:
|
|
358
|
+
"""Compute ``{old_function: new_function}`` for one tree.
|
|
359
|
+
|
|
360
|
+
The reconciler's stored tree references the *pre-reload*
|
|
361
|
+
component functions through ``VNode.element.type``. This
|
|
362
|
+
method walks the tree, collects every callable type whose
|
|
363
|
+
``__module__`` was just reloaded, and asks
|
|
364
|
+
[`find_replacement_function`][pythonnative.hot_reload.ModuleReloader.find_replacement_function]
|
|
365
|
+
for its successor.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
reconciler: The reconciler whose mounted ``root`` should be inspected.
|
|
369
|
+
reloaded_modules: Set of module names that were just
|
|
370
|
+
reloaded (only callables from these modules are
|
|
371
|
+
considered).
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
A mapping suitable for passing to
|
|
375
|
+
[`swap_components_in_tree`][pythonnative.hot_reload.ModuleReloader.swap_components_in_tree].
|
|
376
|
+
"""
|
|
377
|
+
modules: Set[str] = {m for m in reloaded_modules if m}
|
|
378
|
+
if not modules or reconciler is None or getattr(reconciler, "root", None) is None:
|
|
379
|
+
return {}
|
|
380
|
+
|
|
381
|
+
seen: Set[int] = set()
|
|
382
|
+
mapping: Dict[Any, Any] = {}
|
|
383
|
+
|
|
384
|
+
def visit(vnode: Any) -> None:
|
|
385
|
+
if vnode is None:
|
|
386
|
+
return
|
|
387
|
+
elem = getattr(vnode, "element", None)
|
|
388
|
+
if elem is not None and callable(elem.type):
|
|
389
|
+
fn = elem.type
|
|
390
|
+
fn_id = id(fn)
|
|
391
|
+
if fn_id not in seen:
|
|
392
|
+
seen.add(fn_id)
|
|
393
|
+
if getattr(fn, "__module__", None) in modules:
|
|
394
|
+
replacement = ModuleReloader.find_replacement_function(fn)
|
|
395
|
+
if replacement is not None and replacement is not fn:
|
|
396
|
+
mapping[fn] = replacement
|
|
397
|
+
for child in getattr(vnode, "children", []) or []:
|
|
398
|
+
visit(child)
|
|
399
|
+
|
|
400
|
+
visit(reconciler.root)
|
|
401
|
+
return mapping
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
def swap_components_in_tree(reconciler: Any, replacement_map: Dict[Any, Any]) -> int:
|
|
405
|
+
"""Apply a ``{old: new}`` map to every node in the reconciler tree.
|
|
406
|
+
|
|
407
|
+
Mutates ``vnode.element.type`` directly so the NEXT diff sees
|
|
408
|
+
identical types and reuses VNodes (preserving hook state).
|
|
409
|
+
The element lists stored on ``vnode.rendered`` are rewritten
|
|
410
|
+
too because the reconciler reads from them when comparing keys
|
|
411
|
+
across renders.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
The number of element type references that were rewritten.
|
|
415
|
+
"""
|
|
416
|
+
if not replacement_map or reconciler is None or getattr(reconciler, "root", None) is None:
|
|
417
|
+
return 0
|
|
418
|
+
|
|
419
|
+
rewrites = 0
|
|
420
|
+
|
|
421
|
+
def rewrite_element_tree(element: Any) -> None:
|
|
422
|
+
nonlocal rewrites
|
|
423
|
+
if element is None:
|
|
424
|
+
return
|
|
425
|
+
new_type = replacement_map.get(element.type)
|
|
426
|
+
if new_type is not None:
|
|
427
|
+
element.type = new_type
|
|
428
|
+
rewrites += 1
|
|
429
|
+
for child in element.children or []:
|
|
430
|
+
rewrite_element_tree(child)
|
|
431
|
+
|
|
432
|
+
def visit(vnode: Any) -> None:
|
|
433
|
+
if vnode is None:
|
|
434
|
+
return
|
|
435
|
+
if getattr(vnode, "element", None) is not None:
|
|
436
|
+
rewrite_element_tree(vnode.element)
|
|
437
|
+
rendered = getattr(vnode, "rendered", None)
|
|
438
|
+
if isinstance(rendered, (list, tuple)):
|
|
439
|
+
for rendered_el in rendered:
|
|
440
|
+
rewrite_element_tree(rendered_el)
|
|
441
|
+
elif rendered is not None:
|
|
442
|
+
rewrite_element_tree(rendered)
|
|
443
|
+
for child in getattr(vnode, "children", []) or []:
|
|
444
|
+
visit(child)
|
|
445
|
+
|
|
446
|
+
visit(reconciler.root)
|
|
447
|
+
return rewrites
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def refresh_in_place(reconciler: Any, reloaded_modules: Iterable[str]) -> bool:
|
|
451
|
+
"""Try a state-preserving Fast Refresh for one reconciler.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
``True`` if any component function was replaced (callers
|
|
455
|
+
should then trigger a re-render). ``False`` means the
|
|
456
|
+
tree already references the latest functions (or has no
|
|
457
|
+
nodes from the reloaded modules at all).
|
|
458
|
+
"""
|
|
459
|
+
replacement_map = ModuleReloader.build_replacement_map(reconciler, reloaded_modules)
|
|
460
|
+
if not replacement_map:
|
|
461
|
+
return False
|
|
462
|
+
compatible = {
|
|
463
|
+
old: new
|
|
464
|
+
for old, new in replacement_map.items()
|
|
465
|
+
if getattr(old, "refresh_signature", None) is not None
|
|
466
|
+
and old.refresh_signature == getattr(new, "refresh_signature", None)
|
|
467
|
+
}
|
|
468
|
+
ModuleReloader.swap_components_in_tree(reconciler, compatible)
|
|
469
|
+
# Incompatible types remain old in the mounted tree. The next ordinary
|
|
470
|
+
# diff remounts those instances when their parent renders the new type.
|
|
471
|
+
return True
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
# ======================================================================
|
|
475
|
+
# Process-wide reload
|
|
476
|
+
# ======================================================================
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@dataclass
|
|
480
|
+
class ReloadResult:
|
|
481
|
+
"""What [`apply_reload`][pythonnative.hot_reload.apply_reload] did.
|
|
482
|
+
|
|
483
|
+
Attributes:
|
|
484
|
+
requested: Modules the caller reported as changed.
|
|
485
|
+
reloaded: Modules actually re-executed (in reload order).
|
|
486
|
+
mode: ``"fast_refresh"`` when every host refreshed in place,
|
|
487
|
+
``"remount"`` when at least one fell back to a full remount,
|
|
488
|
+
``"error"`` when a host hit an exception (shown in its
|
|
489
|
+
RedBox), or ``"none"`` when nothing could be reloaded.
|
|
490
|
+
error: The import error text when a changed module failed to
|
|
491
|
+
execute (the previous module stays in ``sys.modules``).
|
|
492
|
+
hosts: Number of hosts refreshed.
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
requested: List[str] = field(default_factory=list)
|
|
496
|
+
reloaded: List[str] = field(default_factory=list)
|
|
497
|
+
mode: str = "none"
|
|
498
|
+
error: Optional[str] = None
|
|
499
|
+
hosts: int = 0
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def apply_reload(changed_modules: Sequence[str], hosts: Optional[Sequence[Any]] = None) -> ReloadResult:
|
|
503
|
+
"""Reload ``changed_modules`` once and refresh every mounted screen.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
changed_modules: Dotted module names whose source changed.
|
|
507
|
+
hosts: Screen hosts to refresh; defaults to every live host on
|
|
508
|
+
the current platform (``pythonnative.hosts.live_hosts``).
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
A [`ReloadResult`][pythonnative.hot_reload.ReloadResult].
|
|
512
|
+
"""
|
|
513
|
+
import traceback
|
|
514
|
+
|
|
515
|
+
from . import diagnostics
|
|
516
|
+
|
|
517
|
+
if hosts is None:
|
|
518
|
+
from .hosts import live_hosts
|
|
519
|
+
|
|
520
|
+
hosts = list(live_hosts())
|
|
521
|
+
result = ReloadResult(requested=[m for m in changed_modules if m])
|
|
522
|
+
if not result.requested or not hosts:
|
|
523
|
+
# Nothing changed, or nothing is mounted yet (the files landed
|
|
524
|
+
# before the first screen, and the first import will read them).
|
|
525
|
+
return result
|
|
526
|
+
entry = hosts[0].component_path
|
|
527
|
+
# A changed module that was never imported needs no re-execution;
|
|
528
|
+
# re-running the entry module (always last) imports it if it's used.
|
|
529
|
+
imported = [m for m in result.requested if m in sys.modules]
|
|
530
|
+
# Instances of application classes can live in memo or ref slots. Replacing
|
|
531
|
+
# their defining module requires new instances, even if hooks didn't move.
|
|
532
|
+
from .component import Component
|
|
533
|
+
|
|
534
|
+
remount = any(
|
|
535
|
+
(isinstance(value, type) or callable(value))
|
|
536
|
+
and not isinstance(value, Component)
|
|
537
|
+
and getattr(value, "__module__", None) == name
|
|
538
|
+
and not key.startswith(("_", "use_"))
|
|
539
|
+
for name in imported
|
|
540
|
+
for key, value in vars(sys.modules[name]).items()
|
|
541
|
+
)
|
|
542
|
+
targets = ModuleReloader.expand_reload_targets(imported, entry)
|
|
543
|
+
|
|
544
|
+
# Changed modules are reloaded strictly so a syntax error is reported
|
|
545
|
+
# with its traceback; the dependents (which didn't change) reload
|
|
546
|
+
# leniently, since a failure there is almost always caused by the
|
|
547
|
+
# same error and would only repeat it.
|
|
548
|
+
reloaded: List[str] = []
|
|
549
|
+
for name in targets:
|
|
550
|
+
if name in imported:
|
|
551
|
+
try:
|
|
552
|
+
ModuleReloader.reload_module_strict(name)
|
|
553
|
+
except Exception as exc:
|
|
554
|
+
result.error = traceback.format_exc()
|
|
555
|
+
for host in hosts:
|
|
556
|
+
if diagnostics.is_dev():
|
|
557
|
+
host.show_redbox(exc, phase=f"reloading {name}")
|
|
558
|
+
result.mode = "error"
|
|
559
|
+
return result
|
|
560
|
+
reloaded.append(name)
|
|
561
|
+
elif ModuleReloader.reload_module(name):
|
|
562
|
+
reloaded.append(name)
|
|
563
|
+
result.reloaded = reloaded
|
|
564
|
+
if not reloaded:
|
|
565
|
+
return result
|
|
566
|
+
|
|
567
|
+
modes: List[str] = []
|
|
568
|
+
for host in hosts:
|
|
569
|
+
try:
|
|
570
|
+
modes.append(host.refresh(reloaded, force_remount=True) if remount else host.refresh(reloaded))
|
|
571
|
+
except Exception as exc:
|
|
572
|
+
result.error = traceback.format_exc()
|
|
573
|
+
if diagnostics.is_dev():
|
|
574
|
+
host.show_redbox(exc, phase="hot reload")
|
|
575
|
+
modes.append("error")
|
|
576
|
+
result.hosts = len(hosts)
|
|
577
|
+
if "error" in modes:
|
|
578
|
+
result.mode = "error"
|
|
579
|
+
elif "remount" in modes:
|
|
580
|
+
result.mode = "remount"
|
|
581
|
+
elif "fast_refresh" in modes:
|
|
582
|
+
result.mode = "fast_refresh"
|
|
583
|
+
else:
|
|
584
|
+
result.mode = "fast_refresh"
|
|
585
|
+
return result
|