pythonnative 0.40.0__cp314-cp314-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.cp314-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,149 @@
|
|
|
1
|
+
"""Screen definitions and the typed options a screen accepts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Callable, Dict, Literal, Mapping, Optional, TypedDict, Union
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 11):
|
|
9
|
+
from typing import Unpack
|
|
10
|
+
else: # pragma: no cover
|
|
11
|
+
from typing_extensions import Unpack
|
|
12
|
+
|
|
13
|
+
from ..element import Element
|
|
14
|
+
|
|
15
|
+
__all__ = ["HeaderSlot", "ScreenDef", "ScreenOptions", "Unpack"]
|
|
16
|
+
|
|
17
|
+
HeaderSlot = Union[Element, Callable[[], Optional[Element]], None]
|
|
18
|
+
"""An element (or zero-arg factory) rendered into a header slot."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ScreenOptions(TypedDict, total=False):
|
|
22
|
+
"""Per-screen options accepted by ``Screen(...)`` and ``nav.set_options(...)``.
|
|
23
|
+
|
|
24
|
+
All keys are optional. Navigators ignore keys they don't use; the
|
|
25
|
+
native host applies the header keys it can (see the platform notes
|
|
26
|
+
on each key).
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
title: Screen title. Stack navigators show it in the native
|
|
30
|
+
navigation bar; tab and drawer navigators use it as the item
|
|
31
|
+
label.
|
|
32
|
+
header_shown: Whether the native navigation bar is visible for
|
|
33
|
+
this screen (default ``True``).
|
|
34
|
+
header_large_title: iOS: use a large title that collapses on
|
|
35
|
+
scroll. Ignored elsewhere.
|
|
36
|
+
header_back_title: iOS: label of the back button shown on the
|
|
37
|
+
*next* screen when it navigates back to this one. Ignored
|
|
38
|
+
elsewhere.
|
|
39
|
+
header_back_visible: Whether the back button is shown
|
|
40
|
+
(default ``True``).
|
|
41
|
+
header_left: Element (or factory) rendered at the leading edge
|
|
42
|
+
of the navigation bar. Rendered by PythonNative into the bar
|
|
43
|
+
on iOS; ignored on Android and in the browser preview today.
|
|
44
|
+
header_right: Element (or factory) rendered at the trailing edge
|
|
45
|
+
of the navigation bar (same platform notes as
|
|
46
|
+
``header_left``).
|
|
47
|
+
header_tint_color: Color of the bar's buttons and back chevron.
|
|
48
|
+
header_style: Style dict for the bar itself; ``background_color``
|
|
49
|
+
is honored on every platform that draws a bar.
|
|
50
|
+
header_title_style: Style dict for the title label
|
|
51
|
+
(``color``, ``font_size``, ``bold``).
|
|
52
|
+
presentation: ``"card"`` (default) pushes; ``"modal"`` presents
|
|
53
|
+
the screen as a sheet on iOS and as a full-screen push
|
|
54
|
+
elsewhere.
|
|
55
|
+
gesture_enabled: Whether the interactive back gesture (iOS
|
|
56
|
+
swipe) can pop this screen (default ``True``).
|
|
57
|
+
animation: Transition to use when the screen is pushed:
|
|
58
|
+
``"default"``, ``"none"``, ``"fade"``, ``"slide_from_right"``,
|
|
59
|
+
``"slide_from_bottom"``. Only ``"none"`` versus animated is
|
|
60
|
+
distinguished on iOS and Android today.
|
|
61
|
+
tab_bar_icon: Native system icon identifier for tab items. A
|
|
62
|
+
string is used on every platform; a dict like
|
|
63
|
+
``{"ios": "house.fill", "android": "ic_menu_home"}`` selects
|
|
64
|
+
per platform (SF Symbols on iOS, ``android.R.drawable.<name>``
|
|
65
|
+
on Android).
|
|
66
|
+
tab_bar_badge: Badge text or count shown on the tab item.
|
|
67
|
+
tab_bar_label: Label used for the tab item when it should differ
|
|
68
|
+
from ``title``.
|
|
69
|
+
lazy: Tab and drawer navigators only: mount the screen the first
|
|
70
|
+
time it's focused (default ``True``) instead of at
|
|
71
|
+
navigator mount.
|
|
72
|
+
unmount_on_blur: Tab and drawer navigators only: unmount the
|
|
73
|
+
screen when it loses focus instead of keeping it alive
|
|
74
|
+
hidden (default ``False``).
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
title: str
|
|
78
|
+
header_shown: bool
|
|
79
|
+
header_large_title: bool
|
|
80
|
+
header_back_title: str
|
|
81
|
+
header_back_visible: bool
|
|
82
|
+
header_left: HeaderSlot
|
|
83
|
+
header_right: HeaderSlot
|
|
84
|
+
header_tint_color: str
|
|
85
|
+
header_style: Dict[str, Any]
|
|
86
|
+
header_title_style: Dict[str, Any]
|
|
87
|
+
presentation: Literal["card", "modal"]
|
|
88
|
+
gesture_enabled: bool
|
|
89
|
+
animation: Literal["default", "none", "fade", "slide_from_right", "slide_from_bottom"]
|
|
90
|
+
tab_bar_icon: Union[str, Dict[str, str]]
|
|
91
|
+
tab_bar_badge: Union[str, int]
|
|
92
|
+
tab_bar_label: str
|
|
93
|
+
lazy: bool
|
|
94
|
+
unmount_on_blur: bool
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ScreenDef:
|
|
98
|
+
"""Configuration for one screen inside a navigator.
|
|
99
|
+
|
|
100
|
+
Created by ``Navigator.Screen(name, component, **options)``.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
name: Route name used by ``nav.navigate(name)``.
|
|
104
|
+
component: The ``@component`` rendered when this screen is
|
|
105
|
+
active. Receives no props; read params with
|
|
106
|
+
[`use_route`][pythonnative.use_route].
|
|
107
|
+
options: Static [`ScreenOptions`][pythonnative.ScreenOptions]
|
|
108
|
+
for the screen. May be a callable ``(route) -> options`` to
|
|
109
|
+
derive options from the route's params.
|
|
110
|
+
initial_params: Params merged under any params supplied by
|
|
111
|
+
``navigate`` when this screen is first shown.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
__slots__ = ("name", "component", "options", "initial_params")
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
name: str,
|
|
119
|
+
component: Callable[[], Any],
|
|
120
|
+
*,
|
|
121
|
+
options: Union[ScreenOptions, Callable[[Any], ScreenOptions], None] = None,
|
|
122
|
+
initial_params: Optional[Mapping[str, Any]] = None,
|
|
123
|
+
**option_kwargs: Unpack[ScreenOptions],
|
|
124
|
+
) -> None:
|
|
125
|
+
if not name or not isinstance(name, str):
|
|
126
|
+
raise TypeError("Screen name must be a non-empty string")
|
|
127
|
+
if not callable(component):
|
|
128
|
+
raise TypeError(f"Screen {name!r}: component must be a @component, got {component!r}")
|
|
129
|
+
self.name = name
|
|
130
|
+
self.component = component
|
|
131
|
+
if callable(options):
|
|
132
|
+
if option_kwargs:
|
|
133
|
+
raise TypeError("Pass either a callable `options` or keyword options, not both")
|
|
134
|
+
self.options: Union[ScreenOptions, Callable[[Any], ScreenOptions]] = options
|
|
135
|
+
else:
|
|
136
|
+
merged: Dict[str, Any] = dict(options or {})
|
|
137
|
+
merged.update(option_kwargs)
|
|
138
|
+
self.options = merged # type: ignore[assignment]
|
|
139
|
+
self.initial_params: Dict[str, Any] = dict(initial_params or {})
|
|
140
|
+
|
|
141
|
+
def resolve_options(self, route: Any) -> Dict[str, Any]:
|
|
142
|
+
"""Return the static options, evaluating a callable ``options`` for ``route``."""
|
|
143
|
+
opts = self.options
|
|
144
|
+
if callable(opts):
|
|
145
|
+
return dict(opts(route) or {})
|
|
146
|
+
return dict(opts)
|
|
147
|
+
|
|
148
|
+
def __repr__(self) -> str:
|
|
149
|
+
return f"Screen({self.name!r})"
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Immutable navigation state and the pure operations on it.
|
|
2
|
+
|
|
3
|
+
A navigator's state is a [`NavigationState`][pythonnative.navigation.NavigationState]:
|
|
4
|
+
an ordered tuple of [`Route`][pythonnative.navigation.Route] entries
|
|
5
|
+
plus the index of the active one. Stack navigators treat the tuple as
|
|
6
|
+
a history (the active route is always the last); tab and drawer
|
|
7
|
+
navigators keep one route per screen and move the index.
|
|
8
|
+
|
|
9
|
+
Every operation returns a new state, so navigators can store the state
|
|
10
|
+
in ``use_state`` and diff it like any other value. States serialize to
|
|
11
|
+
plain dicts (``to_dict`` / ``from_dict``) so a native host can hand a
|
|
12
|
+
pushed screen the full history it belongs to.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import itertools
|
|
18
|
+
from typing import Any, Dict, Iterable, Iterator, Mapping, Optional, Sequence, Tuple, cast
|
|
19
|
+
|
|
20
|
+
__all__ = ["NavigationState", "Route", "RouteParams"]
|
|
21
|
+
|
|
22
|
+
type RouteParams = Mapping[str, Any]
|
|
23
|
+
"""Bound for the ``P`` type parameter of [`Route`][pythonnative.navigation.Route].
|
|
24
|
+
|
|
25
|
+
Declare a screen's params as a ``TypedDict`` and read them with
|
|
26
|
+
``use_route(MyParams)`` for a fully typed ``route.params``.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_route_keys = itertools.count(1)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _new_key(name: str) -> str:
|
|
33
|
+
return f"{name}-{next(_route_keys)}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Route[P: RouteParams = Dict[str, Any]]:
|
|
37
|
+
"""One entry in a navigator's state.
|
|
38
|
+
|
|
39
|
+
``Route`` is generic in its params type. Bare ``Route`` is
|
|
40
|
+
``Route[dict[str, Any]]``; pass a ``TypedDict`` to
|
|
41
|
+
[`use_route`][pythonnative.use_route] to get ``Route[MyParams]``
|
|
42
|
+
with a typed ``params`` attribute:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
class DetailParams(TypedDict):
|
|
46
|
+
id: int
|
|
47
|
+
|
|
48
|
+
route = pn.use_route(DetailParams)
|
|
49
|
+
route.params["id"] # int
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
name: The screen name this route renders.
|
|
54
|
+
params: Parameters passed to the screen (read with
|
|
55
|
+
[`use_route`][pythonnative.use_route]). Always a plain
|
|
56
|
+
``dict`` at runtime.
|
|
57
|
+
key: Stable identity for this particular visit to the screen,
|
|
58
|
+
unique per process. Two pushes of the same screen have
|
|
59
|
+
different keys, so their component state never mixes.
|
|
60
|
+
state: Seed state for a navigator rendered by this screen
|
|
61
|
+
(set by ``navigate("Tabs", screen="Profile")`` and by deep
|
|
62
|
+
links). ``None`` for ordinary screens.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
__slots__ = ("name", "params", "key", "state")
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
name: str,
|
|
70
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
71
|
+
key: Optional[str] = None,
|
|
72
|
+
state: Optional["NavigationState"] = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
self.name = name
|
|
75
|
+
self.params: P = cast(P, dict(params or {}))
|
|
76
|
+
self.key = key or _new_key(name)
|
|
77
|
+
self.state = state
|
|
78
|
+
|
|
79
|
+
def with_params(self, params: Mapping[str, Any], *, merge: bool = True) -> "Route[P]":
|
|
80
|
+
"""Return a copy carrying ``params`` (merged over the current ones by default)."""
|
|
81
|
+
merged = {**self.params, **params} if merge else dict(params)
|
|
82
|
+
return Route(self.name, merged, key=self.key, state=self.state)
|
|
83
|
+
|
|
84
|
+
def with_state(self, state: Optional["NavigationState"]) -> "Route[P]":
|
|
85
|
+
"""Return a copy carrying a nested navigator seed ``state``."""
|
|
86
|
+
return Route(self.name, self.params, key=self.key, state=state)
|
|
87
|
+
|
|
88
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
89
|
+
"""Serialize to a plain dict (``name``, ``params``, ``key``, and ``state`` when nested)."""
|
|
90
|
+
out: Dict[str, Any] = {"name": self.name, "params": dict(self.params), "key": self.key}
|
|
91
|
+
if self.state is not None:
|
|
92
|
+
out["state"] = self.state.to_dict()
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_dict(cls, data: Mapping[str, Any]) -> "Route[Dict[str, Any]]":
|
|
97
|
+
"""Rebuild a route from ``to_dict`` output, keeping its ``key`` so component state carries over."""
|
|
98
|
+
params = data.get("params")
|
|
99
|
+
nested = data.get("state")
|
|
100
|
+
return Route(
|
|
101
|
+
str(data["name"]),
|
|
102
|
+
params if isinstance(params, Mapping) else {},
|
|
103
|
+
key=data.get("key"),
|
|
104
|
+
state=NavigationState.from_dict(nested) if isinstance(nested, Mapping) and nested.get("routes") else None,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def __eq__(self, other: object) -> bool:
|
|
108
|
+
if not isinstance(other, Route):
|
|
109
|
+
return NotImplemented
|
|
110
|
+
return (
|
|
111
|
+
self.name == other.name
|
|
112
|
+
and self.params == other.params
|
|
113
|
+
and self.key == other.key
|
|
114
|
+
and self.state == other.state
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def __hash__(self) -> int:
|
|
118
|
+
return hash(self.key)
|
|
119
|
+
|
|
120
|
+
def __repr__(self) -> str:
|
|
121
|
+
return f"Route({self.name!r}, params={self.params!r}, key={self.key!r})"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class NavigationState:
|
|
125
|
+
"""Immutable ordered routes plus the active index.
|
|
126
|
+
|
|
127
|
+
Attributes:
|
|
128
|
+
routes: The routes, oldest first.
|
|
129
|
+
index: Position of the active route in ``routes``.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
__slots__ = ("routes", "index")
|
|
133
|
+
|
|
134
|
+
def __init__(self, routes: Iterable[Route], index: Optional[int] = None) -> None:
|
|
135
|
+
self.routes: Tuple[Route, ...] = tuple(routes)
|
|
136
|
+
if not self.routes:
|
|
137
|
+
raise ValueError("NavigationState needs at least one route")
|
|
138
|
+
self.index = len(self.routes) - 1 if index is None else index
|
|
139
|
+
if not 0 <= self.index < len(self.routes):
|
|
140
|
+
raise IndexError(f"index {self.index} out of range for {len(self.routes)} route(s)")
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
# Introspection
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def current(self) -> Route:
|
|
148
|
+
"""The active route."""
|
|
149
|
+
return self.routes[self.index]
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def can_go_back(self) -> bool:
|
|
153
|
+
"""Whether a route precedes the active one."""
|
|
154
|
+
return self.index > 0
|
|
155
|
+
|
|
156
|
+
def find(self, name: str) -> Optional[int]:
|
|
157
|
+
"""Index of the most recent route named ``name``, or ``None``."""
|
|
158
|
+
for i in range(len(self.routes) - 1, -1, -1):
|
|
159
|
+
if self.routes[i].name == name:
|
|
160
|
+
return i
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def __len__(self) -> int:
|
|
164
|
+
return len(self.routes)
|
|
165
|
+
|
|
166
|
+
def __iter__(self) -> Iterator[Route]:
|
|
167
|
+
return iter(self.routes)
|
|
168
|
+
|
|
169
|
+
def __eq__(self, other: object) -> bool:
|
|
170
|
+
if not isinstance(other, NavigationState):
|
|
171
|
+
return NotImplemented
|
|
172
|
+
return self.routes == other.routes and self.index == other.index
|
|
173
|
+
|
|
174
|
+
def __repr__(self) -> str:
|
|
175
|
+
names = [r.name for r in self.routes]
|
|
176
|
+
return f"NavigationState({names!r}, index={self.index})"
|
|
177
|
+
|
|
178
|
+
# ------------------------------------------------------------------
|
|
179
|
+
# Stack operations (history semantics)
|
|
180
|
+
# ------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
def push(
|
|
183
|
+
self, name: str, params: Optional[Mapping[str, Any]] = None, state: Optional["NavigationState"] = None
|
|
184
|
+
) -> "NavigationState":
|
|
185
|
+
"""Append a new route and make it active (drops any forward entries)."""
|
|
186
|
+
kept = self.routes[: self.index + 1]
|
|
187
|
+
return NavigationState(kept + (Route(name, params, state=state),))
|
|
188
|
+
|
|
189
|
+
def pop(self, count: int = 1) -> "NavigationState":
|
|
190
|
+
"""Remove up to ``count`` routes from the end (never below one)."""
|
|
191
|
+
keep = max(1, len(self.routes) - max(0, count))
|
|
192
|
+
return NavigationState(self.routes[:keep])
|
|
193
|
+
|
|
194
|
+
def pop_to_top(self) -> "NavigationState":
|
|
195
|
+
"""Keep only the first route."""
|
|
196
|
+
return NavigationState(self.routes[:1])
|
|
197
|
+
|
|
198
|
+
def pop_to(
|
|
199
|
+
self, name: str, params: Optional[Mapping[str, Any]] = None, state: Optional["NavigationState"] = None
|
|
200
|
+
) -> "NavigationState":
|
|
201
|
+
"""Pop back to the most recent route named ``name``, merging ``params`` into it."""
|
|
202
|
+
idx = self.find(name)
|
|
203
|
+
if idx is None:
|
|
204
|
+
raise KeyError(name)
|
|
205
|
+
routes = list(self.routes[: idx + 1])
|
|
206
|
+
if params:
|
|
207
|
+
routes[-1] = routes[-1].with_params(params)
|
|
208
|
+
if state is not None:
|
|
209
|
+
routes[-1] = routes[-1].with_state(state)
|
|
210
|
+
return NavigationState(routes)
|
|
211
|
+
|
|
212
|
+
def replace(
|
|
213
|
+
self, name: str, params: Optional[Mapping[str, Any]] = None, state: Optional["NavigationState"] = None
|
|
214
|
+
) -> "NavigationState":
|
|
215
|
+
"""Swap the active route for a fresh one (new key, so state resets)."""
|
|
216
|
+
routes = list(self.routes)
|
|
217
|
+
routes[self.index] = Route(name, params, state=state)
|
|
218
|
+
return NavigationState(routes, self.index)
|
|
219
|
+
|
|
220
|
+
def navigate(
|
|
221
|
+
self, name: str, params: Optional[Mapping[str, Any]] = None, state: Optional["NavigationState"] = None
|
|
222
|
+
) -> "NavigationState":
|
|
223
|
+
"""Go to ``name``: pop back to it if it's in the history, else push it."""
|
|
224
|
+
if self.find(name) is not None:
|
|
225
|
+
return self.pop_to(name, params, state)
|
|
226
|
+
return self.push(name, params, state)
|
|
227
|
+
|
|
228
|
+
# ------------------------------------------------------------------
|
|
229
|
+
# Tab / drawer operations (one route per screen)
|
|
230
|
+
# ------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
def jump_to(
|
|
233
|
+
self, name: str, params: Optional[Mapping[str, Any]] = None, state: Optional["NavigationState"] = None
|
|
234
|
+
) -> "NavigationState":
|
|
235
|
+
"""Activate the route named ``name`` in place, merging ``params``."""
|
|
236
|
+
idx = self.find(name)
|
|
237
|
+
if idx is None:
|
|
238
|
+
raise KeyError(name)
|
|
239
|
+
routes = list(self.routes)
|
|
240
|
+
if params:
|
|
241
|
+
routes[idx] = routes[idx].with_params(params)
|
|
242
|
+
if state is not None:
|
|
243
|
+
routes[idx] = routes[idx].with_state(state)
|
|
244
|
+
return NavigationState(routes, idx)
|
|
245
|
+
|
|
246
|
+
# ------------------------------------------------------------------
|
|
247
|
+
# Shared
|
|
248
|
+
# ------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def set_params(self, params: Mapping[str, Any]) -> "NavigationState":
|
|
251
|
+
"""Merge ``params`` into the active route."""
|
|
252
|
+
routes = list(self.routes)
|
|
253
|
+
routes[self.index] = routes[self.index].with_params(params)
|
|
254
|
+
return NavigationState(routes, self.index)
|
|
255
|
+
|
|
256
|
+
def reset(self, routes: Sequence[Route], index: Optional[int] = None) -> "NavigationState":
|
|
257
|
+
"""Return a state holding exactly ``routes`` with ``index`` active (the last route by default)."""
|
|
258
|
+
return NavigationState(routes, index)
|
|
259
|
+
|
|
260
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
261
|
+
"""Serialize to a plain dict with ``routes`` (each via ``Route.to_dict``) and ``index``."""
|
|
262
|
+
return {"routes": [r.to_dict() for r in self.routes], "index": self.index}
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def from_dict(cls, data: Mapping[str, Any]) -> "NavigationState":
|
|
266
|
+
"""Rebuild a state from ``to_dict`` output; a missing ``index`` activates the last route."""
|
|
267
|
+
routes = [Route.from_dict(r) for r in data.get("routes", [])]
|
|
268
|
+
index = data.get("index")
|
|
269
|
+
return cls(routes, int(index) if isinstance(index, int) else None)
|
pythonnative/net.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Async HTTP client (``pn.fetch``).
|
|
2
|
+
|
|
3
|
+
A small, dependency-free coroutine wrapper around
|
|
4
|
+
:mod:`urllib.request`. Operates on bytes internally and exposes a
|
|
5
|
+
:class:`Response` with `text()`, `json()`, and `bytes` accessors.
|
|
6
|
+
|
|
7
|
+
The implementation is deliberately minimal; it covers the
|
|
8
|
+
"call a JSON API" path that's overwhelmingly the use case for mobile
|
|
9
|
+
apps. For streaming, multipart uploads, or HTTP/2, integrate
|
|
10
|
+
``httpx`` / ``aiohttp`` directly; this module won't try to compete.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
```python
|
|
14
|
+
import pythonnative as pn
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def load_user(user_id):
|
|
18
|
+
resp = await pn.fetch(
|
|
19
|
+
f"https://api.example.com/users/{user_id}",
|
|
20
|
+
headers={"Accept": "application/json"},
|
|
21
|
+
)
|
|
22
|
+
resp.raise_for_status()
|
|
23
|
+
return resp.json()
|
|
24
|
+
```
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import asyncio
|
|
30
|
+
import json
|
|
31
|
+
import ssl
|
|
32
|
+
import urllib.error
|
|
33
|
+
import urllib.parse
|
|
34
|
+
import urllib.request
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from typing import Any, Dict, Mapping, Optional, Union
|
|
37
|
+
|
|
38
|
+
# A package-level SSL context lets callers (or tests) override TLS
|
|
39
|
+
# verification globally without monkeypatching the stdlib. Defaults
|
|
40
|
+
# to the system trust store.
|
|
41
|
+
_default_ssl_context: Optional[ssl.SSLContext] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def set_default_ssl_context(context: Optional[ssl.SSLContext]) -> None:
|
|
45
|
+
"""Override the SSL context used by [`fetch`][pythonnative.fetch].
|
|
46
|
+
|
|
47
|
+
``None`` (the default) means ``urllib`` builds a context from
|
|
48
|
+
the system trust store. Tests can pass an unverified context.
|
|
49
|
+
"""
|
|
50
|
+
global _default_ssl_context
|
|
51
|
+
_default_ssl_context = context
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ======================================================================
|
|
55
|
+
# Response object
|
|
56
|
+
# ======================================================================
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Response:
|
|
61
|
+
"""The result of a [`fetch`][pythonnative.fetch] call.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
status: HTTP status code (e.g. ``200``).
|
|
65
|
+
url: Final URL after any redirects.
|
|
66
|
+
headers: Response headers, case-insensitive.
|
|
67
|
+
content: Raw response body.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
status: int
|
|
71
|
+
url: str
|
|
72
|
+
headers: Mapping[str, str] = field(default_factory=dict)
|
|
73
|
+
content: bytes = b""
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def ok(self) -> bool:
|
|
77
|
+
"""``True`` if the status is in the 2xx range."""
|
|
78
|
+
return 200 <= self.status < 300
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
cls = type(self).__name__
|
|
82
|
+
return f"{cls}(status={self.status}, url={self.url!r}, ok={self.ok}, content={len(self.content)} bytes)"
|
|
83
|
+
|
|
84
|
+
def text(self, encoding: Optional[str] = None) -> str:
|
|
85
|
+
"""Decode ``content`` to ``str``.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
encoding: Optional override; defaults to the
|
|
89
|
+
``charset`` parameter of ``Content-Type`` (or UTF-8).
|
|
90
|
+
"""
|
|
91
|
+
enc = encoding or _charset_from_headers(self.headers) or "utf-8"
|
|
92
|
+
try:
|
|
93
|
+
return self.content.decode(enc)
|
|
94
|
+
except (UnicodeDecodeError, LookupError):
|
|
95
|
+
return self.content.decode("utf-8", errors="replace")
|
|
96
|
+
|
|
97
|
+
def json(self) -> Any:
|
|
98
|
+
"""Parse the response body as JSON.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
json.JSONDecodeError: If the body isn't valid JSON.
|
|
102
|
+
"""
|
|
103
|
+
return json.loads(self.text())
|
|
104
|
+
|
|
105
|
+
def raise_for_status(self) -> None:
|
|
106
|
+
"""Raise [`HTTPError`][pythonnative.net.HTTPError] for non-2xx responses."""
|
|
107
|
+
if not self.ok:
|
|
108
|
+
raise HTTPError(self.status, self.url, self.text())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class HTTPError(Exception):
|
|
112
|
+
"""Raised by [`Response.raise_for_status`][pythonnative.net.Response.raise_for_status]."""
|
|
113
|
+
|
|
114
|
+
def __init__(self, status: int, url: str, body: str) -> None:
|
|
115
|
+
super().__init__(f"HTTP {status} from {url}: {body[:200]}")
|
|
116
|
+
self.status = status
|
|
117
|
+
self.url = url
|
|
118
|
+
self.body = body
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _charset_from_headers(headers: Mapping[str, str]) -> Optional[str]:
|
|
122
|
+
ctype = headers.get("Content-Type") or headers.get("content-type")
|
|
123
|
+
if not ctype:
|
|
124
|
+
return None
|
|
125
|
+
for part in ctype.split(";"):
|
|
126
|
+
part = part.strip()
|
|
127
|
+
if part.lower().startswith("charset="):
|
|
128
|
+
return part[len("charset=") :].strip().strip('"')
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ======================================================================
|
|
133
|
+
# Public fetch coroutine
|
|
134
|
+
# ======================================================================
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def fetch(
|
|
138
|
+
url: str,
|
|
139
|
+
*,
|
|
140
|
+
method: str = "GET",
|
|
141
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
142
|
+
body: Union[bytes, str, Mapping[str, Any], None] = None,
|
|
143
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
144
|
+
timeout: float = 30.0,
|
|
145
|
+
) -> Response:
|
|
146
|
+
"""Make an HTTP request and return a [`Response`][pythonnative.net.Response].
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
url: Target URL. Relative URLs are not supported.
|
|
150
|
+
method: HTTP method (``"GET"``, ``"POST"``, ``"PUT"`` …).
|
|
151
|
+
headers: Optional request headers.
|
|
152
|
+
body: Request body. ``bytes`` are sent as-is; ``str`` is
|
|
153
|
+
UTF-8 encoded; ``dict`` is JSON-encoded with a
|
|
154
|
+
``Content-Type: application/json`` header added (unless
|
|
155
|
+
already supplied).
|
|
156
|
+
params: Optional mapping of query-string parameters appended
|
|
157
|
+
to ``url`` (urlencoded).
|
|
158
|
+
timeout: Seconds to wait for the response (excluding the
|
|
159
|
+
time spent on DNS / connect).
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A [`Response`][pythonnative.net.Response].
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
TimeoutError: If the request doesn't complete within
|
|
166
|
+
``timeout`` seconds.
|
|
167
|
+
OSError: For network errors (DNS failure, connection refused,
|
|
168
|
+
etc.), re-raised from ``urllib``.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
```python
|
|
172
|
+
resp = await pn.fetch(
|
|
173
|
+
"https://api.example.com/posts",
|
|
174
|
+
method="POST",
|
|
175
|
+
body={"title": "Hello"},
|
|
176
|
+
)
|
|
177
|
+
resp.raise_for_status()
|
|
178
|
+
```
|
|
179
|
+
"""
|
|
180
|
+
request = _build_request(url=url, method=method, headers=headers, body=body, params=params)
|
|
181
|
+
return await asyncio.to_thread(_dispatch_request, request, timeout)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _build_request(
|
|
185
|
+
*,
|
|
186
|
+
url: str,
|
|
187
|
+
method: str,
|
|
188
|
+
headers: Optional[Mapping[str, str]],
|
|
189
|
+
body: Union[bytes, str, Mapping[str, Any], None],
|
|
190
|
+
params: Optional[Mapping[str, Any]],
|
|
191
|
+
) -> urllib.request.Request:
|
|
192
|
+
if params:
|
|
193
|
+
sep = "&" if "?" in url else "?"
|
|
194
|
+
url = url + sep + urllib.parse.urlencode(params, doseq=True)
|
|
195
|
+
|
|
196
|
+
header_dict: Dict[str, str] = dict(headers or {})
|
|
197
|
+
payload: Optional[bytes] = None
|
|
198
|
+
if body is None:
|
|
199
|
+
pass
|
|
200
|
+
elif isinstance(body, (bytes, bytearray)):
|
|
201
|
+
payload = bytes(body)
|
|
202
|
+
elif isinstance(body, str):
|
|
203
|
+
payload = body.encode("utf-8")
|
|
204
|
+
elif isinstance(body, Mapping):
|
|
205
|
+
payload = json.dumps(body, default=str).encode("utf-8")
|
|
206
|
+
header_dict.setdefault("Content-Type", "application/json")
|
|
207
|
+
else:
|
|
208
|
+
raise TypeError(f"Unsupported body type: {type(body)!r}")
|
|
209
|
+
|
|
210
|
+
return urllib.request.Request(
|
|
211
|
+
url=url,
|
|
212
|
+
data=payload,
|
|
213
|
+
method=method.upper(),
|
|
214
|
+
headers=header_dict,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _dispatch_request(request: urllib.request.Request, timeout: float) -> Response:
|
|
219
|
+
context = _default_ssl_context
|
|
220
|
+
try:
|
|
221
|
+
with urllib.request.urlopen(request, timeout=timeout, context=context) as resp:
|
|
222
|
+
content = resp.read()
|
|
223
|
+
return Response(
|
|
224
|
+
status=resp.status,
|
|
225
|
+
url=resp.geturl(),
|
|
226
|
+
headers={k: v for k, v in resp.headers.items()},
|
|
227
|
+
content=content,
|
|
228
|
+
)
|
|
229
|
+
except urllib.error.HTTPError as exc:
|
|
230
|
+
# HTTPError is itself a response object; propagate the body
|
|
231
|
+
# so callers can inspect it before deciding to raise.
|
|
232
|
+
body = exc.read() if hasattr(exc, "read") else b""
|
|
233
|
+
return Response(
|
|
234
|
+
status=exc.code,
|
|
235
|
+
url=getattr(exc, "url", request.full_url),
|
|
236
|
+
headers={k: v for k, v in (exc.headers or {}).items()},
|
|
237
|
+
content=body,
|
|
238
|
+
)
|
|
239
|
+
except urllib.error.URLError as exc:
|
|
240
|
+
# Convert urllib's TimeoutError wrapper into a plain TimeoutError
|
|
241
|
+
# so callers get a recognisable exception type.
|
|
242
|
+
reason = getattr(exc, "reason", exc)
|
|
243
|
+
if isinstance(reason, TimeoutError):
|
|
244
|
+
raise reason
|
|
245
|
+
raise OSError(str(reason)) from exc
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
__all__ = ["fetch", "Response", "HTTPError", "set_default_ssl_context"]
|