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,719 @@
|
|
|
1
|
+
"""Target-aware resolution of ``[requirements].packages``.
|
|
2
|
+
|
|
3
|
+
A PythonNative app runs on an embedded CPython, so its third-party
|
|
4
|
+
packages have to be resolved for the *device*, not for the machine
|
|
5
|
+
running ``pn``. Pure-Python wheels (``py3-none-any``) work everywhere,
|
|
6
|
+
but anything with a compiled extension needs a wheel built for the
|
|
7
|
+
exact platform triple the app will run on:
|
|
8
|
+
|
|
9
|
+
- iOS (PEP 730): ``ios_13_0_arm64_iphoneos`` for devices and
|
|
10
|
+
``ios_13_0_arm64_iphonesimulator`` / ``ios_13_0_x86_64_iphonesimulator``
|
|
11
|
+
for the Simulator, published on PyPI and on BeeWare's index.
|
|
12
|
+
- Android (PEP 738): ``android_24_arm64_v8a`` / ``android_24_x86_64``,
|
|
13
|
+
published on PyPI and on Chaquopy's index.
|
|
14
|
+
|
|
15
|
+
This module models those targets ([`Target`][pythonnative.project.deps.Target]),
|
|
16
|
+
asks pip to resolve the project's requirements against each of them
|
|
17
|
+
without installing anything ([`resolve`][pythonnative.project.deps.resolve]),
|
|
18
|
+
and installs the resolved set into a per-target directory for the
|
|
19
|
+
iOS build ([`install`][pythonnative.project.deps.install]). ``pip``
|
|
20
|
+
does the actual work through its ``--platform`` / ``--python-version``
|
|
21
|
+
/ ``--only-binary=:all:`` cross-resolution mode, so the host
|
|
22
|
+
interpreter's version and platform never leak into the app.
|
|
23
|
+
|
|
24
|
+
On Android the authoritative install is done by Chaquopy inside the
|
|
25
|
+
Gradle build (with the same ``--only-binary`` rule); resolving here is
|
|
26
|
+
a fast preview that catches missing wheels before a multi-minute build.
|
|
27
|
+
|
|
28
|
+
All pip invocations go through the builder's
|
|
29
|
+
[`CommandRunner`][pythonnative.project.builder.CommandRunner] so the
|
|
30
|
+
logic is unit testable with a recording fake.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
from dataclasses import dataclass, field
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
41
|
+
from urllib.parse import urlparse
|
|
42
|
+
|
|
43
|
+
from .config import AppConfig
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from .builder import CommandRunner
|
|
47
|
+
|
|
48
|
+
BEEWARE_INDEX_URL = "https://pypi.anaconda.org/beeware/simple"
|
|
49
|
+
"""BeeWare's wheel index: iOS binary wheels for the CPython versions Python-Apple-support ships."""
|
|
50
|
+
|
|
51
|
+
CHAQUOPY_INDEX_URL = "https://chaquo.com/pypi-13.1/"
|
|
52
|
+
"""Chaquopy's wheel index: Android binary wheels built for its embedded CPython."""
|
|
53
|
+
|
|
54
|
+
MIN_IOS_WHEEL_VERSION = (12, 0)
|
|
55
|
+
"""Oldest iOS version a PEP 730 wheel can be tagged with (CPython's own floor)."""
|
|
56
|
+
|
|
57
|
+
MIN_ANDROID_WHEEL_API = 16
|
|
58
|
+
"""Oldest Android API level a PEP 738 wheel can be tagged with."""
|
|
59
|
+
|
|
60
|
+
IOS_SDKS = ("iphoneos", "iphonesimulator")
|
|
61
|
+
"""The two iOS SDKs a build can target; each gets its own ``app_packages`` slice."""
|
|
62
|
+
|
|
63
|
+
ANDROID_ABI_TAGS: Dict[str, str] = {"arm64-v8a": "arm64_v8a", "x86_64": "x86_64"}
|
|
64
|
+
"""Gradle ABI name -> wheel platform tag component."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ======================================================================
|
|
68
|
+
# Targets
|
|
69
|
+
# ======================================================================
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class Target:
|
|
74
|
+
"""One device platform to resolve packages for.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
platform: ``"ios"`` or ``"android"``.
|
|
78
|
+
python_version: CPython ``major.minor`` the app embeds.
|
|
79
|
+
arch: CPU architecture (``"arm64"`` or ``"x86_64"``).
|
|
80
|
+
sdk: iOS SDK (``"iphoneos"`` / ``"iphonesimulator"``); empty on
|
|
81
|
+
Android.
|
|
82
|
+
os_version: Minimum OS version the app declares: the iOS
|
|
83
|
+
deployment target (``"13.0"``) or the Android API level as a
|
|
84
|
+
string (``"24"``).
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
platform: str
|
|
88
|
+
python_version: str
|
|
89
|
+
arch: str
|
|
90
|
+
sdk: str = ""
|
|
91
|
+
os_version: str = "13.0"
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def label(self) -> str:
|
|
95
|
+
"""Short human-readable name, e.g. ``"iOS Simulator (arm64)"``."""
|
|
96
|
+
if self.platform == "ios":
|
|
97
|
+
kind = "iOS device" if self.sdk == "iphoneos" else "iOS Simulator"
|
|
98
|
+
return f"{kind} ({self.arch}, iOS {self.os_version}+)"
|
|
99
|
+
return f"Android {self.abi} (API {self.os_version}+)"
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def abi(self) -> str:
|
|
103
|
+
"""The Gradle ABI name for an Android target (``"arm64-v8a"``)."""
|
|
104
|
+
for abi, tag in ANDROID_ABI_TAGS.items():
|
|
105
|
+
if tag == self.arch or abi == self.arch:
|
|
106
|
+
return abi
|
|
107
|
+
return self.arch
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def slice_name(self) -> str:
|
|
111
|
+
"""The staged packages folder name for an iOS target (``app_packages.iphoneos``)."""
|
|
112
|
+
return f"app_packages.{self.sdk}"
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def platform_tags(self) -> List[str]:
|
|
116
|
+
"""Every wheel platform tag this target accepts, newest first.
|
|
117
|
+
|
|
118
|
+
A wheel is tagged with the *minimum* OS version it supports, so
|
|
119
|
+
a target on iOS 13.0 also accepts wheels tagged for 12.x.
|
|
120
|
+
Mirrors ``packaging.tags.ios_platforms`` /
|
|
121
|
+
``android_platforms`` so the list stays in step with what pip
|
|
122
|
+
itself would compute on the device.
|
|
123
|
+
"""
|
|
124
|
+
if self.platform == "ios":
|
|
125
|
+
return list(_ios_platform_tags(self.os_version, f"{self.arch}_{self.sdk}"))
|
|
126
|
+
return list(_android_platform_tags(int(self.os_version), ANDROID_ABI_TAGS.get(self.arch, self.arch)))
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def index_urls(self) -> List[str]:
|
|
130
|
+
"""Extra indexes searched after PyPI: the platform's binary wheel repository."""
|
|
131
|
+
return [BEEWARE_INDEX_URL] if self.platform == "ios" else [CHAQUOPY_INDEX_URL]
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def abi_tag(self) -> str:
|
|
135
|
+
"""The CPython ABI tag (``cp313``)."""
|
|
136
|
+
return "cp" + self.python_version.replace(".", "")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _ios_platform_tags(version: str, multiarch: str) -> Iterable[str]:
|
|
140
|
+
major, minor = _parse_version(version)
|
|
141
|
+
for maj in range(major, MIN_IOS_WHEEL_VERSION[0] - 1, -1):
|
|
142
|
+
top = minor if maj == major else 9
|
|
143
|
+
bottom = MIN_IOS_WHEEL_VERSION[1] if maj == MIN_IOS_WHEEL_VERSION[0] else 0
|
|
144
|
+
for mnr in range(top, bottom - 1, -1):
|
|
145
|
+
yield f"ios_{maj}_{mnr}_{multiarch}"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _android_platform_tags(api_level: int, abi: str) -> Iterable[str]:
|
|
149
|
+
for level in range(api_level, MIN_ANDROID_WHEEL_API - 1, -1):
|
|
150
|
+
yield f"android_{level}_{abi}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _parse_version(version: str) -> Tuple[int, int]:
|
|
154
|
+
parts = version.split(".")
|
|
155
|
+
major = int(parts[0])
|
|
156
|
+
minor = int(parts[1]) if len(parts) > 1 else 0
|
|
157
|
+
return major, minor
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def host_simulator_arch() -> str:
|
|
161
|
+
"""The iOS Simulator architecture matching this Mac (``arm64`` or ``x86_64``)."""
|
|
162
|
+
import platform as platform_module
|
|
163
|
+
|
|
164
|
+
return "arm64" if platform_module.machine() == "arm64" else "x86_64"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def ios_targets(
|
|
168
|
+
config: AppConfig, *, sdks: Sequence[str] = IOS_SDKS, simulator_arch: Optional[str] = None
|
|
169
|
+
) -> List[Target]:
|
|
170
|
+
"""Build the iOS targets for ``config``.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
config: The validated app configuration.
|
|
174
|
+
sdks: Which SDKs to include (default both).
|
|
175
|
+
simulator_arch: Architecture for the Simulator target; defaults
|
|
176
|
+
to the host Mac's, which is also what the builder pins
|
|
177
|
+
``ARCHS`` to.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
One [`Target`][pythonnative.project.deps.Target] per SDK.
|
|
181
|
+
"""
|
|
182
|
+
targets: List[Target] = []
|
|
183
|
+
for sdk in sdks:
|
|
184
|
+
if sdk not in IOS_SDKS:
|
|
185
|
+
raise ValueError(f"Unknown iOS SDK {sdk!r} (expected one of {IOS_SDKS}).")
|
|
186
|
+
arch = "arm64" if sdk == "iphoneos" else (simulator_arch or host_simulator_arch())
|
|
187
|
+
targets.append(
|
|
188
|
+
Target(
|
|
189
|
+
platform="ios",
|
|
190
|
+
python_version=config.python_version,
|
|
191
|
+
arch=arch,
|
|
192
|
+
sdk=sdk,
|
|
193
|
+
os_version=config.ios.deployment_target,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
return targets
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def android_targets(config: AppConfig) -> List[Target]:
|
|
200
|
+
"""Build one Android target per ``[android].abi_filters`` entry."""
|
|
201
|
+
return [
|
|
202
|
+
Target(
|
|
203
|
+
platform="android",
|
|
204
|
+
python_version=config.python_version,
|
|
205
|
+
arch=ANDROID_ABI_TAGS.get(abi, abi),
|
|
206
|
+
os_version=str(config.android.min_sdk),
|
|
207
|
+
)
|
|
208
|
+
for abi in config.android.abi_filters
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def targets_for(
|
|
213
|
+
config: AppConfig, platform: Optional[str] = None, *, all_simulator_archs: bool = False
|
|
214
|
+
) -> List[Target]:
|
|
215
|
+
"""Targets for a platform, optionally including both Mac architectures for portable locks."""
|
|
216
|
+
targets: List[Target] = []
|
|
217
|
+
if platform in (None, "ios"):
|
|
218
|
+
if all_simulator_archs:
|
|
219
|
+
targets.extend(ios_targets(config, simulator_arch="arm64"))
|
|
220
|
+
targets.extend(ios_targets(config, sdks=("iphonesimulator",), simulator_arch="x86_64"))
|
|
221
|
+
else:
|
|
222
|
+
targets.extend(ios_targets(config))
|
|
223
|
+
if platform in (None, "android"):
|
|
224
|
+
targets.extend(android_targets(config))
|
|
225
|
+
return targets
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ======================================================================
|
|
229
|
+
# pip invocations
|
|
230
|
+
# ======================================================================
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def pip_base_args(config: AppConfig, target: Target, *, python: Optional[str] = None) -> List[str]:
|
|
234
|
+
"""The pip arguments that select ``target`` for cross-platform resolution.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
config: The app configuration (for ``extra_index_urls``).
|
|
238
|
+
target: The device target.
|
|
239
|
+
python: Interpreter to run pip with; defaults to the current one.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
``[python, "-m", "pip", "install", <selection flags...>]`` with
|
|
243
|
+
no requirements appended.
|
|
244
|
+
"""
|
|
245
|
+
args = [
|
|
246
|
+
python or sys.executable,
|
|
247
|
+
"-m",
|
|
248
|
+
"pip",
|
|
249
|
+
"install",
|
|
250
|
+
"--disable-pip-version-check",
|
|
251
|
+
# Cross-resolution: pip refuses to build from source for a foreign
|
|
252
|
+
# platform, which is also exactly what an app store build needs.
|
|
253
|
+
"--only-binary=:all:",
|
|
254
|
+
"--implementation",
|
|
255
|
+
"cp",
|
|
256
|
+
"--python-version",
|
|
257
|
+
target.python_version,
|
|
258
|
+
"--abi",
|
|
259
|
+
target.abi_tag,
|
|
260
|
+
]
|
|
261
|
+
for tag in target.platform_tags:
|
|
262
|
+
args.extend(["--platform", tag])
|
|
263
|
+
for url in [*target.index_urls, *config.extra_index_urls]:
|
|
264
|
+
args.extend(["--extra-index-url", url])
|
|
265
|
+
return args
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def report_args(config: AppConfig, target: Target, *, python: Optional[str] = None) -> List[str]:
|
|
269
|
+
"""Pip arguments that resolve ``config.requirements`` for ``target`` without installing.
|
|
270
|
+
|
|
271
|
+
The JSON report (``--report -``) lists every wheel pip *would*
|
|
272
|
+
install, which is what [`resolve`][pythonnative.project.deps.resolve]
|
|
273
|
+
parses.
|
|
274
|
+
"""
|
|
275
|
+
return [
|
|
276
|
+
*pip_base_args(config, target, python=python),
|
|
277
|
+
"--dry-run",
|
|
278
|
+
"--ignore-installed",
|
|
279
|
+
"--quiet",
|
|
280
|
+
"--report",
|
|
281
|
+
"-",
|
|
282
|
+
*config.requirements,
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def install_args(config: AppConfig, target: Target, dest: Path, *, python: Optional[str] = None) -> List[str]:
|
|
287
|
+
"""Pip arguments that install ``config.requirements`` for ``target`` into ``dest``."""
|
|
288
|
+
return [
|
|
289
|
+
*pip_base_args(config, target, python=python),
|
|
290
|
+
"--target",
|
|
291
|
+
str(dest),
|
|
292
|
+
"--upgrade",
|
|
293
|
+
*config.requirements,
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ======================================================================
|
|
298
|
+
# Results
|
|
299
|
+
# ======================================================================
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@dataclass
|
|
303
|
+
class ResolvedPackage:
|
|
304
|
+
"""One wheel pip selected for a target.
|
|
305
|
+
|
|
306
|
+
Attributes:
|
|
307
|
+
name: Distribution name as reported by pip.
|
|
308
|
+
version: Selected version.
|
|
309
|
+
filename: The wheel filename (its tags say what it is built for).
|
|
310
|
+
url: Where the wheel comes from.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
name: str
|
|
314
|
+
version: str
|
|
315
|
+
filename: str
|
|
316
|
+
url: str = ""
|
|
317
|
+
sha256: str = ""
|
|
318
|
+
latest: str = ""
|
|
319
|
+
"""The version an unconstrained desktop resolution picks, when it is newer than ``version``.
|
|
320
|
+
|
|
321
|
+
Set by [`mark_downgrades`][pythonnative.project.deps.mark_downgrades].
|
|
322
|
+
A non-empty value means pip walked back to an older release because
|
|
323
|
+
the newer ones have no wheel for this target.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def downgraded(self) -> bool:
|
|
328
|
+
"""Whether this target got an older release than the desktop would."""
|
|
329
|
+
return bool(self.latest)
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def is_pure(self) -> bool:
|
|
333
|
+
"""Whether this is a platform-independent (``none-any``) wheel."""
|
|
334
|
+
return self.filename.endswith("-none-any.whl")
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def platform_tag(self) -> str:
|
|
338
|
+
"""The wheel's platform tag (``"any"`` for pure-Python wheels)."""
|
|
339
|
+
stem = self.filename[:-4] if self.filename.endswith(".whl") else self.filename
|
|
340
|
+
return stem.rsplit("-", 1)[-1]
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def index_host(self) -> str:
|
|
344
|
+
"""Host name of the index the wheel is served from (``"files.pythonhosted.org"`` for PyPI)."""
|
|
345
|
+
return urlparse(self.url).netloc if self.url else ""
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def source_label(self) -> str:
|
|
349
|
+
"""Friendly source name: ``PyPI``, ``BeeWare``, ``Chaquopy``, or the index host."""
|
|
350
|
+
host = self.index_host
|
|
351
|
+
if host.endswith("pythonhosted.org") or host.endswith("pypi.org"):
|
|
352
|
+
return "PyPI"
|
|
353
|
+
if host == "pypi.anaconda.org" and "/beeware/" in self.url:
|
|
354
|
+
return "BeeWare"
|
|
355
|
+
if host == "chaquo.com":
|
|
356
|
+
return "Chaquopy"
|
|
357
|
+
return host or "local"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@dataclass
|
|
361
|
+
class Resolution:
|
|
362
|
+
"""The outcome of resolving the requirements for one target.
|
|
363
|
+
|
|
364
|
+
Attributes:
|
|
365
|
+
target: The target that was resolved.
|
|
366
|
+
packages: Wheels pip selected (empty when ``error`` is set or
|
|
367
|
+
there are no requirements).
|
|
368
|
+
error: pip's failure explanation when resolution failed, else
|
|
369
|
+
``None``.
|
|
370
|
+
missing: Requirement names pip could not satisfy, parsed from
|
|
371
|
+
``error`` (best effort).
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
target: Target
|
|
375
|
+
packages: List[ResolvedPackage] = field(default_factory=list)
|
|
376
|
+
error: Optional[str] = None
|
|
377
|
+
missing: List[str] = field(default_factory=list)
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def ok(self) -> bool:
|
|
381
|
+
"""Whether every requirement resolved to a wheel."""
|
|
382
|
+
return self.error is None
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def binary_packages(self) -> List[ResolvedPackage]:
|
|
386
|
+
"""The platform-specific wheels in ``packages``."""
|
|
387
|
+
return [pkg for pkg in self.packages if not pkg.is_pure]
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def downgraded_packages(self) -> List[ResolvedPackage]:
|
|
391
|
+
"""Packages that resolved to an older release than the desktop would get."""
|
|
392
|
+
return [pkg for pkg in self.packages if pkg.downgraded]
|
|
393
|
+
|
|
394
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
395
|
+
"""Serialize for ``pn deps --json`` and the compatibility matrix job."""
|
|
396
|
+
return {
|
|
397
|
+
"platform": self.target.platform,
|
|
398
|
+
"label": self.target.label,
|
|
399
|
+
"python_version": self.target.python_version,
|
|
400
|
+
"arch": self.target.arch,
|
|
401
|
+
"sdk": self.target.sdk,
|
|
402
|
+
"ok": self.ok,
|
|
403
|
+
"error": self.error,
|
|
404
|
+
"missing": list(self.missing),
|
|
405
|
+
"packages": [
|
|
406
|
+
{
|
|
407
|
+
"name": pkg.name,
|
|
408
|
+
"version": pkg.version,
|
|
409
|
+
"filename": pkg.filename,
|
|
410
|
+
"pure": pkg.is_pure,
|
|
411
|
+
"platform_tag": pkg.platform_tag,
|
|
412
|
+
"source": pkg.source_label,
|
|
413
|
+
"url": pkg.url,
|
|
414
|
+
"latest": pkg.latest or None,
|
|
415
|
+
}
|
|
416
|
+
for pkg in self.packages
|
|
417
|
+
],
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class DependencyError(Exception):
|
|
422
|
+
"""Raised when requirements can't be resolved or installed for a target.
|
|
423
|
+
|
|
424
|
+
The message is user-facing: it names the target, the unsatisfiable
|
|
425
|
+
requirement(s), and what to do about it.
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# ======================================================================
|
|
430
|
+
# Resolving
|
|
431
|
+
# ======================================================================
|
|
432
|
+
|
|
433
|
+
_NO_MATCH_RE = re.compile(r"No matching distribution found for (?P<req>[^\s]+)")
|
|
434
|
+
_NO_VERSION_RE = re.compile(r"Could not find a version that satisfies the requirement (?P<req>[^\s]+)")
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def parse_report(report_json: str, target: Target) -> Resolution:
|
|
438
|
+
"""Turn a pip ``--report`` JSON document into a [`Resolution`][pythonnative.project.deps.Resolution]."""
|
|
439
|
+
data = json.loads(report_json or "{}")
|
|
440
|
+
packages: List[ResolvedPackage] = []
|
|
441
|
+
for item in data.get("install", []):
|
|
442
|
+
metadata = item.get("metadata", {})
|
|
443
|
+
download = item.get("download_info", {}) or {}
|
|
444
|
+
url = str(download.get("url", ""))
|
|
445
|
+
filename = url.rsplit("/", 1)[-1] if url else ""
|
|
446
|
+
packages.append(
|
|
447
|
+
ResolvedPackage(
|
|
448
|
+
name=str(metadata.get("name", "?")),
|
|
449
|
+
version=str(metadata.get("version", "?")),
|
|
450
|
+
filename=filename,
|
|
451
|
+
url=url,
|
|
452
|
+
sha256=str(download.get("archive_info", {}).get("hashes", {}).get("sha256", "")),
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
packages.sort(key=lambda pkg: pkg.name.lower())
|
|
456
|
+
return Resolution(target=target, packages=packages)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def parse_failure(stderr: str) -> Tuple[str, List[str]]:
|
|
460
|
+
"""Extract a one-paragraph explanation and the unsatisfied requirement names from pip's stderr."""
|
|
461
|
+
text = (stderr or "").strip()
|
|
462
|
+
missing: List[str] = []
|
|
463
|
+
for match in _NO_MATCH_RE.finditer(text):
|
|
464
|
+
req = match.group("req").strip(".,")
|
|
465
|
+
if req not in missing:
|
|
466
|
+
missing.append(req)
|
|
467
|
+
if not missing:
|
|
468
|
+
for match in _NO_VERSION_RE.finditer(text):
|
|
469
|
+
req = match.group("req").strip(".,")
|
|
470
|
+
if req not in missing:
|
|
471
|
+
missing.append(req)
|
|
472
|
+
# pip prints the useful sentence(s) as ERROR: lines; everything else is noise.
|
|
473
|
+
lines = [line for line in text.splitlines() if line.startswith("ERROR:")]
|
|
474
|
+
summary = "\n".join(lines) if lines else (text.splitlines()[-1] if text else "pip failed without output")
|
|
475
|
+
return summary, missing
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def resolve(config: AppConfig, target: Target, *, runner: CommandRunner, python: Optional[str] = None) -> Resolution:
|
|
479
|
+
"""Resolve ``config.requirements`` for ``target`` without installing.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
config: The validated app configuration.
|
|
483
|
+
target: The device target.
|
|
484
|
+
runner: A [`CommandRunner`][pythonnative.project.builder.CommandRunner].
|
|
485
|
+
python: Interpreter to run pip with (defaults to the current one).
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
A [`Resolution`][pythonnative.project.deps.Resolution]; check
|
|
489
|
+
``.ok`` rather than expecting an exception, so callers can
|
|
490
|
+
report every target at once.
|
|
491
|
+
"""
|
|
492
|
+
if not config.requirements:
|
|
493
|
+
return Resolution(target=target)
|
|
494
|
+
result = runner.run(report_args(config, target, python=python), capture=True)
|
|
495
|
+
if not result.ok:
|
|
496
|
+
summary, missing = parse_failure(result.stderr)
|
|
497
|
+
return Resolution(target=target, error=summary, missing=missing)
|
|
498
|
+
try:
|
|
499
|
+
return parse_report(result.stdout, target)
|
|
500
|
+
except (ValueError, TypeError) as exc:
|
|
501
|
+
return Resolution(target=target, error=f"Could not parse pip's report: {exc}")
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def resolve_all(
|
|
505
|
+
config: AppConfig,
|
|
506
|
+
targets: Sequence[Target],
|
|
507
|
+
*,
|
|
508
|
+
runner: CommandRunner,
|
|
509
|
+
python: Optional[str] = None,
|
|
510
|
+
detect_downgrades: bool = True,
|
|
511
|
+
) -> List[Resolution]:
|
|
512
|
+
"""[`resolve`][pythonnative.project.deps.resolve] every target in order.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
config: The validated app configuration.
|
|
516
|
+
targets: Targets to resolve for.
|
|
517
|
+
runner: A [`CommandRunner`][pythonnative.project.builder.CommandRunner].
|
|
518
|
+
python: Interpreter to run pip with (defaults to the current one).
|
|
519
|
+
detect_downgrades: Also resolve once without any platform
|
|
520
|
+
constraint and mark packages that got an older release on a
|
|
521
|
+
target than that unconstrained resolution picked (see
|
|
522
|
+
[`mark_downgrades`][pythonnative.project.deps.mark_downgrades]).
|
|
523
|
+
One extra pip call; skipped when there are no requirements.
|
|
524
|
+
"""
|
|
525
|
+
resolutions = [resolve(config, target, runner=runner, python=python) for target in targets]
|
|
526
|
+
if detect_downgrades and config.requirements and any(res.ok and res.packages for res in resolutions):
|
|
527
|
+
reference = resolve_reference(config, runner=runner, python=python)
|
|
528
|
+
if reference is not None:
|
|
529
|
+
mark_downgrades(resolutions, reference)
|
|
530
|
+
return resolutions
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def reference_args(config: AppConfig, *, python: Optional[str] = None) -> List[str]:
|
|
534
|
+
"""Pip arguments for an unconstrained dry-run resolution of ``config.requirements``.
|
|
535
|
+
|
|
536
|
+
No platform or ABI flags and source distributions allowed, so pip
|
|
537
|
+
picks the newest release it would give a desktop. Only the versions
|
|
538
|
+
matter; nothing is downloaded beyond metadata.
|
|
539
|
+
"""
|
|
540
|
+
return [
|
|
541
|
+
python or sys.executable,
|
|
542
|
+
"-m",
|
|
543
|
+
"pip",
|
|
544
|
+
"install",
|
|
545
|
+
"--disable-pip-version-check",
|
|
546
|
+
"--dry-run",
|
|
547
|
+
"--ignore-installed",
|
|
548
|
+
"--quiet",
|
|
549
|
+
"--report",
|
|
550
|
+
"-",
|
|
551
|
+
*[arg for url in config.extra_index_urls for arg in ("--extra-index-url", url)],
|
|
552
|
+
*config.requirements,
|
|
553
|
+
]
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def resolve_reference(
|
|
557
|
+
config: AppConfig, *, runner: CommandRunner, python: Optional[str] = None
|
|
558
|
+
) -> Optional[Dict[str, str]]:
|
|
559
|
+
"""Resolve the requirements with no target constraint; return ``{name: version}``.
|
|
560
|
+
|
|
561
|
+
Returns ``None`` when pip can't resolve them at all (the per-target
|
|
562
|
+
errors already say why) or the report can't be parsed.
|
|
563
|
+
"""
|
|
564
|
+
if not config.requirements:
|
|
565
|
+
return {}
|
|
566
|
+
result = runner.run(reference_args(config, python=python), capture=True)
|
|
567
|
+
if not result.ok:
|
|
568
|
+
return None
|
|
569
|
+
try:
|
|
570
|
+
data = json.loads(result.stdout or "{}")
|
|
571
|
+
except ValueError:
|
|
572
|
+
return None
|
|
573
|
+
versions: Dict[str, str] = {}
|
|
574
|
+
for item in data.get("install", []):
|
|
575
|
+
metadata = item.get("metadata", {})
|
|
576
|
+
versions[_canonical(str(metadata.get("name", "")))] = str(metadata.get("version", ""))
|
|
577
|
+
return versions
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def mark_downgrades(resolutions: Sequence[Resolution], reference: Dict[str, str]) -> None:
|
|
581
|
+
"""Set ``latest`` on every resolved package that is older than in ``reference``.
|
|
582
|
+
|
|
583
|
+
pip's cross-resolution silently walks back through a package's
|
|
584
|
+
release history until it finds a version with a usable wheel for
|
|
585
|
+
the target. That is the right call for a build (it works), but it
|
|
586
|
+
is a surprise for the developer who gets a two-year-old API on the
|
|
587
|
+
device and the current one in their tests. Marking the gap lets
|
|
588
|
+
``pn deps`` say so.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
resolutions: Per-target resolutions to annotate in place.
|
|
592
|
+
reference: ``{canonical name: version}`` from an unconstrained
|
|
593
|
+
resolution (see
|
|
594
|
+
[`resolve_reference`][pythonnative.project.deps.resolve_reference]).
|
|
595
|
+
"""
|
|
596
|
+
for res in resolutions:
|
|
597
|
+
for pkg in res.packages:
|
|
598
|
+
latest = reference.get(_canonical(pkg.name))
|
|
599
|
+
if latest and _version_key(latest) > _version_key(pkg.version):
|
|
600
|
+
pkg.latest = latest
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _canonical(name: str) -> str:
|
|
604
|
+
return re.sub(r"[-_.]+", "-", name).lower()
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _version_key(version: str) -> Tuple[int, ...]:
|
|
608
|
+
"""Order versions by their numeric release segment, enough to detect a walk-back.
|
|
609
|
+
|
|
610
|
+
Pre/post/dev suffixes are ignored on purpose: a ``2.5.2`` versus
|
|
611
|
+
``2.5.2.post1`` gap is not the kind of downgrade worth a warning.
|
|
612
|
+
"""
|
|
613
|
+
match = re.match(r"^\d+(?:\.\d+)*", version)
|
|
614
|
+
return tuple(int(part) for part in match.group(0).split(".")) if match else ()
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def install(
|
|
618
|
+
config: AppConfig,
|
|
619
|
+
target: Target,
|
|
620
|
+
dest: Path,
|
|
621
|
+
*,
|
|
622
|
+
runner: CommandRunner,
|
|
623
|
+
python: Optional[str] = None,
|
|
624
|
+
) -> None:
|
|
625
|
+
"""Install ``config.requirements`` for ``target`` into ``dest``.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
config: The validated app configuration.
|
|
629
|
+
target: The device target (an iOS one; Android installs happen in
|
|
630
|
+
Gradle).
|
|
631
|
+
dest: The ``app_packages.<sdk>`` directory to populate.
|
|
632
|
+
runner: A [`CommandRunner`][pythonnative.project.builder.CommandRunner].
|
|
633
|
+
python: Interpreter to run pip with (defaults to the current one).
|
|
634
|
+
|
|
635
|
+
Raises:
|
|
636
|
+
DependencyError: When pip can't satisfy the requirements for the
|
|
637
|
+
target, with the unsatisfiable requirement(s) named.
|
|
638
|
+
"""
|
|
639
|
+
if not config.requirements:
|
|
640
|
+
return
|
|
641
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
642
|
+
from .lockfile import requirements
|
|
643
|
+
|
|
644
|
+
locked = requirements(config, [target], direct=True)
|
|
645
|
+
command = install_args(config, target, dest, python=python)
|
|
646
|
+
if locked is not None:
|
|
647
|
+
locked_path = dest.parent / f"requirements.{target.slice_name}.lock"
|
|
648
|
+
locked_path.write_text(locked, encoding="utf-8")
|
|
649
|
+
command = command[: -len(config.requirements)] + ["--no-deps", "-r", str(locked_path)]
|
|
650
|
+
result = runner.run(command, capture=True)
|
|
651
|
+
if result.ok:
|
|
652
|
+
return
|
|
653
|
+
summary, missing = parse_failure(result.stderr)
|
|
654
|
+
raise DependencyError(explain_failure(target, summary, missing))
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def explain_failure(target: Target, summary: str, missing: Sequence[str]) -> str:
|
|
658
|
+
"""Compose the user-facing message for a failed resolution."""
|
|
659
|
+
names = ", ".join(missing) if missing else "one or more requirements"
|
|
660
|
+
lines = [f"Could not resolve {names} for {target.label} (Python {target.python_version})."]
|
|
661
|
+
if summary:
|
|
662
|
+
lines.append(summary)
|
|
663
|
+
if target.platform == "ios":
|
|
664
|
+
lines.append(
|
|
665
|
+
"iOS needs a wheel tagged for the device (ios_*_arm64_iphoneos) and, for 'pn run ios', the "
|
|
666
|
+
"Simulator (ios_*_iphonesimulator). Pure-Python packages always work; for a C extension, check "
|
|
667
|
+
f"{BEEWARE_INDEX_URL} or ask upstream for iOS wheels (PEP 730)."
|
|
668
|
+
)
|
|
669
|
+
else:
|
|
670
|
+
lines.append(
|
|
671
|
+
"Android needs a wheel tagged android_*_arm64_v8a / android_*_x86_64 (PEP 738). Pure-Python "
|
|
672
|
+
f"packages always work; for a C extension, check {CHAQUOPY_INDEX_URL} or PyPI's Android wheels."
|
|
673
|
+
)
|
|
674
|
+
lines.append("Run 'pn deps' for a per-target report; see https://pythonnative.com/guides/pypi-packages/.")
|
|
675
|
+
return "\n".join(lines)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
# ======================================================================
|
|
679
|
+
# Reporting
|
|
680
|
+
# ======================================================================
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def format_report(resolutions: Sequence[Resolution], *, requirements: Sequence[str]) -> str:
|
|
684
|
+
"""Render resolutions as the terminal report ``pn deps`` prints."""
|
|
685
|
+
if not requirements:
|
|
686
|
+
return "No [requirements].packages declared in pythonnative.toml; nothing to resolve."
|
|
687
|
+
out: List[str] = []
|
|
688
|
+
for res in resolutions:
|
|
689
|
+
header = res.target.label
|
|
690
|
+
if res.target.platform == "android":
|
|
691
|
+
header += " (preview; Chaquopy resolves again inside the Gradle build)"
|
|
692
|
+
out.append(header)
|
|
693
|
+
if not res.ok:
|
|
694
|
+
out.append(" [x] " + (res.error or "resolution failed").replace("\n", "\n "))
|
|
695
|
+
out.append("")
|
|
696
|
+
continue
|
|
697
|
+
width = max((len(f"{pkg.name} {pkg.version}") for pkg in res.packages), default=0)
|
|
698
|
+
for pkg in res.packages:
|
|
699
|
+
name = f"{pkg.name} {pkg.version}".ljust(width)
|
|
700
|
+
kind = "pure Python" if pkg.is_pure else f"binary wheel {pkg.platform_tag} ({pkg.source_label})"
|
|
701
|
+
marker = "[!!]" if pkg.downgraded else "[ok]"
|
|
702
|
+
out.append(f" {marker} {name} {kind}")
|
|
703
|
+
if pkg.downgraded:
|
|
704
|
+
out.append(
|
|
705
|
+
f" older than the desktop resolution ({pkg.latest}): newer releases have no wheel here"
|
|
706
|
+
)
|
|
707
|
+
out.append("")
|
|
708
|
+
failed = [res for res in resolutions if not res.ok]
|
|
709
|
+
downgraded = sorted({pkg.name for res in resolutions for pkg in res.downgraded_packages}, key=str.lower)
|
|
710
|
+
if downgraded:
|
|
711
|
+
out.append(
|
|
712
|
+
f"Older release selected for: {', '.join(downgraded)} ([!!] above). Pin a version in "
|
|
713
|
+
"[requirements].packages if the API difference matters."
|
|
714
|
+
)
|
|
715
|
+
if failed:
|
|
716
|
+
out.append(f"{len(failed)} of {len(resolutions)} targets cannot be satisfied. See the [x] lines above.")
|
|
717
|
+
else:
|
|
718
|
+
out.append(f"All {len(resolutions)} targets resolved.")
|
|
719
|
+
return "\n".join(out)
|