pythonnative 0.30.0__tar.gz → 0.32.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.
Files changed (171) hide show
  1. {pythonnative-0.30.0/src/pythonnative.egg-info → pythonnative-0.32.0}/PKG-INFO +1 -1
  2. {pythonnative-0.30.0 → pythonnative-0.32.0}/pyproject.toml +1 -1
  3. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/__init__.py +1 -1
  4. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/cli/pn.py +95 -10
  5. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/platform_metrics.py +8 -5
  6. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/config.py +47 -0
  7. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/devices.py +14 -1
  8. {pythonnative-0.30.0 → pythonnative-0.32.0/src/pythonnative.egg-info}/PKG-INFO +1 -1
  9. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_cli.py +300 -19
  10. {pythonnative-0.30.0 → pythonnative-0.32.0}/LICENSE +0 -0
  11. {pythonnative-0.30.0 → pythonnative-0.32.0}/README.md +0 -0
  12. {pythonnative-0.30.0 → pythonnative-0.32.0}/setup.cfg +0 -0
  13. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/_ios_log.py +0 -0
  14. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/alerts.py +0 -0
  15. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/animated.py +0 -0
  16. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/appearance.py +0 -0
  17. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/cli/__init__.py +0 -0
  18. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/components.py +0 -0
  19. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/diagnostics.py +0 -0
  20. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/element.py +0 -0
  21. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/events.py +0 -0
  22. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/gestures.py +0 -0
  23. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/hooks.py +0 -0
  24. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/hot_reload.py +0 -0
  25. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/images.py +0 -0
  26. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/layout.py +0 -0
  27. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/mutations.py +0 -0
  28. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/__init__.py +0 -0
  29. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/app_state.py +0 -0
  30. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/battery.py +0 -0
  31. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/biometrics.py +0 -0
  32. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/camera.py +0 -0
  33. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/clipboard.py +0 -0
  34. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/file_system.py +0 -0
  35. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/haptics.py +0 -0
  36. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/linking.py +0 -0
  37. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/location.py +0 -0
  38. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/net_info.py +0 -0
  39. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/notifications.py +0 -0
  40. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/permissions.py +0 -0
  41. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/secure_store.py +0 -0
  42. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/share.py +0 -0
  43. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/__init__.py +0 -0
  44. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/android.py +0 -0
  45. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/base.py +0 -0
  46. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/desktop.py +0 -0
  47. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/ios.py +0 -0
  48. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/navigation.py +0 -0
  49. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/net.py +0 -0
  50. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/platform.py +0 -0
  51. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/preview.py +0 -0
  52. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/__init__.py +0 -0
  53. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/android.py +0 -0
  54. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/builder.py +0 -0
  55. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/doctor.py +0 -0
  56. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/icons.py +0 -0
  57. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/ios.py +0 -0
  58. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/permissions.py +0 -0
  59. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/runtime_assets.py +0 -0
  60. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/reconciler.py +0 -0
  61. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/runtime.py +0 -0
  62. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/screen.py +0 -0
  63. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/sdk/__init__.py +0 -0
  64. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/sdk/_components.py +0 -0
  65. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/storage.py +0 -0
  66. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/style.py +0 -0
  67. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/suspense.py +0 -0
  68. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
  69. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  70. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  71. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
  72. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
  73. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
  74. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
  75. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
  76. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +0 -0
  77. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
  78. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
  79. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  80. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  81. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
  82. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  83. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  84. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  85. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  86. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  87. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  88. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  89. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  90. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  91. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  92. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  93. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  94. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
  95. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  96. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  97. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  98. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  99. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  100. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  101. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  102. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
  103. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  104. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
  105. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  106. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  107. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  108. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  109. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
  110. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  111. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  112. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  113. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  114. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +0 -0
  115. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
  116. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +0 -0
  117. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
  118. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
  119. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
  120. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  121. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  122. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  123. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  124. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/utils.py +0 -0
  125. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/virtual_rows.py +0 -0
  126. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/SOURCES.txt +0 -0
  127. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  128. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  129. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/requires.txt +0 -0
  130. {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/top_level.txt +0 -0
  131. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_alert.py +0 -0
  132. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_animated.py +0 -0
  133. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_animated_graph.py +0 -0
  134. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_appearance.py +0 -0
  135. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_async_hooks.py +0 -0
  136. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_components.py +0 -0
  137. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_core_semantics.py +0 -0
  138. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_desktop_backend.py +0 -0
  139. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_element.py +0 -0
  140. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_events.py +0 -0
  141. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_extended_components.py +0 -0
  142. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_gesture_composition.py +0 -0
  143. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_gestures.py +0 -0
  144. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_hooks.py +0 -0
  145. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_hot_reload.py +0 -0
  146. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_images.py +0 -0
  147. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_incremental_render.py +0 -0
  148. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_interaction_props.py +0 -0
  149. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_ios_log.py +0 -0
  150. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_layout.py +0 -0
  151. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_metric_hooks.py +0 -0
  152. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_mutations.py +0 -0
  153. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_native_lists.py +0 -0
  154. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_native_modules.py +0 -0
  155. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_native_views.py +0 -0
  156. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_navigation.py +0 -0
  157. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_net.py +0 -0
  158. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_new_components.py +0 -0
  159. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_platform.py +0 -0
  160. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_platform_metrics.py +0 -0
  161. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_reconciler.py +0 -0
  162. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_ref.py +0 -0
  163. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_runtime.py +0 -0
  164. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_screen.py +0 -0
  165. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_sdk.py +0 -0
  166. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_smoke.py +0 -0
  167. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_storage.py +0 -0
  168. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_style.py +0 -0
  169. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_suspense.py +0 -0
  170. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_transitions.py +0 -0
  171. {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.30.0
3
+ Version: 0.32.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pythonnative"
7
- version = "0.30.0"
7
+ version = "0.32.0"
8
8
  description = "Cross-platform native UI toolkit for Android and iOS"
9
9
  authors = [
10
10
  { name = "Owen Carey" }
@@ -60,7 +60,7 @@ Example:
60
60
  ```
61
61
  """
62
62
 
63
- __version__ = "0.30.0"
63
+ __version__ = "0.32.0"
64
64
 
65
65
  from . import appearance, diagnostics, gestures, images, runtime, sdk
66
66
  from .alerts import Alert
@@ -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
@@ -98,6 +98,35 @@ def App():
98
98
  _GITIGNORE = "# PythonNative\n__pycache__/\n*.pyc\n.venv/\nbuild/\n.DS_Store\n"
99
99
 
100
100
 
101
+ _NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
102
+ """Legal ``pn init`` project names, in the spirit of ``flutter create`` / ``cargo new``."""
103
+
104
+ _FALLBACK_NAME = "my_app"
105
+
106
+
107
+ def _sanitize_name(name: str) -> str:
108
+ """Return a legal project name derived from ``name``.
109
+
110
+ Lowercases, collapses each run of illegal characters to one
111
+ underscore, trims leading and trailing ``_`` and ``-``, and prefixes
112
+ a name that doesn't start with a letter. The result always matches
113
+ ``_NAME_RE``, falling back to ``_FALLBACK_NAME`` when nothing usable
114
+ survives.
115
+
116
+ Args:
117
+ name: The rejected name, which may be empty.
118
+
119
+ Returns:
120
+ A name suitable for suggesting back to the user.
121
+ """
122
+ slug = re.sub(r"[^a-z0-9_-]+", "_", name.lower()).strip("_-")
123
+ if not slug:
124
+ return _FALLBACK_NAME
125
+ if not slug[0].isascii() or not slug[0].isalpha():
126
+ slug = f"app_{slug}"
127
+ return slug
128
+
129
+
101
130
  def _app_id_from_name(name: str) -> str:
102
131
  slug = re.sub(r"[^a-z0-9_]", "", name.lower())
103
132
  if not slug or not slug[0].isalpha():
@@ -113,9 +142,17 @@ def init_project(args: argparse.Namespace) -> None:
113
142
  it. Either way it writes ``app/main.py``, ``pythonnative.toml``, and
114
143
  ``.gitignore``.
115
144
 
116
- The name has to be a single directory name, so the project always lands
117
- inside the current directory. Anything that reads as a path, such as
118
- ``a/b``, ``..``, or ``/tmp/app``, is refused, and so is a name that
145
+ A name you pass has to match ``^[a-z][a-z0-9_-]*$``: lowercase letters,
146
+ digits, ``-``, and ``_``, starting with a letter. Anything else is
147
+ refused with a legal suggestion. That keeps the directory name and the
148
+ ``name`` field in the generated config identical, in the same spirit as
149
+ ``flutter create`` and ``cargo new``. The name taken from the current
150
+ directory when you pass none is used as-is, so an existing directory
151
+ with any name still works.
152
+
153
+ The name also has to be a single directory name, so the project always
154
+ lands inside the current directory. Anything that reads as a path, such
155
+ as ``a/b``, ``..``, or ``/tmp/app``, is refused, and so is a name that
119
156
  resolves somewhere else, such as a symlink to another directory.
120
157
 
121
158
  It won't scaffold into a target directory that already holds files, and it
@@ -137,6 +174,19 @@ def init_project(args: argparse.Namespace) -> None:
137
174
  print(f"Refusing to treat a path as a project name: {name!r}. Use a single directory name like my_app.")
138
175
  sys.exit(1)
139
176
 
177
+ # Charset check, still lexical, so it stays ahead of ``Path.cwd()`` below.
178
+ # ``is not None`` rather than truthiness: "" is invalid under the pattern,
179
+ # and falling through to the no-name path would silently scaffold here.
180
+ # ``fullmatch``, not ``match``: ``$`` also matches before a trailing
181
+ # newline, so ``match`` would accept "app\n" and create a directory
182
+ # whose name contains one.
183
+ if name is not None and not _NAME_RE.fullmatch(name):
184
+ print(
185
+ f"Invalid project name: {name!r}. Use lowercase letters, digits, '-', and '_', "
186
+ f"starting with a letter. Try: {_sanitize_name(name)}"
187
+ )
188
+ sys.exit(1)
189
+
140
190
  cwd = Path.cwd()
141
191
  target = cwd / name if name else cwd
142
192
  project_name: str = name or cwd.name
@@ -298,18 +348,46 @@ def _preview_entry(project_dir: Path) -> str:
298
348
  # ======================================================================
299
349
 
300
350
 
351
+ def _print_no_devices_hints(stream: TextIO) -> None:
352
+ """Print the "no devices" guidance to ``stream``.
353
+
354
+ Shared by both output modes so the wording can't drift between the
355
+ table (which sends it to stdout) and ``--json`` (stderr).
356
+
357
+ Args:
358
+ stream: Where to write, ``sys.stdout`` or ``sys.stderr``.
359
+ """
360
+ print("No devices found.", file=stream)
361
+ print("Android: start an emulator or connect a device with USB debugging enabled.", file=stream)
362
+ print("iOS: open Xcode once to install Simulators, or plug in a device.", file=stream)
363
+
364
+
301
365
  def devices_command(args: argparse.Namespace) -> None:
302
366
  """List connected devices, emulators, and simulators.
303
367
 
368
+ Prints an aligned table and exits 1 when nothing is connected.
369
+
370
+ With ``--json``, stdout carries a JSON array and nothing else, one
371
+ object per device (see ``Device.to_dict``), so it stays parseable.
372
+ The "no devices" hints go to stderr instead, an empty result prints
373
+ ``[]``, and the exit status is 0 either way, since "no devices" is a
374
+ valid answer for a script rather than a failure.
375
+
304
376
  Args:
305
- args: Parsed namespace with optional ``platform``.
377
+ args: Parsed namespace with optional ``platform`` and ``json``.
306
378
  """
307
379
  platform: Optional[str] = getattr(args, "platform", None)
380
+ as_json: bool = getattr(args, "json", False)
308
381
  devices = devices_mod.list_devices(platform)
382
+
383
+ if as_json:
384
+ if not devices:
385
+ _print_no_devices_hints(sys.stderr)
386
+ print(json.dumps([device.to_dict() for device in devices], indent=2))
387
+ return
388
+
309
389
  if not devices:
310
- print("No devices found.")
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.")
390
+ _print_no_devices_hints(sys.stdout)
313
391
  sys.exit(1)
314
392
  print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
315
393
  for device in devices:
@@ -919,7 +997,11 @@ def _build_parser() -> argparse.ArgumentParser:
919
997
  subparsers = parser.add_subparsers()
920
998
 
921
999
  parser_init = subparsers.add_parser("init", help="Scaffold a new project")
922
- parser_init.add_argument("name", nargs="?", help="Project name; creates ./<name>/ (default: current directory)")
1000
+ parser_init.add_argument(
1001
+ "name",
1002
+ nargs="?",
1003
+ help="Project name, matching ^[a-z][a-z0-9_-]*$; creates ./<name>/ (default: current directory)",
1004
+ )
923
1005
  parser_init.add_argument("--force", action="store_true", help="Overwrite existing files or a non-empty directory")
924
1006
  parser_init.set_defaults(func=init_project)
925
1007
 
@@ -943,6 +1025,9 @@ def _build_parser() -> argparse.ArgumentParser:
943
1025
 
944
1026
  parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
945
1027
  parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
1028
+ parser_devices.add_argument(
1029
+ "--json", action="store_true", help="Print a JSON array to stdout for scripting (hints go to stderr)"
1030
+ )
946
1031
  parser_devices.set_defaults(func=devices_command)
947
1032
 
948
1033
  parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
@@ -226,12 +226,15 @@ def reset_keyboard_height() -> None:
226
226
  # that natural height. Forcing our own height threw off the pill
227
227
  # geometry, so the Android handler defers entirely to the system.
228
228
 
229
- #: UIKit HIG tab-bar content height in points. The total bar reaches
230
- #: ``IOS_TAB_BAR_BASE_HEIGHT_PT + safe_area_insets.bottom`` so the
231
- #: pill background can extend over the home indicator. Apple's HIG
232
- #: places the tab bar flush with the screen edge and lets UIKit
233
- #: render its own internal padding for the home indicator.
234
229
  IOS_TAB_BAR_BASE_HEIGHT_PT: float = 49.0
230
+ """UIKit HIG tab-bar content height in points.
231
+
232
+ The total bar reaches ``IOS_TAB_BAR_BASE_HEIGHT_PT +
233
+ safe_area_insets.bottom`` so the pill background can extend over the
234
+ home indicator. Apple's HIG places the tab bar flush with the screen
235
+ edge and lets UIKit render its own internal padding for the home
236
+ indicator.
237
+ """
235
238
 
236
239
 
237
240
  def ios_tab_bar_height() -> float:
@@ -586,9 +586,52 @@ def entrypoint_to_module(entry_point: str) -> str:
586
586
  return normalized or "app.main"
587
587
 
588
588
 
589
+ # TOML v1.0.0 basic strings must escape the quotation mark, the backslash,
590
+ # and every control character except tab: U+0000-U+0008, U+000A-U+001F, and
591
+ # U+007F. Tab is legal raw, and U+000B has no compact escape, so anything
592
+ # without one falls through to \uXXXX.
593
+ _TOML_COMPACT_ESCAPES = {
594
+ "\\": "\\\\",
595
+ '"': '\\"',
596
+ "\b": "\\b",
597
+ "\f": "\\f",
598
+ "\n": "\\n",
599
+ "\r": "\\r",
600
+ }
601
+
602
+
603
+ def _toml_escape(value: str) -> str:
604
+ """Escape ``value`` for use inside a TOML basic string.
605
+
606
+ Applied at the render boundary rather than relying on the caller,
607
+ since this module is public API and reachable without ``pn init``'s
608
+ name validation in front of it.
609
+
610
+ Args:
611
+ value: The raw string to embed between double quotes.
612
+
613
+ Returns:
614
+ The escaped text, without the surrounding quotes.
615
+ """
616
+ out = []
617
+ for char in value:
618
+ escaped = _TOML_COMPACT_ESCAPES.get(char)
619
+ if escaped is not None:
620
+ out.append(escaped)
621
+ elif char != "\t" and (char < "\x20" or char == "\x7f"):
622
+ out.append(f"\\u{ord(char):04X}")
623
+ else:
624
+ out.append(char)
625
+ return "".join(out)
626
+
627
+
589
628
  def render_default_toml(*, name: str, app_id: str, python_version: str = "3.11") -> str:
590
629
  """Render a starter ``pythonnative.toml`` for ``pn init``.
591
630
 
631
+ Every interpolated value is escaped for a TOML basic string, so a
632
+ name containing a quote, a backslash, or a control character still
633
+ produces a parseable file.
634
+
592
635
  Args:
593
636
  name: Project name.
594
637
  app_id: Reverse-DNS app identifier.
@@ -599,6 +642,10 @@ def render_default_toml(*, name: str, app_id: str, python_version: str = "3.11")
599
642
  for the optional tables.
600
643
  """
601
644
  display = name.replace("_", " ").replace("-", " ").strip().title() or name
645
+ name = _toml_escape(name)
646
+ display = _toml_escape(display)
647
+ app_id = _toml_escape(app_id)
648
+ python_version = _toml_escape(python_version)
602
649
  return f"""# PythonNative project configuration.
603
650
  # Docs: https://pythonnative.com/guides/configuration/
604
651
 
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.30.0
3
+ Version: 0.32.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License