pythonnative 0.29.0__tar.gz → 0.30.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.29.0/src/pythonnative.egg-info → pythonnative-0.30.0}/PKG-INFO +1 -1
  2. {pythonnative-0.29.0 → pythonnative-0.30.0}/pyproject.toml +1 -1
  3. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/__init__.py +1 -1
  4. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/cli/pn.py +59 -13
  5. {pythonnative-0.29.0 → pythonnative-0.30.0/src/pythonnative.egg-info}/PKG-INFO +1 -1
  6. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_cli.py +145 -12
  7. {pythonnative-0.29.0 → pythonnative-0.30.0}/LICENSE +0 -0
  8. {pythonnative-0.29.0 → pythonnative-0.30.0}/README.md +0 -0
  9. {pythonnative-0.29.0 → pythonnative-0.30.0}/setup.cfg +0 -0
  10. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/_ios_log.py +0 -0
  11. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/alerts.py +0 -0
  12. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/animated.py +0 -0
  13. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/appearance.py +0 -0
  14. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/cli/__init__.py +0 -0
  15. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/components.py +0 -0
  16. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/diagnostics.py +0 -0
  17. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/element.py +0 -0
  18. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/events.py +0 -0
  19. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/gestures.py +0 -0
  20. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/hooks.py +0 -0
  21. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/hot_reload.py +0 -0
  22. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/images.py +0 -0
  23. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/layout.py +0 -0
  24. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/mutations.py +0 -0
  25. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/__init__.py +0 -0
  26. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/app_state.py +0 -0
  27. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/battery.py +0 -0
  28. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/biometrics.py +0 -0
  29. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/camera.py +0 -0
  30. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/clipboard.py +0 -0
  31. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/file_system.py +0 -0
  32. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/haptics.py +0 -0
  33. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/linking.py +0 -0
  34. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/location.py +0 -0
  35. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/net_info.py +0 -0
  36. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/notifications.py +0 -0
  37. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/permissions.py +0 -0
  38. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/secure_store.py +0 -0
  39. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_modules/share.py +0 -0
  40. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_views/__init__.py +0 -0
  41. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_views/android.py +0 -0
  42. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_views/base.py +0 -0
  43. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_views/desktop.py +0 -0
  44. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/native_views/ios.py +0 -0
  45. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/navigation.py +0 -0
  46. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/net.py +0 -0
  47. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/platform.py +0 -0
  48. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/platform_metrics.py +0 -0
  49. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/preview.py +0 -0
  50. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/__init__.py +0 -0
  51. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/android.py +0 -0
  52. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/builder.py +0 -0
  53. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/config.py +0 -0
  54. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/devices.py +0 -0
  55. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/doctor.py +0 -0
  56. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/icons.py +0 -0
  57. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/ios.py +0 -0
  58. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/permissions.py +0 -0
  59. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/project/runtime_assets.py +0 -0
  60. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/reconciler.py +0 -0
  61. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/runtime.py +0 -0
  62. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/screen.py +0 -0
  63. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/sdk/__init__.py +0 -0
  64. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/sdk/_components.py +0 -0
  65. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/storage.py +0 -0
  66. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/style.py +0 -0
  67. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/suspense.py +0 -0
  68. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
  69. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  70. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  71. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
  72. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
  73. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
  74. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
  75. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
  76. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +0 -0
  77. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
  78. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
  79. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  80. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  81. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
  82. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  83. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  84. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  85. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  86. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  87. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  88. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  89. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  90. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  91. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  92. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  93. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  94. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
  95. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  96. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  97. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  98. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  99. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  100. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  101. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  102. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
  103. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  104. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
  105. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  106. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  107. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  108. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  109. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
  110. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  111. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  112. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  113. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  114. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +0 -0
  115. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
  116. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +0 -0
  117. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
  118. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
  119. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
  120. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  121. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  122. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  123. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  124. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/utils.py +0 -0
  125. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative/virtual_rows.py +0 -0
  126. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative.egg-info/SOURCES.txt +0 -0
  127. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  128. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  129. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative.egg-info/requires.txt +0 -0
  130. {pythonnative-0.29.0 → pythonnative-0.30.0}/src/pythonnative.egg-info/top_level.txt +0 -0
  131. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_alert.py +0 -0
  132. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_animated.py +0 -0
  133. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_animated_graph.py +0 -0
  134. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_appearance.py +0 -0
  135. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_async_hooks.py +0 -0
  136. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_components.py +0 -0
  137. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_core_semantics.py +0 -0
  138. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_desktop_backend.py +0 -0
  139. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_element.py +0 -0
  140. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_events.py +0 -0
  141. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_extended_components.py +0 -0
  142. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_gesture_composition.py +0 -0
  143. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_gestures.py +0 -0
  144. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_hooks.py +0 -0
  145. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_hot_reload.py +0 -0
  146. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_images.py +0 -0
  147. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_incremental_render.py +0 -0
  148. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_interaction_props.py +0 -0
  149. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_ios_log.py +0 -0
  150. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_layout.py +0 -0
  151. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_metric_hooks.py +0 -0
  152. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_mutations.py +0 -0
  153. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_native_lists.py +0 -0
  154. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_native_modules.py +0 -0
  155. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_native_views.py +0 -0
  156. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_navigation.py +0 -0
  157. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_net.py +0 -0
  158. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_new_components.py +0 -0
  159. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_platform.py +0 -0
  160. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_platform_metrics.py +0 -0
  161. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_reconciler.py +0 -0
  162. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_ref.py +0 -0
  163. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_runtime.py +0 -0
  164. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_screen.py +0 -0
  165. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_sdk.py +0 -0
  166. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_smoke.py +0 -0
  167. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_storage.py +0 -0
  168. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_style.py +0 -0
  169. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_suspense.py +0 -0
  170. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_transitions.py +0 -0
  171. {pythonnative-0.29.0 → pythonnative-0.30.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.29.0
3
+ Version: 0.30.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.29.0"
7
+ version = "0.30.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.29.0"
63
+ __version__ = "0.30.0"
64
64
 
65
65
  from . import appearance, diagnostics, gestures, images, runtime, sdk
66
66
  from .alerts import Alert
@@ -2,7 +2,9 @@
2
2
 
3
3
  The console script `pn` (declared in `pyproject.toml`) dispatches to:
4
4
 
5
- - `pn init [name]`: scaffold a new project (``pythonnative.toml`` + ``app/``).
5
+ - `pn init [name]`: scaffold a new project (``pythonnative.toml`` +
6
+ ``app/``) into ``./name/``, or into the current directory when no name
7
+ is given.
6
8
  - `pn doctor [platform]`: diagnose the local toolchain and config.
7
9
  - `pn preview [component]`: render the app in a desktop (Tkinter) window
8
10
  with Fast Refresh, the fast inner dev loop, no device required.
@@ -104,21 +106,62 @@ def _app_id_from_name(name: str) -> str:
104
106
 
105
107
 
106
108
  def init_project(args: argparse.Namespace) -> None:
107
- """Scaffold a new PythonNative project in the current directory.
109
+ """Scaffold a new PythonNative project.
108
110
 
109
- Creates ``app/main.py``, ``pythonnative.toml``, and ``.gitignore``.
110
- Refuses to overwrite existing files unless ``--force`` is passed.
111
+ Given a name, this creates ``./<name>/`` and scaffolds into it. Without
112
+ one, it scaffolds into the current directory and names the project after
113
+ it. Either way it writes ``app/main.py``, ``pythonnative.toml``, and
114
+ ``.gitignore``.
115
+
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
119
+ resolves somewhere else, such as a symlink to another directory.
120
+
121
+ It won't scaffold into a target directory that already holds files, and it
122
+ won't overwrite any of the three paths above; pass ``--force`` to override
123
+ both. An existing but empty target directory is fine. A plain file at
124
+ ``./<name>`` is always refused, since ``--force`` can't turn it into a
125
+ directory, and ``--force`` lifts neither of the rules above.
111
126
 
112
127
  Args:
113
128
  args: Parsed namespace with ``name`` (optional) and ``force``.
114
129
  """
115
- cwd = Path.cwd()
116
- project_name: str = getattr(args, "name", None) or cwd.name
130
+ name: Optional[str] = getattr(args, "name", None)
117
131
  force: bool = getattr(args, "force", False)
118
132
 
119
- app_dir = cwd / "app"
120
- config_path = cwd / CONFIG_FILENAME
121
- gitignore_path = cwd / ".gitignore"
133
+ # Lexical check, before anything reads the filesystem. ``Path("..").name``
134
+ # is "..", so ".." needs naming explicitly; the rest (absolute, nested,
135
+ # trailing separator, ".") fall out of the name check.
136
+ if name and (name in (os.curdir, os.pardir) or Path(name).name != name):
137
+ print(f"Refusing to treat a path as a project name: {name!r}. Use a single directory name like my_app.")
138
+ sys.exit(1)
139
+
140
+ cwd = Path.cwd()
141
+ target = cwd / name if name else cwd
142
+ project_name: str = name or cwd.name
143
+
144
+ # A lexically clean name can still resolve elsewhere, and ``exists()`` and
145
+ # ``is_dir()`` below follow symlinks. Check containment rather than just
146
+ # ``is_symlink()`` so the whole class is closed, not one spelling of it.
147
+ if name and (target.is_symlink() or target.resolve().parent != cwd.resolve()):
148
+ print(
149
+ f"Refusing to scaffold through a link or outside the current directory: {name}. "
150
+ "Use a plain directory name."
151
+ )
152
+ sys.exit(1)
153
+
154
+ app_dir = target / "app"
155
+ config_path = target / CONFIG_FILENAME
156
+ gitignore_path = target / ".gitignore"
157
+
158
+ if name and target.exists():
159
+ if not target.is_dir():
160
+ print(f"Refusing to overwrite existing file: {name}. Remove it or choose a different name.")
161
+ sys.exit(1)
162
+ if any(target.iterdir()) and not force:
163
+ print(f"Refusing to overwrite existing non-empty directory: {name}/. Use --force to overwrite.")
164
+ sys.exit(1)
122
165
 
123
166
  if not force:
124
167
  existing = [
@@ -142,8 +185,11 @@ def init_project(args: argparse.Namespace) -> None:
142
185
  if force or not gitignore_path.exists():
143
186
  gitignore_path.write_text(_GITIGNORE, encoding="utf-8")
144
187
 
145
- print(f"Initialized PythonNative project in {cwd}.")
146
- print("Next: pn preview (desktop) | pn run android | pn run ios")
188
+ print(f"Initialized PythonNative project in {target}.")
189
+ next_steps = "pn preview (desktop) | pn run android | pn run ios"
190
+ if name:
191
+ next_steps = f"cd {name} | {next_steps}"
192
+ print(f"Next: {next_steps}")
147
193
 
148
194
 
149
195
  # ======================================================================
@@ -873,8 +919,8 @@ def _build_parser() -> argparse.ArgumentParser:
873
919
  subparsers = parser.add_subparsers()
874
920
 
875
921
  parser_init = subparsers.add_parser("init", help="Scaffold a new project")
876
- parser_init.add_argument("name", nargs="?", help="Project name (defaults to current directory name)")
877
- parser_init.add_argument("--force", action="store_true", help="Overwrite existing files if present")
922
+ parser_init.add_argument("name", nargs="?", help="Project name; creates ./<name>/ (default: current directory)")
923
+ parser_init.add_argument("--force", action="store_true", help="Overwrite existing files or a non-empty directory")
878
924
  parser_init.set_defaults(func=init_project)
879
925
 
880
926
  parser_doctor = subparsers.add_parser("doctor", help="Diagnose the local toolchain and config")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.29.0
3
+ Version: 0.30.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -36,21 +36,22 @@ def test_cli_init_and_clean() -> None:
36
36
  try:
37
37
  result = run_pn(["init", "MyApp"], tmpdir)
38
38
  assert result.returncode == 0, result.stderr
39
- assert os.path.isdir(os.path.join(tmpdir, "app"))
39
+ project_dir = os.path.join(tmpdir, "MyApp")
40
+ assert os.path.isdir(os.path.join(project_dir, "app"))
40
41
 
41
- main_path = os.path.join(tmpdir, "app", "main.py")
42
+ main_path = os.path.join(project_dir, "app", "main.py")
42
43
  assert os.path.isfile(main_path)
43
44
  content = Path(main_path).read_text(encoding="utf-8")
44
45
  assert "def App(" in content
45
46
  assert "Stack.Navigator" in content
46
47
 
47
- config_path = os.path.join(tmpdir, "pythonnative.toml")
48
+ config_path = os.path.join(project_dir, "pythonnative.toml")
48
49
  assert os.path.isfile(config_path)
49
50
  toml_text = Path(config_path).read_text(encoding="utf-8")
50
51
  assert 'id = "com.example.myapp"' in toml_text
51
- assert os.path.isfile(os.path.join(tmpdir, ".gitignore"))
52
+ assert os.path.isfile(os.path.join(project_dir, ".gitignore"))
52
53
  # The legacy JSON config and requirements.txt are no longer scaffolded.
53
- assert not os.path.exists(os.path.join(tmpdir, "pythonnative.json"))
54
+ assert not os.path.exists(os.path.join(project_dir, "pythonnative.json"))
54
55
 
55
56
  # clean on empty build is a no-op
56
57
  result = run_pn(["clean"], tmpdir)
@@ -76,6 +77,136 @@ def test_cli_init_refuses_overwrite() -> None:
76
77
  shutil.rmtree(tmpdir, ignore_errors=True)
77
78
 
78
79
 
80
+ def test_cli_init_creates_named_directory(tmp_path: Path) -> None:
81
+ result = run_pn(["init", "my_app"], str(tmp_path))
82
+ assert result.returncode == 0, result.stderr
83
+ assert "cd my_app" in result.stdout
84
+
85
+ project_dir = os.path.join(str(tmp_path), "my_app")
86
+ assert os.path.isfile(os.path.join(project_dir, "app", "main.py"))
87
+ assert os.path.isfile(os.path.join(project_dir, "pythonnative.toml"))
88
+ assert os.path.isfile(os.path.join(project_dir, ".gitignore"))
89
+ toml_text = Path(os.path.join(project_dir, "pythonnative.toml")).read_text(encoding="utf-8")
90
+ assert 'id = "com.example.my_app"' in toml_text
91
+ # Nothing is scaffolded beside the project directory.
92
+ assert os.listdir(str(tmp_path)) == ["my_app"]
93
+
94
+
95
+ def test_cli_init_without_name_uses_cwd(tmp_path: Path) -> None:
96
+ project_dir = tmp_path / "widgets"
97
+ project_dir.mkdir()
98
+
99
+ result = run_pn(["init"], str(project_dir))
100
+ assert result.returncode == 0, result.stderr
101
+ assert "cd " not in result.stdout
102
+
103
+ assert os.path.isfile(os.path.join(str(project_dir), "app", "main.py"))
104
+ assert os.path.isfile(os.path.join(str(project_dir), ".gitignore"))
105
+ toml_text = Path(os.path.join(str(project_dir), "pythonnative.toml")).read_text(encoding="utf-8")
106
+ assert 'id = "com.example.widgets"' in toml_text
107
+ assert 'name = "widgets"' in toml_text
108
+
109
+
110
+ def test_cli_init_refuses_non_empty_directory(tmp_path: Path) -> None:
111
+ project_dir = tmp_path / "my_app"
112
+ project_dir.mkdir()
113
+ keeper = project_dir / "README.md"
114
+ keeper.write_text("keep me\n", encoding="utf-8")
115
+
116
+ result = run_pn(["init", "my_app"], str(tmp_path))
117
+ assert result.returncode != 0
118
+ assert "Refusing to overwrite" in result.stdout
119
+ assert "non-empty directory" in result.stdout
120
+ assert not os.path.exists(os.path.join(str(project_dir), "app"))
121
+ assert keeper.read_text(encoding="utf-8") == "keep me\n"
122
+
123
+ result = run_pn(["init", "my_app", "--force"], str(tmp_path))
124
+ assert result.returncode == 0, result.stderr
125
+ assert os.path.isfile(os.path.join(str(project_dir), "app", "main.py"))
126
+ # --force scaffolds over the directory; it doesn't empty it first.
127
+ assert keeper.read_text(encoding="utf-8") == "keep me\n"
128
+
129
+
130
+ def test_cli_init_accepts_existing_empty_directory(tmp_path: Path) -> None:
131
+ project_dir = tmp_path / "my_app"
132
+ project_dir.mkdir()
133
+
134
+ result = run_pn(["init", "my_app"], str(tmp_path))
135
+ assert result.returncode == 0, result.stderr
136
+ assert os.path.isfile(os.path.join(str(project_dir), "app", "main.py"))
137
+ assert os.path.isfile(os.path.join(str(project_dir), "pythonnative.toml"))
138
+
139
+
140
+ def test_cli_init_refuses_existing_file(tmp_path: Path) -> None:
141
+ blocker = tmp_path / "my_app"
142
+ blocker.write_text("not a project\n", encoding="utf-8")
143
+
144
+ result = run_pn(["init", "my_app"], str(tmp_path))
145
+ assert result.returncode != 0
146
+ assert "Refusing to overwrite existing file" in result.stdout
147
+
148
+ # --force can't turn a file into a directory, so it is refused too.
149
+ result = run_pn(["init", "my_app", "--force"], str(tmp_path))
150
+ assert result.returncode != 0
151
+ assert "Refusing to overwrite existing file" in result.stdout
152
+ assert blocker.read_text(encoding="utf-8") == "not a project\n"
153
+
154
+
155
+ @pytest.mark.parametrize("name", ["{absolute}", "nested/my_app", "my_app/", ".", "..", "../", "a/.."])
156
+ def test_cli_init_rejects_path_like_names(tmp_path: Path, name: str) -> None:
157
+ work_dir = tmp_path / "work"
158
+ work_dir.mkdir()
159
+
160
+ result = run_pn(["init", name.format(absolute=str(tmp_path / "elsewhere"))], str(work_dir))
161
+ assert result.returncode != 0
162
+ assert "Refusing to" in result.stdout
163
+ assert "project name" in result.stdout
164
+ # Nothing was created in the working directory or anywhere above it.
165
+ assert os.listdir(str(work_dir)) == []
166
+ assert os.listdir(str(tmp_path)) == ["work"]
167
+
168
+
169
+ def test_cli_init_force_does_not_escape_to_parent(tmp_path: Path) -> None:
170
+ parent_config = tmp_path / "pythonnative.toml"
171
+ parent_config.write_text("# hand-written\n", encoding="utf-8")
172
+ work_dir = tmp_path / "work"
173
+ work_dir.mkdir()
174
+
175
+ result = run_pn(["init", "..", "--force"], str(work_dir))
176
+ assert result.returncode != 0
177
+ assert "Refusing to" in result.stdout
178
+ # --force does not lift the single-name rule, so the parent is untouched.
179
+ assert parent_config.read_text(encoding="utf-8") == "# hand-written\n"
180
+ assert not os.path.exists(os.path.join(str(tmp_path), "app"))
181
+ assert not os.path.exists(os.path.join(str(tmp_path), ".gitignore"))
182
+ assert os.listdir(str(work_dir)) == []
183
+
184
+
185
+ @pytest.mark.parametrize("extra_args", [[], ["--force"]])
186
+ @pytest.mark.parametrize("populated", [True, False])
187
+ def test_cli_init_rejects_symlinked_target(tmp_path: Path, populated: bool, extra_args: List[str]) -> None:
188
+ outside = tmp_path / "outside"
189
+ outside.mkdir()
190
+ outside_config = outside / "pythonnative.toml"
191
+ if populated:
192
+ outside_config.write_text("# hand-written\n", encoding="utf-8")
193
+ before = sorted(os.listdir(str(outside)))
194
+
195
+ work_dir = tmp_path / "work"
196
+ work_dir.mkdir()
197
+ os.symlink(str(outside), os.path.join(str(work_dir), "link"))
198
+
199
+ result = run_pn(["init", "link"] + extra_args, str(work_dir))
200
+ assert result.returncode != 0
201
+ assert "Refusing to" in result.stdout
202
+ # exists() and is_dir() follow symlinks, so the destination must stay untouched:
203
+ # no new entries, and no rewrite of a config that was already there.
204
+ assert sorted(os.listdir(str(outside))) == before
205
+ if populated:
206
+ assert outside_config.read_text(encoding="utf-8") == "# hand-written\n"
207
+ assert os.listdir(str(work_dir)) == ["link"]
208
+
209
+
79
210
  def test_cli_run_help_lists_flags() -> None:
80
211
  tmpdir = tempfile.mkdtemp(prefix="pn_cli_test_")
81
212
  try:
@@ -119,15 +250,16 @@ def test_cli_run_without_config_errors() -> None:
119
250
 
120
251
  def test_cli_app_id_resolves(tmp_path: Path) -> None:
121
252
  assert run_pn(["init", "MyApp"], str(tmp_path)).returncode == 0
122
- result = run_pn(["app-id", "android"], str(tmp_path))
253
+ project_dir = str(tmp_path / "MyApp")
254
+ result = run_pn(["app-id", "android"], project_dir)
123
255
  assert result.returncode == 0, result.stderr
124
256
  assert result.stdout.strip() == "com.example.myapp"
125
- assert run_pn(["app-id", "ios"], str(tmp_path)).stdout.strip() == "com.example.myapp"
257
+ assert run_pn(["app-id", "ios"], project_dir).stdout.strip() == "com.example.myapp"
126
258
 
127
259
 
128
260
  def test_cli_doctor_runs(tmp_path: Path) -> None:
129
261
  assert run_pn(["init", "MyApp"], str(tmp_path)).returncode == 0
130
- result = run_pn(["doctor", "android"], str(tmp_path))
262
+ result = run_pn(["doctor", "android"], str(tmp_path / "MyApp"))
131
263
  assert "PythonNative doctor" in result.stdout
132
264
  # android-only doctor on a CI box without adb still produces warnings, not errors.
133
265
  assert result.returncode in (0, 1)
@@ -137,10 +269,11 @@ def test_cli_run_prepare_only_android_and_ios() -> None:
137
269
  tmpdir = tempfile.mkdtemp(prefix="pn_cli_test_")
138
270
  try:
139
271
  assert run_pn(["init", "MyApp"], tmpdir).returncode == 0
272
+ project_dir = os.path.join(tmpdir, "MyApp")
140
273
 
141
- result = run_pn(["run", "android", "--prepare-only", "--no-logs"], tmpdir)
274
+ result = run_pn(["run", "android", "--prepare-only", "--no-logs"], project_dir)
142
275
  assert result.returncode == 0, result.stderr
143
- android_root = os.path.join(tmpdir, "build", "android", "android_template")
276
+ android_root = os.path.join(project_dir, "build", "android", "android_template")
144
277
  assert os.path.isdir(android_root)
145
278
  # Package relocated to the configured application id.
146
279
  relocated = os.path.join(
@@ -154,9 +287,9 @@ def test_cli_run_prepare_only_android_and_ios() -> None:
154
287
  gradle = Path(os.path.join(android_root, "app", "build.gradle")).read_text(encoding="utf-8")
155
288
  assert "com.example.myapp" in gradle
156
289
 
157
- result = run_pn(["run", "ios", "--prepare-only", "--no-logs"], tmpdir)
290
+ result = run_pn(["run", "ios", "--prepare-only", "--no-logs"], project_dir)
158
291
  assert result.returncode == 0, result.stderr
159
- ios_root = os.path.join(tmpdir, "build", "ios", "ios_template")
292
+ ios_root = os.path.join(project_dir, "build", "ios", "ios_template")
160
293
  assert os.path.isdir(ios_root)
161
294
  info_plist = Path(os.path.join(ios_root, "ios_template", "Info.plist")).read_bytes()
162
295
  assert b"CFBundleDisplayName" in info_plist
File without changes
File without changes
File without changes