pythonnative 0.30.0__tar.gz → 0.31.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.30.0/src/pythonnative.egg-info → pythonnative-0.31.0}/PKG-INFO +1 -1
- {pythonnative-0.30.0 → pythonnative-0.31.0}/pyproject.toml +1 -1
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/__init__.py +1 -1
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/cli/pn.py +37 -6
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/devices.py +14 -1
- {pythonnative-0.30.0 → pythonnative-0.31.0/src/pythonnative.egg-info}/PKG-INFO +1 -1
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_cli.py +198 -3
- {pythonnative-0.30.0 → pythonnative-0.31.0}/LICENSE +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/README.md +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/setup.cfg +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/_ios_log.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/alerts.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/animated.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/appearance.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/cli/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/diagnostics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/element.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/events.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/gestures.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/hot_reload.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/images.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/layout.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/mutations.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/app_state.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/battery.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/biometrics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/camera.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/clipboard.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/file_system.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/haptics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/linking.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/location.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/net_info.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/notifications.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/permissions.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/secure_store.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/share.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_views/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_views/android.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_views/base.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_views/desktop.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_views/ios.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/navigation.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/net.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/platform.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/platform_metrics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/preview.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/android.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/builder.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/config.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/doctor.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/icons.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/ios.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/permissions.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/project/runtime_assets.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/reconciler.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/runtime.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/screen.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/sdk/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/sdk/_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/storage.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/style.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/suspense.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradlew +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/utils.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/virtual_rows.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/SOURCES.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/requires.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/top_level.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_alert.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_animated.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_animated_graph.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_appearance.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_async_hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_core_semantics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_desktop_backend.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_element.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_events.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_extended_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_gesture_composition.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_gestures.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_hot_reload.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_images.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_incremental_render.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_interaction_props.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_ios_log.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_layout.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_metric_hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_mutations.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_native_lists.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_native_modules.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_native_views.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_navigation.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_net.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_new_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_platform.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_platform_metrics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_reconciler.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_ref.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_runtime.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_screen.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_sdk.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_smoke.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_storage.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_style.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_suspense.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_transitions.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.31.0}/tests/test_utils.py +0 -0
|
@@ -9,7 +9,7 @@ The console script `pn` (declared in `pyproject.toml`) dispatches to:
|
|
|
9
9
|
- `pn preview [component]`: render the app in a desktop (Tkinter) window
|
|
10
10
|
with Fast Refresh, the fast inner dev loop, no device required.
|
|
11
11
|
- `pn devices [platform]`: list connected devices, emulators, and
|
|
12
|
-
simulators
|
|
12
|
+
simulators, as a table or as JSON with `--json`.
|
|
13
13
|
- `pn run android|ios [--device D]`: stage + build + install + launch on
|
|
14
14
|
a device, emulator, or simulator, with optional on-device hot reload.
|
|
15
15
|
- `pn logs android|ios`: stream logs from the running app without
|
|
@@ -38,7 +38,7 @@ import sys
|
|
|
38
38
|
import time
|
|
39
39
|
from importlib.metadata import version as pkg_version
|
|
40
40
|
from pathlib import Path
|
|
41
|
-
from typing import Any, Dict, List, Optional
|
|
41
|
+
from typing import Any, Dict, List, Optional, TextIO
|
|
42
42
|
|
|
43
43
|
from ..project import builder as builder_mod
|
|
44
44
|
from ..project import devices as devices_mod
|
|
@@ -298,18 +298,46 @@ def _preview_entry(project_dir: Path) -> str:
|
|
|
298
298
|
# ======================================================================
|
|
299
299
|
|
|
300
300
|
|
|
301
|
+
def _print_no_devices_hints(stream: TextIO) -> None:
|
|
302
|
+
"""Print the "no devices" guidance to ``stream``.
|
|
303
|
+
|
|
304
|
+
Shared by both output modes so the wording can't drift between the
|
|
305
|
+
table (which sends it to stdout) and ``--json`` (stderr).
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
stream: Where to write, ``sys.stdout`` or ``sys.stderr``.
|
|
309
|
+
"""
|
|
310
|
+
print("No devices found.", file=stream)
|
|
311
|
+
print("Android: start an emulator or connect a device with USB debugging enabled.", file=stream)
|
|
312
|
+
print("iOS: open Xcode once to install Simulators, or plug in a device.", file=stream)
|
|
313
|
+
|
|
314
|
+
|
|
301
315
|
def devices_command(args: argparse.Namespace) -> None:
|
|
302
316
|
"""List connected devices, emulators, and simulators.
|
|
303
317
|
|
|
318
|
+
Prints an aligned table and exits 1 when nothing is connected.
|
|
319
|
+
|
|
320
|
+
With ``--json``, stdout carries a JSON array and nothing else, one
|
|
321
|
+
object per device (see ``Device.to_dict``), so it stays parseable.
|
|
322
|
+
The "no devices" hints go to stderr instead, an empty result prints
|
|
323
|
+
``[]``, and the exit status is 0 either way, since "no devices" is a
|
|
324
|
+
valid answer for a script rather than a failure.
|
|
325
|
+
|
|
304
326
|
Args:
|
|
305
|
-
args: Parsed namespace with optional ``platform``.
|
|
327
|
+
args: Parsed namespace with optional ``platform`` and ``json``.
|
|
306
328
|
"""
|
|
307
329
|
platform: Optional[str] = getattr(args, "platform", None)
|
|
330
|
+
as_json: bool = getattr(args, "json", False)
|
|
308
331
|
devices = devices_mod.list_devices(platform)
|
|
332
|
+
|
|
333
|
+
if as_json:
|
|
334
|
+
if not devices:
|
|
335
|
+
_print_no_devices_hints(sys.stderr)
|
|
336
|
+
print(json.dumps([device.to_dict() for device in devices], indent=2))
|
|
337
|
+
return
|
|
338
|
+
|
|
309
339
|
if not devices:
|
|
310
|
-
|
|
311
|
-
print("Android: start an emulator or connect a device with USB debugging enabled.")
|
|
312
|
-
print("iOS: open Xcode once to install Simulators, or plug in a device.")
|
|
340
|
+
_print_no_devices_hints(sys.stdout)
|
|
313
341
|
sys.exit(1)
|
|
314
342
|
print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
|
|
315
343
|
for device in devices:
|
|
@@ -943,6 +971,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
943
971
|
|
|
944
972
|
parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
|
|
945
973
|
parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
|
|
974
|
+
parser_devices.add_argument(
|
|
975
|
+
"--json", action="store_true", help="Print a JSON array to stdout for scripting (hints go to stderr)"
|
|
976
|
+
)
|
|
946
977
|
parser_devices.set_defaults(func=devices_command)
|
|
947
978
|
|
|
948
979
|
parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
|
|
@@ -18,7 +18,7 @@ import json
|
|
|
18
18
|
import re
|
|
19
19
|
import subprocess
|
|
20
20
|
import tempfile
|
|
21
|
-
from dataclasses import dataclass
|
|
21
|
+
from dataclasses import asdict, dataclass
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Any, Dict, List, Optional
|
|
24
24
|
|
|
@@ -64,6 +64,19 @@ class Device:
|
|
|
64
64
|
return self.state in ("booted", "shutdown")
|
|
65
65
|
return self.state in ("booted", "connected")
|
|
66
66
|
|
|
67
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
68
|
+
"""Return a JSON-serializable view of this target.
|
|
69
|
+
|
|
70
|
+
Derived from the dataclass fields so a field added later is
|
|
71
|
+
carried into the payload automatically, plus the computed
|
|
72
|
+
``is_ready`` that consumers would otherwise have to re-derive
|
|
73
|
+
from ``kind`` and ``state``.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The declared fields in declaration order, then ``is_ready``.
|
|
77
|
+
"""
|
|
78
|
+
return {**asdict(self), "is_ready": self.is_ready}
|
|
79
|
+
|
|
67
80
|
def format(self) -> str:
|
|
68
81
|
"""Return one aligned listing row for the CLI."""
|
|
69
82
|
os_part = f" ({self.os_version})" if self.os_version else ""
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
1
3
|
import os
|
|
2
4
|
import shutil
|
|
3
5
|
import subprocess
|
|
4
6
|
import sys
|
|
5
7
|
import tempfile
|
|
6
8
|
from pathlib import Path
|
|
7
|
-
from typing import List
|
|
9
|
+
from typing import Callable, Dict, List, Optional
|
|
8
10
|
|
|
9
11
|
import pytest
|
|
10
12
|
|
|
11
13
|
import pythonnative.cli.pn as pn_cli
|
|
12
14
|
import pythonnative.hot_reload as hot_reload_module
|
|
15
|
+
from pythonnative.project.devices import Device
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
def run_pn(args: List[str], cwd: str) -> "subprocess.CompletedProcess[str]":
|
|
18
|
+
def run_pn(args: List[str], cwd: str, env: Optional[Dict[str, str]] = None) -> "subprocess.CompletedProcess[str]":
|
|
16
19
|
cmd = [sys.executable, "-m", "pythonnative.cli.pn"] + args
|
|
17
|
-
return subprocess.run(cmd, cwd=cwd, check=False, capture_output=True, text=True)
|
|
20
|
+
return subprocess.run(cmd, cwd=cwd, check=False, capture_output=True, text=True, env=env)
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def test_cli_version(tmp_path: Path) -> None:
|
|
@@ -339,6 +342,198 @@ def test_booted_ios_udid_handles_xcrun_missing(monkeypatch: pytest.MonkeyPatch)
|
|
|
339
342
|
assert pn_cli._booted_ios_udid() is None
|
|
340
343
|
|
|
341
344
|
|
|
345
|
+
# `pn devices` tests run devices_command() in-process rather than through run_pn,
|
|
346
|
+
# because run_pn spawns a child interpreter that monkeypatch can't reach: it would
|
|
347
|
+
# re-import the real list_devices and shell out to adb/xcrun, making the result
|
|
348
|
+
# depend on whatever hardware the test machine has attached. The one run_pn test
|
|
349
|
+
# below empties PATH instead, so every list_* call hits FileNotFoundError.
|
|
350
|
+
|
|
351
|
+
# A naive `state == "booted"` readiness check disagrees with is_ready on two of
|
|
352
|
+
# these: the connected Android device (ready, not booted) and the shutdown
|
|
353
|
+
# simulator (ready, because simulators boot on demand).
|
|
354
|
+
_FAKE_DEVICES = [
|
|
355
|
+
Device("android", "device", "R5CT12345XYZ", "Pixel 8 Pro", "", "connected"),
|
|
356
|
+
Device("android", "device", "OFFLINE99", "Galaxy S24", "", "offline"),
|
|
357
|
+
Device("ios", "simulator", "ABC-123", "iPhone 17 Pro", "iOS 26.4", "booted"),
|
|
358
|
+
Device("ios", "simulator", "DEF-456", "iPad Pro 13-inch (M4)", "iOS 26.4", "shutdown"),
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
_NO_DEVICES_HINTS = (
|
|
362
|
+
"No devices found.\n"
|
|
363
|
+
"Android: start an emulator or connect a device with USB debugging enabled.\n"
|
|
364
|
+
"iOS: open Xcode once to install Simulators, or plug in a device.\n"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
_DEVICE_TABLE = (
|
|
368
|
+
" IDENTIFIER KIND STATE NAME\n"
|
|
369
|
+
" R5CT12345XYZ device connected Pixel 8 Pro\n"
|
|
370
|
+
" OFFLINE99 device offline Galaxy S24\n"
|
|
371
|
+
" ABC-123 simulator booted iPhone 17 Pro (iOS 26.4)\n"
|
|
372
|
+
" DEF-456 simulator shutdown iPad Pro 13-inch (M4) (iOS 26.4)\n"
|
|
373
|
+
"\n"
|
|
374
|
+
"Target one with: pn run <platform> --device <identifier or name>\n"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _fake_list_devices(
|
|
379
|
+
devices: List[Device], calls: Optional[List[Optional[str]]] = None
|
|
380
|
+
) -> Callable[..., List[Device]]:
|
|
381
|
+
"""Stand in for list_devices, filtering by platform and recording the argument."""
|
|
382
|
+
|
|
383
|
+
def _list(platform: Optional[str] = None) -> List[Device]:
|
|
384
|
+
if calls is not None:
|
|
385
|
+
calls.append(platform)
|
|
386
|
+
return [device for device in devices if platform in (None, device.platform)]
|
|
387
|
+
|
|
388
|
+
return _list
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def test_devices_json_emits_parseable_array(
|
|
392
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
393
|
+
) -> None:
|
|
394
|
+
monkeypatch.setattr(pn_cli.devices_mod, "list_devices", _fake_list_devices(_FAKE_DEVICES))
|
|
395
|
+
|
|
396
|
+
# Returns rather than exiting, so a populated listing stays exit 0.
|
|
397
|
+
pn_cli.devices_command(argparse.Namespace(platform=None, json=True))
|
|
398
|
+
|
|
399
|
+
payload = json.loads(capsys.readouterr().out)
|
|
400
|
+
assert [entry["identifier"] for entry in payload] == [
|
|
401
|
+
"R5CT12345XYZ",
|
|
402
|
+
"OFFLINE99",
|
|
403
|
+
"ABC-123",
|
|
404
|
+
"DEF-456",
|
|
405
|
+
]
|
|
406
|
+
for entry in payload:
|
|
407
|
+
assert set(entry) == {
|
|
408
|
+
"platform",
|
|
409
|
+
"kind",
|
|
410
|
+
"identifier",
|
|
411
|
+
"name",
|
|
412
|
+
"os_version",
|
|
413
|
+
"state",
|
|
414
|
+
"is_ready",
|
|
415
|
+
}
|
|
416
|
+
ready = {entry["identifier"]: entry["is_ready"] for entry in payload}
|
|
417
|
+
assert ready == {"R5CT12345XYZ": True, "OFFLINE99": False, "ABC-123": True, "DEF-456": True}
|
|
418
|
+
|
|
419
|
+
# The two entries a naive `state == "booted"` check would get wrong.
|
|
420
|
+
by_id = {entry["identifier"]: entry for entry in payload}
|
|
421
|
+
assert by_id["DEF-456"]["state"] == "shutdown" and by_id["DEF-456"]["is_ready"] is True
|
|
422
|
+
assert by_id["R5CT12345XYZ"]["state"] == "connected" and by_id["R5CT12345XYZ"]["is_ready"] is True
|
|
423
|
+
assert by_id["OFFLINE99"]["kind"] == "device" and by_id["OFFLINE99"]["is_ready"] is False
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_devices_json_empty_prints_array_and_hints_to_stderr(
|
|
427
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
428
|
+
) -> None:
|
|
429
|
+
monkeypatch.setattr(pn_cli.devices_mod, "list_devices", _fake_list_devices([]))
|
|
430
|
+
|
|
431
|
+
# No SystemExit: an empty result is exit 0 under --json, unlike the table.
|
|
432
|
+
pn_cli.devices_command(argparse.Namespace(platform=None, json=True))
|
|
433
|
+
|
|
434
|
+
captured = capsys.readouterr()
|
|
435
|
+
assert captured.out == "[]\n"
|
|
436
|
+
assert json.loads(captured.out) == []
|
|
437
|
+
assert captured.err == _NO_DEVICES_HINTS
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@pytest.mark.parametrize(
|
|
441
|
+
("platform", "expected"),
|
|
442
|
+
[("android", ["R5CT12345XYZ", "OFFLINE99"]), ("ios", ["ABC-123", "DEF-456"])],
|
|
443
|
+
)
|
|
444
|
+
def test_devices_json_respects_platform_filter(
|
|
445
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
446
|
+
capsys: pytest.CaptureFixture[str],
|
|
447
|
+
platform: str,
|
|
448
|
+
expected: List[str],
|
|
449
|
+
) -> None:
|
|
450
|
+
calls: List[Optional[str]] = []
|
|
451
|
+
monkeypatch.setattr(pn_cli.devices_mod, "list_devices", _fake_list_devices(_FAKE_DEVICES, calls))
|
|
452
|
+
|
|
453
|
+
pn_cli.devices_command(argparse.Namespace(platform=platform, json=True))
|
|
454
|
+
|
|
455
|
+
assert calls == [platform]
|
|
456
|
+
payload = json.loads(capsys.readouterr().out)
|
|
457
|
+
assert {entry["platform"] for entry in payload} == {platform}
|
|
458
|
+
assert [entry["identifier"] for entry in payload] == expected
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@pytest.mark.parametrize("devices", [[], _FAKE_DEVICES], ids=["empty", "populated"])
|
|
462
|
+
def test_devices_json_keeps_human_text_off_stdout(
|
|
463
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], devices: List[Device]
|
|
464
|
+
) -> None:
|
|
465
|
+
monkeypatch.setattr(pn_cli.devices_mod, "list_devices", _fake_list_devices(devices))
|
|
466
|
+
|
|
467
|
+
pn_cli.devices_command(argparse.Namespace(platform=None, json=True))
|
|
468
|
+
|
|
469
|
+
out = capsys.readouterr().out
|
|
470
|
+
json.loads(out)
|
|
471
|
+
# Every non-JSON source the table mode prints: the three hints, the column
|
|
472
|
+
# header, each aligned row, and the trailer.
|
|
473
|
+
for hint in _NO_DEVICES_HINTS.splitlines():
|
|
474
|
+
assert hint not in out
|
|
475
|
+
assert "IDENTIFIER" not in out
|
|
476
|
+
assert "Target one with:" not in out
|
|
477
|
+
for device in devices:
|
|
478
|
+
assert device.format() not in out
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def test_devices_human_output_unchanged_when_empty(tmp_path: Path) -> None:
|
|
482
|
+
# An empty PATH makes adb/xcrun unresolvable, so every list_* call returns [].
|
|
483
|
+
env = {**os.environ, "PATH": str(tmp_path)}
|
|
484
|
+
result = run_pn(["devices"], str(tmp_path), env=env)
|
|
485
|
+
|
|
486
|
+
assert result.returncode == 1
|
|
487
|
+
assert result.stdout == _NO_DEVICES_HINTS
|
|
488
|
+
assert result.stderr == ""
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def test_devices_human_output_unchanged_when_populated(
|
|
492
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
493
|
+
) -> None:
|
|
494
|
+
monkeypatch.setattr(pn_cli.devices_mod, "list_devices", _fake_list_devices(_FAKE_DEVICES))
|
|
495
|
+
|
|
496
|
+
pn_cli.devices_command(argparse.Namespace(platform=None, json=False))
|
|
497
|
+
|
|
498
|
+
captured = capsys.readouterr()
|
|
499
|
+
assert captured.out == _DEVICE_TABLE
|
|
500
|
+
assert captured.err == ""
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def test_devices_json_flag_is_wired_through_argparse(tmp_path: Path) -> None:
|
|
504
|
+
# The other JSON tests call devices_command() directly, so only this one
|
|
505
|
+
# proves the subparser actually accepts --json and routes it through.
|
|
506
|
+
env = {**os.environ, "PATH": str(tmp_path)}
|
|
507
|
+
result = run_pn(["devices", "--json"], str(tmp_path), env=env)
|
|
508
|
+
|
|
509
|
+
assert result.returncode == 0
|
|
510
|
+
assert json.loads(result.stdout) == []
|
|
511
|
+
assert result.stdout == "[]\n"
|
|
512
|
+
assert result.stderr == _NO_DEVICES_HINTS
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def test_devices_json_serializes_awkward_field_values(
|
|
516
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
517
|
+
) -> None:
|
|
518
|
+
# Quotes, a newline, non-ASCII, and the two empty-string defaults all survive
|
|
519
|
+
# the round trip today; nothing pinned them before.
|
|
520
|
+
awkward = Device("android", "device", "SER!@#123", 'M\u00e1laga "Pixel"\nLab', "", "")
|
|
521
|
+
monkeypatch.setattr(pn_cli.devices_mod, "list_devices", _fake_list_devices([awkward]))
|
|
522
|
+
|
|
523
|
+
pn_cli.devices_command(argparse.Namespace(platform=None, json=True))
|
|
524
|
+
|
|
525
|
+
(entry,) = json.loads(capsys.readouterr().out)
|
|
526
|
+
assert entry == {
|
|
527
|
+
"platform": "android",
|
|
528
|
+
"kind": "device",
|
|
529
|
+
"identifier": "SER!@#123",
|
|
530
|
+
"name": 'M\u00e1laga "Pixel"\nLab',
|
|
531
|
+
"os_version": "",
|
|
532
|
+
"state": "",
|
|
533
|
+
"is_ready": False,
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
342
537
|
def test_hot_reload_manifest_payload_maps_files_to_modules(tmp_path: Path) -> None:
|
|
343
538
|
app_dir = tmp_path / "app"
|
|
344
539
|
app_dir.mkdir()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/notifications.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/build.gradle
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradlew
RENAMED
|
File without changes
|
{pythonnative-0.30.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradlew.bat
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|