pythonnative 0.24.0__tar.gz → 0.26.0__tar.gz
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-0.24.0/src/pythonnative.egg-info → pythonnative-0.26.0}/PKG-INFO +2 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/README.md +1 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/pyproject.toml +1 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/__init__.py +16 -4
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/animated.py +2 -2
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/cli/pn.py +172 -12
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/components.py +59 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/diagnostics.py +27 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/hooks.py +364 -81
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/app_state.py +2 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/battery.py +3 -2
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/biometrics.py +2 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/camera.py +16 -7
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/clipboard.py +3 -2
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/file_system.py +3 -2
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/haptics.py +7 -6
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/linking.py +62 -6
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/location.py +11 -8
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/net_info.py +2 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/notifications.py +132 -9
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/permissions.py +27 -3
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/share.py +2 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_views/android.py +149 -124
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_views/desktop.py +50 -49
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_views/ios.py +240 -161
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/android.py +15 -3
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/builder.py +172 -70
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/config.py +28 -6
- pythonnative-0.26.0/src/pythonnative/project/devices.py +291 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/doctor.py +5 -4
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/ios.py +51 -77
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/permissions.py +19 -1
- pythonnative-0.26.0/src/pythonnative/project/runtime_assets.py +184 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/reconciler.py +518 -37
- pythonnative-0.26.0/src/pythonnative/runtime.py +704 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/screen.py +53 -7
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/storage.py +2 -2
- pythonnative-0.26.0/src/pythonnative/suspense.py +417 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/build.gradle +5 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +1 -0
- pythonnative-0.26.0/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +115 -0
- pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +60 -0
- pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -2
- pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +397 -0
- pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +66 -0
- pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +157 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +72 -47
- {pythonnative-0.24.0 → pythonnative-0.26.0/src/pythonnative.egg-info}/PKG-INFO +2 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/SOURCES.txt +6 -1
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_async_hooks.py +110 -83
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_native_modules.py +79 -0
- pythonnative-0.26.0/tests/test_runtime.py +252 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_storage.py +15 -23
- pythonnative-0.26.0/tests/test_suspense.py +547 -0
- pythonnative-0.26.0/tests/test_transitions.py +178 -0
- pythonnative-0.24.0/src/pythonnative/project/runtime_assets.py +0 -272
- pythonnative-0.24.0/src/pythonnative/runtime.py +0 -512
- pythonnative-0.24.0/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -39
- pythonnative-0.24.0/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -36
- pythonnative-0.24.0/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -24
- pythonnative-0.24.0/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -55
- pythonnative-0.24.0/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -278
- pythonnative-0.24.0/tests/test_runtime.py +0 -146
- {pythonnative-0.24.0 → pythonnative-0.26.0}/LICENSE +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/setup.cfg +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/_ios_log.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/alerts.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/appearance.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/cli/__init__.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/element.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/events.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/gestures.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/hot_reload.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/images.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/layout.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/mutations.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/__init__.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/secure_store.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_views/__init__.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/native_views/base.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/navigation.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/net.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/platform.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/platform_metrics.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/preview.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/__init__.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/project/icons.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/sdk/__init__.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/sdk/_components.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/style.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradlew +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/utils.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative/virtual_rows.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/requires.txt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/top_level.txt +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_alert.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_animated.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_appearance.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_cli.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_components.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_core_semantics.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_desktop_backend.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_element.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_events.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_extended_components.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_gestures.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_hooks.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_hot_reload.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_images.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_incremental_render.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_ios_log.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_layout.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_metric_hooks.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_mutations.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_native_lists.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_native_views.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_navigation.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_net.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_new_components.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_platform.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_platform_metrics.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_reconciler.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_ref.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_screen.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_sdk.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_smoke.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_style.py +0 -0
- {pythonnative-0.24.0 → pythonnative-0.26.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonnative
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Cross-platform native UI toolkit for Android and iOS
|
|
5
5
|
Author: Owen Carey
|
|
6
6
|
License: MIT License
|
|
@@ -105,6 +105,7 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
|
|
|
105
105
|
- **Rich component library:** 25+ built-in components backed by real native widgets: `TextInput`, `Image` / `ImageBackground`, `ScrollView`, `FlatList` / `SectionList`, `Modal`, `Pressable` / `TouchableOpacity`, `Switch` / `Checkbox`, `Slider`, `SegmentedControl`, `Picker`, `DatePicker`, `ProgressBar` / `ActivityIndicator`, `WebView`, and more.
|
|
106
106
|
- **Device APIs:** Cross-platform modules for `Camera`, `Location`, `FileSystem`, `Notifications`, `Clipboard`, `Share`, `Linking`, `Permissions`, `AppState`, `NetInfo`, `SecureStore`, `Battery`, `Haptics` / `Vibration`, and `Biometrics`, plus reactive `use_app_state` and `use_net_info` hooks.
|
|
107
107
|
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect` / `use_layout_effect`, refs and imperative handles with `use_ref` / `use_imperative_handle`, and navigation with `use_navigation`, all through one consistent pattern. Components can return a single element, a list of siblings, or `None`, and `Fragment`, `Portal`, and reactive `Provider` context work the way they do in React.
|
|
108
|
+
- **Async-first rendering:** One `asyncio` event loop runs the whole framework on the platform's main thread. Components can be `async def` and `await` data directly in the body; `Suspense` boundaries show declarative loading states while they wait. `use_resource` fetches during render, `use_effect` accepts coroutine callbacks (cancelled automatically on unmount), `lazy` code-splits components, and `use_transition` / `use_deferred_value` keep the UI responsive during expensive updates.
|
|
108
109
|
- **Developer feedback that finds your bugs:** In dev mode (`pn preview`, hot reload, or `PN_DEV=1`), uncaught errors from renders, effects, and event handlers show a full-screen RedBox with the traceback; unknown style keys and duplicate list keys print "did you mean" warnings; and conditional hooks raise a `HookOrderError` at the source instead of silently cross-wiring state.
|
|
109
110
|
- **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
|
|
110
111
|
- **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
|
|
@@ -34,6 +34,7 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
|
|
|
34
34
|
- **Rich component library:** 25+ built-in components backed by real native widgets: `TextInput`, `Image` / `ImageBackground`, `ScrollView`, `FlatList` / `SectionList`, `Modal`, `Pressable` / `TouchableOpacity`, `Switch` / `Checkbox`, `Slider`, `SegmentedControl`, `Picker`, `DatePicker`, `ProgressBar` / `ActivityIndicator`, `WebView`, and more.
|
|
35
35
|
- **Device APIs:** Cross-platform modules for `Camera`, `Location`, `FileSystem`, `Notifications`, `Clipboard`, `Share`, `Linking`, `Permissions`, `AppState`, `NetInfo`, `SecureStore`, `Battery`, `Haptics` / `Vibration`, and `Biometrics`, plus reactive `use_app_state` and `use_net_info` hooks.
|
|
36
36
|
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect` / `use_layout_effect`, refs and imperative handles with `use_ref` / `use_imperative_handle`, and navigation with `use_navigation`, all through one consistent pattern. Components can return a single element, a list of siblings, or `None`, and `Fragment`, `Portal`, and reactive `Provider` context work the way they do in React.
|
|
37
|
+
- **Async-first rendering:** One `asyncio` event loop runs the whole framework on the platform's main thread. Components can be `async def` and `await` data directly in the body; `Suspense` boundaries show declarative loading states while they wait. `use_resource` fetches during render, `use_effect` accepts coroutine callbacks (cancelled automatically on unmount), `lazy` code-splits components, and `use_transition` / `use_deferred_value` keep the UI responsive during expensive updates.
|
|
37
38
|
- **Developer feedback that finds your bugs:** In dev mode (`pn preview`, hot reload, or `PN_DEV=1`), uncaught errors from renders, effects, and event handlers show a full-screen RedBox with the traceback; unknown style keys and duplicate list keys print "did you mean" warnings; and conditional hooks raise a `HookOrderError` at the source instead of silently cross-wiring state.
|
|
38
39
|
- **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
|
|
39
40
|
- **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
|
|
@@ -60,7 +60,7 @@ Example:
|
|
|
60
60
|
```
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
|
-
__version__ = "0.
|
|
63
|
+
__version__ = "0.26.0"
|
|
64
64
|
|
|
65
65
|
from . import appearance, diagnostics, gestures, images, runtime, sdk
|
|
66
66
|
from .alerts import Alert
|
|
@@ -92,6 +92,7 @@ from .components import (
|
|
|
92
92
|
Slider,
|
|
93
93
|
Spacer,
|
|
94
94
|
StatusBar,
|
|
95
|
+
Suspense,
|
|
95
96
|
Switch,
|
|
96
97
|
Text,
|
|
97
98
|
TextInput,
|
|
@@ -111,11 +112,11 @@ from .hooks import (
|
|
|
111
112
|
component,
|
|
112
113
|
create_context,
|
|
113
114
|
memo,
|
|
114
|
-
use_async_effect,
|
|
115
115
|
use_back_handler,
|
|
116
116
|
use_callback,
|
|
117
117
|
use_color_scheme,
|
|
118
118
|
use_context,
|
|
119
|
+
use_deferred_value,
|
|
119
120
|
use_effect,
|
|
120
121
|
use_imperative_handle,
|
|
121
122
|
use_keyboard_height,
|
|
@@ -126,8 +127,10 @@ from .hooks import (
|
|
|
126
127
|
use_query,
|
|
127
128
|
use_reducer,
|
|
128
129
|
use_ref,
|
|
130
|
+
use_resource,
|
|
129
131
|
use_safe_area_insets,
|
|
130
132
|
use_state,
|
|
133
|
+
use_transition,
|
|
131
134
|
use_window_dimensions,
|
|
132
135
|
)
|
|
133
136
|
from .native_modules import (
|
|
@@ -160,7 +163,7 @@ from .navigation import (
|
|
|
160
163
|
)
|
|
161
164
|
from .net import HTTPError, Response, fetch
|
|
162
165
|
from .platform import Platform
|
|
163
|
-
from .runtime import run_async
|
|
166
|
+
from .runtime import run_async, run_blocking
|
|
164
167
|
from .screen import create_screen
|
|
165
168
|
from .sdk import (
|
|
166
169
|
Props,
|
|
@@ -204,6 +207,7 @@ from .style import (
|
|
|
204
207
|
style,
|
|
205
208
|
use_theme,
|
|
206
209
|
)
|
|
210
|
+
from .suspense import Resource, lazy, start_resource
|
|
207
211
|
|
|
208
212
|
__all__ = [
|
|
209
213
|
# Components
|
|
@@ -233,6 +237,7 @@ __all__ = [
|
|
|
233
237
|
"Slider",
|
|
234
238
|
"Spacer",
|
|
235
239
|
"StatusBar",
|
|
240
|
+
"Suspense",
|
|
236
241
|
"Switch",
|
|
237
242
|
"Text",
|
|
238
243
|
"TextInput",
|
|
@@ -251,11 +256,11 @@ __all__ = [
|
|
|
251
256
|
"MutationState",
|
|
252
257
|
"QueryResult",
|
|
253
258
|
"Ref",
|
|
254
|
-
"use_async_effect",
|
|
255
259
|
"use_back_handler",
|
|
256
260
|
"use_callback",
|
|
257
261
|
"use_color_scheme",
|
|
258
262
|
"use_context",
|
|
263
|
+
"use_deferred_value",
|
|
259
264
|
"use_effect",
|
|
260
265
|
"use_focus_effect",
|
|
261
266
|
"use_imperative_handle",
|
|
@@ -268,11 +273,17 @@ __all__ = [
|
|
|
268
273
|
"use_query",
|
|
269
274
|
"use_reducer",
|
|
270
275
|
"use_ref",
|
|
276
|
+
"use_resource",
|
|
271
277
|
"use_route",
|
|
272
278
|
"use_safe_area_insets",
|
|
273
279
|
"use_state",
|
|
280
|
+
"use_transition",
|
|
274
281
|
"use_window_dimensions",
|
|
275
282
|
"Provider",
|
|
283
|
+
# Suspense and async rendering
|
|
284
|
+
"Resource",
|
|
285
|
+
"lazy",
|
|
286
|
+
"start_resource",
|
|
276
287
|
# Navigation
|
|
277
288
|
"NavigationContainer",
|
|
278
289
|
"ScreenOptions",
|
|
@@ -349,6 +360,7 @@ __all__ = [
|
|
|
349
360
|
"Response",
|
|
350
361
|
# Runtime
|
|
351
362
|
"run_async",
|
|
363
|
+
"run_blocking",
|
|
352
364
|
"runtime",
|
|
353
365
|
# Diagnostics
|
|
354
366
|
"HookOrderError",
|
|
@@ -47,7 +47,7 @@ Example:
|
|
|
47
47
|
await pn.Animated.timing(opacity, to=1.0, duration=400)
|
|
48
48
|
await pn.Animated.timing(opacity, to=0.5, duration=200)
|
|
49
49
|
|
|
50
|
-
pn.
|
|
50
|
+
pn.use_effect(fade_in, [])
|
|
51
51
|
|
|
52
52
|
return pn.Animated.View(
|
|
53
53
|
pn.Text("Hello!"),
|
|
@@ -1014,7 +1014,7 @@ def use_animated_value(initial: float = 0.0) -> AnimatedValue:
|
|
|
1014
1014
|
async def fade_in():
|
|
1015
1015
|
await pn.Animated.timing(opacity, to=1.0, duration=300)
|
|
1016
1016
|
|
|
1017
|
-
pn.
|
|
1017
|
+
pn.use_effect(fade_in, [])
|
|
1018
1018
|
return pn.Animated.View(
|
|
1019
1019
|
pn.Text("Hello"),
|
|
1020
1020
|
style=pn.style(opacity=opacity),
|
|
@@ -6,10 +6,14 @@ The console script `pn` (declared in `pyproject.toml`) dispatches to:
|
|
|
6
6
|
- `pn doctor [platform]`: diagnose the local toolchain and config.
|
|
7
7
|
- `pn preview [component]`: render the app in a desktop (Tkinter) window
|
|
8
8
|
with Fast Refresh, the fast inner dev loop, no device required.
|
|
9
|
-
- `pn
|
|
10
|
-
|
|
9
|
+
- `pn devices [platform]`: list connected devices, emulators, and
|
|
10
|
+
simulators.
|
|
11
|
+
- `pn run android|ios [--device D]`: stage + build + install + launch on
|
|
12
|
+
a device, emulator, or simulator, with optional on-device hot reload.
|
|
13
|
+
- `pn logs android|ios`: stream logs from the running app without
|
|
14
|
+
rebuilding.
|
|
11
15
|
- `pn build android|ios`: produce standalone artifacts (signed APK/AAB,
|
|
12
|
-
or an iOS archive/IPA).
|
|
16
|
+
or an iOS archive/IPA, optionally uploaded to App Store Connect).
|
|
13
17
|
- `pn app-id android|ios`: print the resolved application/bundle id
|
|
14
18
|
(handy for scripts and CI).
|
|
15
19
|
- `pn clean`: remove the local `build/` directory.
|
|
@@ -34,6 +38,7 @@ from pathlib import Path
|
|
|
34
38
|
from typing import Any, Dict, List, Optional
|
|
35
39
|
|
|
36
40
|
from ..project import builder as builder_mod
|
|
41
|
+
from ..project import devices as devices_mod
|
|
37
42
|
from ..project import doctor as doctor_mod
|
|
38
43
|
from ..project.android import collect_logcat_filters
|
|
39
44
|
from ..project.config import CONFIG_FILENAME, AppConfig, ConfigError, render_default_toml
|
|
@@ -241,6 +246,41 @@ def _preview_entry(project_dir: Path) -> str:
|
|
|
241
246
|
return "app.main"
|
|
242
247
|
|
|
243
248
|
|
|
249
|
+
# ======================================================================
|
|
250
|
+
# devices
|
|
251
|
+
# ======================================================================
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def devices_command(args: argparse.Namespace) -> None:
|
|
255
|
+
"""List connected devices, emulators, and simulators.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
args: Parsed namespace with optional ``platform``.
|
|
259
|
+
"""
|
|
260
|
+
platform: Optional[str] = getattr(args, "platform", None)
|
|
261
|
+
devices = devices_mod.list_devices(platform)
|
|
262
|
+
if not devices:
|
|
263
|
+
print("No devices found.")
|
|
264
|
+
print("Android: start an emulator or connect a device with USB debugging enabled.")
|
|
265
|
+
print("iOS: open Xcode once to install Simulators, or plug in a device.")
|
|
266
|
+
sys.exit(1)
|
|
267
|
+
print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
|
|
268
|
+
for device in devices:
|
|
269
|
+
print(device.format())
|
|
270
|
+
print("\nTarget one with: pn run <platform> --device <identifier or name>")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _resolve_device(platform: str, query: Optional[str]) -> Optional[devices_mod.Device]:
|
|
274
|
+
"""Resolve ``--device`` to a concrete target, exiting on a bad query."""
|
|
275
|
+
if not query:
|
|
276
|
+
return None
|
|
277
|
+
device = devices_mod.find_device(devices_mod.list_devices(platform), query)
|
|
278
|
+
if device is None:
|
|
279
|
+
print(f"Error: no {platform} device matches {query!r}. Run 'pn devices {platform}' to list targets.")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
return device
|
|
282
|
+
|
|
283
|
+
|
|
244
284
|
# ======================================================================
|
|
245
285
|
# run
|
|
246
286
|
# ======================================================================
|
|
@@ -250,13 +290,14 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
250
290
|
"""Stage, build, install, and launch the app on a device/simulator.
|
|
251
291
|
|
|
252
292
|
Args:
|
|
253
|
-
args: Parsed namespace (``platform``, ``
|
|
254
|
-
``hot_reload``, ``no_logs``).
|
|
293
|
+
args: Parsed namespace (``platform``, ``device``,
|
|
294
|
+
``prepare_only``, ``hot_reload``, ``no_logs``).
|
|
255
295
|
"""
|
|
256
296
|
platform: str = args.platform
|
|
257
297
|
prepare_only: bool = getattr(args, "prepare_only", False)
|
|
258
298
|
hot_reload: bool = getattr(args, "hot_reload", False)
|
|
259
299
|
show_logs: bool = not getattr(args, "no_logs", False)
|
|
300
|
+
device = _resolve_device(platform, getattr(args, "device", None))
|
|
260
301
|
|
|
261
302
|
config = _load_config_or_exit()
|
|
262
303
|
builder = builder_mod.Builder(config, log=print)
|
|
@@ -273,9 +314,14 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
273
314
|
|
|
274
315
|
try:
|
|
275
316
|
if platform == "android":
|
|
276
|
-
_run_android(builder, prepared, hot_reload=hot_reload, show_logs=show_logs)
|
|
317
|
+
_run_android(builder, prepared, device=device, hot_reload=hot_reload, show_logs=show_logs)
|
|
318
|
+
elif device is not None and device.kind == "device":
|
|
319
|
+
_run_ios_device(builder, prepared, device=device)
|
|
320
|
+
if hot_reload:
|
|
321
|
+
print("Note: hot reload targets the iOS Simulator; skipping it for a physical device.")
|
|
322
|
+
return
|
|
277
323
|
else:
|
|
278
|
-
_run_ios(builder, prepared, hot_reload=hot_reload, show_logs=show_logs)
|
|
324
|
+
_run_ios(builder, prepared, device=device, hot_reload=hot_reload, show_logs=show_logs)
|
|
279
325
|
except builder_mod.BuildError as exc:
|
|
280
326
|
print(f"Error: {exc}")
|
|
281
327
|
sys.exit(1)
|
|
@@ -295,9 +341,14 @@ def _run_android(
|
|
|
295
341
|
builder: builder_mod.Builder,
|
|
296
342
|
prepared: builder_mod.PreparedProject,
|
|
297
343
|
*,
|
|
344
|
+
device: Optional[devices_mod.Device],
|
|
298
345
|
hot_reload: bool,
|
|
299
346
|
show_logs: bool,
|
|
300
347
|
) -> None:
|
|
348
|
+
if device is not None:
|
|
349
|
+
# Both Gradle's install task and every adb call below honor
|
|
350
|
+
# ANDROID_SERIAL, so exporting it targets the whole run.
|
|
351
|
+
os.environ["ANDROID_SERIAL"] = device.identifier
|
|
301
352
|
builder.install_android_debug(prepared)
|
|
302
353
|
_clear_android_hot_reload_overlay(prepared.app_id)
|
|
303
354
|
subprocess.run(
|
|
@@ -319,11 +370,12 @@ def _run_ios(
|
|
|
319
370
|
builder: builder_mod.Builder,
|
|
320
371
|
prepared: builder_mod.PreparedProject,
|
|
321
372
|
*,
|
|
373
|
+
device: Optional[devices_mod.Device],
|
|
322
374
|
hot_reload: bool,
|
|
323
375
|
show_logs: bool,
|
|
324
376
|
) -> None:
|
|
325
377
|
app_path = builder.build_ios_simulator(prepared)
|
|
326
|
-
udid = _select_ios_simulator()
|
|
378
|
+
udid = device.identifier if device is not None else _select_ios_simulator()
|
|
327
379
|
if udid is None:
|
|
328
380
|
print("No available iOS Simulators found; open the project in Xcode to run.")
|
|
329
381
|
return
|
|
@@ -352,6 +404,45 @@ def _run_ios(
|
|
|
352
404
|
print("Launched iOS app on Simulator.")
|
|
353
405
|
|
|
354
406
|
|
|
407
|
+
def _run_ios_device(
|
|
408
|
+
builder: builder_mod.Builder,
|
|
409
|
+
prepared: builder_mod.PreparedProject,
|
|
410
|
+
*,
|
|
411
|
+
device: devices_mod.Device,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Build, install, and launch on a physical iOS device via devicectl."""
|
|
414
|
+
app_path = builder.build_ios_device(prepared)
|
|
415
|
+
print(f"Installing on {device.name}...")
|
|
416
|
+
install = subprocess.run(
|
|
417
|
+
["xcrun", "devicectl", "device", "install", "app", "--device", device.identifier, str(app_path)],
|
|
418
|
+
check=False,
|
|
419
|
+
)
|
|
420
|
+
if install.returncode != 0:
|
|
421
|
+
print(
|
|
422
|
+
"Error: install failed. Make sure the device is unlocked, paired with this Mac, "
|
|
423
|
+
"and has Developer Mode enabled (Settings > Privacy & Security > Developer Mode)."
|
|
424
|
+
)
|
|
425
|
+
sys.exit(1)
|
|
426
|
+
launch = subprocess.run(
|
|
427
|
+
[
|
|
428
|
+
"xcrun",
|
|
429
|
+
"devicectl",
|
|
430
|
+
"device",
|
|
431
|
+
"process",
|
|
432
|
+
"launch",
|
|
433
|
+
"--terminate-existing",
|
|
434
|
+
"--device",
|
|
435
|
+
device.identifier,
|
|
436
|
+
prepared.app_id,
|
|
437
|
+
],
|
|
438
|
+
check=False,
|
|
439
|
+
)
|
|
440
|
+
if launch.returncode != 0:
|
|
441
|
+
print("Error: launch failed. Launch the app from the home screen to see details.")
|
|
442
|
+
sys.exit(1)
|
|
443
|
+
print(f"Launched on {device.name}. Use Console.app (or Xcode > Devices) for device logs.")
|
|
444
|
+
|
|
445
|
+
|
|
355
446
|
# ======================================================================
|
|
356
447
|
# build
|
|
357
448
|
# ======================================================================
|
|
@@ -361,16 +452,24 @@ def build_project(args: argparse.Namespace) -> None:
|
|
|
361
452
|
"""Build standalone, distributable artifacts for ``platform``.
|
|
362
453
|
|
|
363
454
|
Args:
|
|
364
|
-
args: Parsed namespace (``platform``, ``debug``).
|
|
455
|
+
args: Parsed namespace (``platform``, ``debug``, ``upload``).
|
|
365
456
|
"""
|
|
366
457
|
platform: str = args.platform
|
|
367
458
|
debug: bool = getattr(args, "debug", False)
|
|
459
|
+
upload: bool = getattr(args, "upload", False)
|
|
368
460
|
|
|
369
461
|
config = _load_config_or_exit()
|
|
370
462
|
builder = builder_mod.Builder(config, log=print)
|
|
371
463
|
|
|
464
|
+
if upload and (platform != "ios" or debug):
|
|
465
|
+
print("Error: --upload applies to 'pn build ios' release builds only.")
|
|
466
|
+
sys.exit(1)
|
|
467
|
+
if upload and config.ios.signing.export_method != "app-store":
|
|
468
|
+
print('Error: --upload requires [ios.signing] export_method = "app-store" in pythonnative.toml.')
|
|
469
|
+
sys.exit(1)
|
|
470
|
+
|
|
372
471
|
try:
|
|
373
|
-
prepared = builder.prepare(platform)
|
|
472
|
+
prepared = builder.prepare(platform, release=not debug)
|
|
374
473
|
if platform == "android":
|
|
375
474
|
artifacts = builder.build_android(prepared, debug=debug)
|
|
376
475
|
else:
|
|
@@ -378,19 +477,61 @@ def build_project(args: argparse.Namespace) -> None:
|
|
|
378
477
|
app_path = builder.build_ios_simulator(prepared)
|
|
379
478
|
artifacts = builder_mod.BuildArtifacts(paths=[app_path])
|
|
380
479
|
else:
|
|
381
|
-
artifacts = builder.build_ios_archive(prepared)
|
|
480
|
+
artifacts = builder.build_ios_archive(prepared, upload=upload)
|
|
382
481
|
except builder_mod.BuildError as exc:
|
|
383
482
|
print(f"Error: {exc}")
|
|
384
483
|
sys.exit(1)
|
|
385
484
|
|
|
485
|
+
if upload:
|
|
486
|
+
print("\nUploaded to App Store Connect. Check the build's status at appstoreconnect.apple.com.")
|
|
386
487
|
if not artifacts.paths:
|
|
387
|
-
|
|
488
|
+
if not upload:
|
|
489
|
+
print("Build completed, but no artifacts were found. Check the build output above.")
|
|
388
490
|
return
|
|
389
491
|
print("\nBuilt artifacts:")
|
|
390
492
|
for path in artifacts.paths:
|
|
391
493
|
print(f" {path}")
|
|
392
494
|
|
|
393
495
|
|
|
496
|
+
# ======================================================================
|
|
497
|
+
# logs
|
|
498
|
+
# ======================================================================
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def logs_command(args: argparse.Namespace) -> None:
|
|
502
|
+
"""Stream logs from the running app without rebuilding.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
args: Parsed namespace (``platform``).
|
|
506
|
+
"""
|
|
507
|
+
platform: str = args.platform
|
|
508
|
+
if platform == "android":
|
|
509
|
+
proc = _start_android_log_stream()
|
|
510
|
+
if proc is None:
|
|
511
|
+
sys.exit(1)
|
|
512
|
+
try:
|
|
513
|
+
proc.wait()
|
|
514
|
+
except KeyboardInterrupt:
|
|
515
|
+
print()
|
|
516
|
+
_terminate_subprocess(proc)
|
|
517
|
+
print("Stopped log streaming.")
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
# iOS: relaunch the app on the booted simulator with a console PTY so
|
|
521
|
+
# Python's stdout/stderr stream to this terminal.
|
|
522
|
+
config = _load_config_or_exit()
|
|
523
|
+
proc = _start_ios_log_stream(config.bundle_id)
|
|
524
|
+
if proc is None:
|
|
525
|
+
print("For a physical device, use Console.app or Xcode > Devices and Simulators.")
|
|
526
|
+
sys.exit(1)
|
|
527
|
+
try:
|
|
528
|
+
proc.wait()
|
|
529
|
+
except KeyboardInterrupt:
|
|
530
|
+
print()
|
|
531
|
+
_terminate_subprocess(proc)
|
|
532
|
+
print("Stopped log streaming.")
|
|
533
|
+
|
|
534
|
+
|
|
394
535
|
# ======================================================================
|
|
395
536
|
# clean
|
|
396
537
|
# ======================================================================
|
|
@@ -747,16 +888,35 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
747
888
|
parser_preview.add_argument("--no-hot-reload", action="store_true", help="Disable file watching / Fast Refresh")
|
|
748
889
|
parser_preview.set_defaults(func=preview_project)
|
|
749
890
|
|
|
891
|
+
parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
|
|
892
|
+
parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
|
|
893
|
+
parser_devices.set_defaults(func=devices_command)
|
|
894
|
+
|
|
750
895
|
parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
|
|
751
896
|
parser_run.add_argument("platform", choices=["android", "ios"])
|
|
897
|
+
parser_run.add_argument(
|
|
898
|
+
"--device",
|
|
899
|
+
"-d",
|
|
900
|
+
help="Target device: an identifier or name from 'pn devices' "
|
|
901
|
+
"(physical iOS devices need [ios].development_team)",
|
|
902
|
+
)
|
|
752
903
|
parser_run.add_argument("--prepare-only", action="store_true", help="Stage + configure without building")
|
|
753
904
|
parser_run.add_argument("--hot-reload", action="store_true", help="Watch app/ and push updates to the running app")
|
|
754
905
|
parser_run.add_argument("--no-logs", action="store_true", help="Don't stream device logs after launch")
|
|
755
906
|
parser_run.set_defaults(func=run_project)
|
|
756
907
|
|
|
908
|
+
parser_logs = subparsers.add_parser("logs", help="Stream logs from the running app")
|
|
909
|
+
parser_logs.add_argument("platform", choices=["android", "ios"])
|
|
910
|
+
parser_logs.set_defaults(func=logs_command)
|
|
911
|
+
|
|
757
912
|
parser_build = subparsers.add_parser("build", help="Build distributable artifacts")
|
|
758
913
|
parser_build.add_argument("platform", choices=["android", "ios"])
|
|
759
914
|
parser_build.add_argument("--debug", action="store_true", help="Build the debug variant instead of release")
|
|
915
|
+
parser_build.add_argument(
|
|
916
|
+
"--upload",
|
|
917
|
+
action="store_true",
|
|
918
|
+
help='Upload the iOS release build to App Store Connect (needs export_method = "app-store")',
|
|
919
|
+
)
|
|
760
920
|
parser_build.set_defaults(func=build_project)
|
|
761
921
|
|
|
762
922
|
parser_app_id = subparsers.add_parser("app-id", help="Print the resolved application/bundle id")
|
|
@@ -1732,6 +1732,65 @@ def ErrorBoundary(
|
|
|
1732
1732
|
return Element("__ErrorBoundary__", props, list(children), key=key)
|
|
1733
1733
|
|
|
1734
1734
|
|
|
1735
|
+
def Suspense(
|
|
1736
|
+
*children: Element,
|
|
1737
|
+
fallback: Optional[Any] = None,
|
|
1738
|
+
key: Optional[str] = None,
|
|
1739
|
+
) -> Element:
|
|
1740
|
+
"""Show ``fallback`` while descendants wait on async work, then swap in the content.
|
|
1741
|
+
|
|
1742
|
+
A Suspense boundary catches **suspensions** from the subtree it
|
|
1743
|
+
wraps: an ``async def`` component body blocking on a pending
|
|
1744
|
+
await, or a regular component calling
|
|
1745
|
+
[`Resource.read`][pythonnative.suspense.Resource.read] on data that hasn't
|
|
1746
|
+
arrived (see [`use_resource`][pythonnative.use_resource] and
|
|
1747
|
+
[`lazy`][pythonnative.lazy]). While anything is pending the
|
|
1748
|
+
boundary renders ``fallback``; when the awaited work completes it
|
|
1749
|
+
retries the content and swaps it in. Suspended components keep
|
|
1750
|
+
their hook state across retries, so cached resources aren't
|
|
1751
|
+
refetched.
|
|
1752
|
+
|
|
1753
|
+
Two timing behaviors, matching React:
|
|
1754
|
+
|
|
1755
|
+
- **Initial mount**: the fallback shows until the content is ready.
|
|
1756
|
+
- **Updates**: a component that's already on screen and suspends
|
|
1757
|
+
again (its dependencies changed) keeps its previous content
|
|
1758
|
+
visible and re-renders when ready; there's no fallback flash.
|
|
1759
|
+
|
|
1760
|
+
Args:
|
|
1761
|
+
*children: Subtree to wrap (the async content).
|
|
1762
|
+
fallback: Content shown while suspended: an
|
|
1763
|
+
[`Element`][pythonnative.Element] or a zero-arg callable
|
|
1764
|
+
returning one. Without it, suspensions propagate to the
|
|
1765
|
+
next Suspense boundary up.
|
|
1766
|
+
key: Stable identity for keyed reconciliation.
|
|
1767
|
+
|
|
1768
|
+
Returns:
|
|
1769
|
+
An [`Element`][pythonnative.Element] of type ``"__Suspense__"``.
|
|
1770
|
+
|
|
1771
|
+
Example:
|
|
1772
|
+
```python
|
|
1773
|
+
import pythonnative as pn
|
|
1774
|
+
|
|
1775
|
+
@pn.component
|
|
1776
|
+
async def Profile(user_id: str):
|
|
1777
|
+
user = await api.fetch_user(user_id)
|
|
1778
|
+
return pn.Text(user.name)
|
|
1779
|
+
|
|
1780
|
+
@pn.component
|
|
1781
|
+
def Screen():
|
|
1782
|
+
return pn.Suspense(
|
|
1783
|
+
Profile(user_id="42"),
|
|
1784
|
+
fallback=pn.ActivityIndicator(),
|
|
1785
|
+
)
|
|
1786
|
+
```
|
|
1787
|
+
"""
|
|
1788
|
+
props: Dict[str, Any] = {}
|
|
1789
|
+
if fallback is not None:
|
|
1790
|
+
props["__fallback__"] = fallback
|
|
1791
|
+
return Element("__Suspense__", props, list(children), key=key)
|
|
1792
|
+
|
|
1793
|
+
|
|
1735
1794
|
# ======================================================================
|
|
1736
1795
|
# Lists (native virtualization with a Python-windowed fallback)
|
|
1737
1796
|
# ======================================================================
|
|
@@ -37,6 +37,7 @@ __all__ = [
|
|
|
37
37
|
"is_dev",
|
|
38
38
|
"warn",
|
|
39
39
|
"warn_once",
|
|
40
|
+
"swallowed",
|
|
40
41
|
"get_warnings",
|
|
41
42
|
"clear_warnings",
|
|
42
43
|
"set_error_reporter",
|
|
@@ -143,6 +144,32 @@ def warn_once(message: str, key: Optional[str] = None) -> None:
|
|
|
143
144
|
warn(message)
|
|
144
145
|
|
|
145
146
|
|
|
147
|
+
def swallowed(context: str) -> None:
|
|
148
|
+
"""Surface a suppressed native-backend exception (dev mode only).
|
|
149
|
+
|
|
150
|
+
The native view/module backends intentionally degrade gracefully in
|
|
151
|
+
production: a failed style application or a missing OS API must not
|
|
152
|
+
crash the app. Call this from the ``except`` block instead of
|
|
153
|
+
``pass`` so that, in dev mode, each suppression is reported once per
|
|
154
|
+
call site instead of disappearing.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
context: Where the suppression happened, e.g.
|
|
158
|
+
``"ios.TextHandler._apply"``.
|
|
159
|
+
"""
|
|
160
|
+
if not is_dev():
|
|
161
|
+
return
|
|
162
|
+
exc = sys.exc_info()[1]
|
|
163
|
+
if exc is None:
|
|
164
|
+
return
|
|
165
|
+
frame = sys._getframe(1)
|
|
166
|
+
location = f"{os.path.basename(frame.f_code.co_filename)}:{frame.f_lineno}"
|
|
167
|
+
warn_once(
|
|
168
|
+
f"{context} suppressed {type(exc).__name__}: {exc} ({location})",
|
|
169
|
+
key=f"swallowed:{context}:{location}",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
146
173
|
def get_warnings() -> List[str]:
|
|
147
174
|
"""Return a snapshot of the recorded warnings (oldest first)."""
|
|
148
175
|
with _warn_lock:
|