pythonnative 0.5.0__tar.gz → 0.7.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.5.0/src/pythonnative.egg-info → pythonnative-0.7.0}/PKG-INFO +18 -38
- {pythonnative-0.5.0 → pythonnative-0.7.0}/README.md +16 -35
- {pythonnative-0.5.0 → pythonnative-0.7.0}/pyproject.toml +2 -3
- pythonnative-0.7.0/src/pythonnative/__init__.py +92 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/cli/pn.py +150 -30
- pythonnative-0.7.0/src/pythonnative/components.py +351 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/element.py +14 -8
- pythonnative-0.7.0/src/pythonnative/hooks.py +334 -0
- pythonnative-0.7.0/src/pythonnative/hot_reload.py +143 -0
- pythonnative-0.7.0/src/pythonnative/native_modules/__init__.py +19 -0
- pythonnative-0.7.0/src/pythonnative/native_modules/camera.py +105 -0
- pythonnative-0.7.0/src/pythonnative/native_modules/file_system.py +131 -0
- pythonnative-0.7.0/src/pythonnative/native_modules/location.py +61 -0
- pythonnative-0.7.0/src/pythonnative/native_modules/notifications.py +151 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/native_views.py +638 -34
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/page.py +138 -171
- pythonnative-0.7.0/src/pythonnative/reconciler.py +262 -0
- pythonnative-0.7.0/src/pythonnative/style.py +135 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/build.gradle +2 -7
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +2 -9
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/build.gradle +1 -1
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +7 -20
- {pythonnative-0.5.0 → pythonnative-0.7.0/src/pythonnative.egg-info}/PKG-INFO +18 -38
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/SOURCES.txt +11 -4
- {pythonnative-0.5.0 → pythonnative-0.7.0}/tests/test_cli.py +1 -1
- {pythonnative-0.5.0 → pythonnative-0.7.0}/tests/test_components.py +136 -10
- pythonnative-0.7.0/tests/test_hooks.py +465 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/tests/test_reconciler.py +99 -8
- {pythonnative-0.5.0 → pythonnative-0.7.0}/tests/test_smoke.py +22 -1
- pythonnative-0.7.0/tests/test_style.py +80 -0
- pythonnative-0.5.0/src/pythonnative/__init__.py +0 -54
- pythonnative-0.5.0/src/pythonnative/collection_view.py +0 -0
- pythonnative-0.5.0/src/pythonnative/components.py +0 -241
- pythonnative-0.5.0/src/pythonnative/material_bottom_navigation_view.py +0 -0
- pythonnative-0.5.0/src/pythonnative/material_toolbar.py +0 -0
- pythonnative-0.5.0/src/pythonnative/reconciler.py +0 -129
- {pythonnative-0.5.0 → pythonnative-0.7.0}/LICENSE +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/setup.cfg +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/cli/__init__.py +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradlew +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative/utils.py +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/requires.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/top_level.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.7.0}/tests/test_element.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonnative
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Cross-platform native UI toolkit for Android and iOS
|
|
5
5
|
Author: Owen Carey
|
|
6
6
|
License: MIT License
|
|
@@ -34,12 +34,11 @@ Classifier: Intended Audience :: Developers
|
|
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
|
35
35
|
Classifier: Programming Language :: Python :: 3
|
|
36
36
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
38
37
|
Classifier: Programming Language :: Python :: 3.10
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.12
|
|
41
40
|
Classifier: Topic :: Software Development :: User Interfaces
|
|
42
|
-
Requires-Python: >=3.
|
|
41
|
+
Requires-Python: >=3.10
|
|
43
42
|
Description-Content-Type: text/markdown
|
|
44
43
|
License-File: LICENSE
|
|
45
44
|
Requires-Dist: requests>=2.31.0
|
|
@@ -89,17 +88,18 @@ Dynamic: license-file
|
|
|
89
88
|
|
|
90
89
|
## Overview
|
|
91
90
|
|
|
92
|
-
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS.
|
|
91
|
+
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
|
|
93
92
|
|
|
94
93
|
## Features
|
|
95
94
|
|
|
96
95
|
- **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
|
|
97
|
-
- **
|
|
96
|
+
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
|
|
97
|
+
- **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`.
|
|
98
98
|
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
|
|
99
99
|
- **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
|
|
100
100
|
- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
|
|
101
|
-
- **Navigation:** Push and pop screens with argument passing
|
|
102
|
-
- **Bundled templates:** Android Gradle and iOS Xcode templates are included
|
|
101
|
+
- **Navigation:** Push and pop screens with argument passing via the `use_navigation()` hook.
|
|
102
|
+
- **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
|
|
103
103
|
|
|
104
104
|
## Quick Start
|
|
105
105
|
|
|
@@ -115,39 +115,19 @@ pip install pythonnative
|
|
|
115
115
|
import pythonnative as pn
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
),
|
|
130
|
-
spacing=12,
|
|
131
|
-
padding=16,
|
|
132
|
-
)
|
|
118
|
+
@pn.component
|
|
119
|
+
def MainPage():
|
|
120
|
+
count, set_count = pn.use_state(0)
|
|
121
|
+
return pn.Column(
|
|
122
|
+
pn.Text(f"Count: {count}", style={"font_size": 24}),
|
|
123
|
+
pn.Button(
|
|
124
|
+
"Tap me",
|
|
125
|
+
on_click=lambda: set_count(count + 1),
|
|
126
|
+
),
|
|
127
|
+
style={"spacing": 12, "padding": 16},
|
|
128
|
+
)
|
|
133
129
|
```
|
|
134
130
|
|
|
135
|
-
### Available Components
|
|
136
|
-
|
|
137
|
-
| Component | Description |
|
|
138
|
-
|---|---|
|
|
139
|
-
| `Text` | Display text |
|
|
140
|
-
| `Button` | Tappable button with `on_click` callback |
|
|
141
|
-
| `Column` / `Row` | Vertical / horizontal layout containers |
|
|
142
|
-
| `ScrollView` | Scrollable wrapper |
|
|
143
|
-
| `TextInput` | Text entry field with `on_change` callback |
|
|
144
|
-
| `Image` | Display images |
|
|
145
|
-
| `Switch` | Toggle with `on_change` callback |
|
|
146
|
-
| `ProgressBar` | Determinate progress (0.0–1.0) |
|
|
147
|
-
| `ActivityIndicator` | Indeterminate loading spinner |
|
|
148
|
-
| `WebView` | Embedded web content |
|
|
149
|
-
| `Spacer` | Empty space |
|
|
150
|
-
|
|
151
131
|
## Documentation
|
|
152
132
|
|
|
153
133
|
Visit [docs.pythonnative.com](https://docs.pythonnative.com/) for the full documentation, including getting started guides, platform-specific instructions for Android and iOS, API reference, and working examples.
|
|
@@ -26,17 +26,18 @@
|
|
|
26
26
|
|
|
27
27
|
## Overview
|
|
28
28
|
|
|
29
|
-
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS.
|
|
29
|
+
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
|
|
30
30
|
|
|
31
31
|
## Features
|
|
32
32
|
|
|
33
33
|
- **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
|
|
34
|
-
- **
|
|
34
|
+
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
|
|
35
|
+
- **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`.
|
|
35
36
|
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
|
|
36
37
|
- **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
|
|
37
38
|
- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
|
|
38
|
-
- **Navigation:** Push and pop screens with argument passing
|
|
39
|
-
- **Bundled templates:** Android Gradle and iOS Xcode templates are included
|
|
39
|
+
- **Navigation:** Push and pop screens with argument passing via the `use_navigation()` hook.
|
|
40
|
+
- **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
|
|
40
41
|
|
|
41
42
|
## Quick Start
|
|
42
43
|
|
|
@@ -52,39 +53,19 @@ pip install pythonnative
|
|
|
52
53
|
import pythonnative as pn
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
),
|
|
67
|
-
spacing=12,
|
|
68
|
-
padding=16,
|
|
69
|
-
)
|
|
56
|
+
@pn.component
|
|
57
|
+
def MainPage():
|
|
58
|
+
count, set_count = pn.use_state(0)
|
|
59
|
+
return pn.Column(
|
|
60
|
+
pn.Text(f"Count: {count}", style={"font_size": 24}),
|
|
61
|
+
pn.Button(
|
|
62
|
+
"Tap me",
|
|
63
|
+
on_click=lambda: set_count(count + 1),
|
|
64
|
+
),
|
|
65
|
+
style={"spacing": 12, "padding": 16},
|
|
66
|
+
)
|
|
70
67
|
```
|
|
71
68
|
|
|
72
|
-
### Available Components
|
|
73
|
-
|
|
74
|
-
| Component | Description |
|
|
75
|
-
|---|---|
|
|
76
|
-
| `Text` | Display text |
|
|
77
|
-
| `Button` | Tappable button with `on_click` callback |
|
|
78
|
-
| `Column` / `Row` | Vertical / horizontal layout containers |
|
|
79
|
-
| `ScrollView` | Scrollable wrapper |
|
|
80
|
-
| `TextInput` | Text entry field with `on_change` callback |
|
|
81
|
-
| `Image` | Display images |
|
|
82
|
-
| `Switch` | Toggle with `on_change` callback |
|
|
83
|
-
| `ProgressBar` | Determinate progress (0.0–1.0) |
|
|
84
|
-
| `ActivityIndicator` | Indeterminate loading spinner |
|
|
85
|
-
| `WebView` | Embedded web content |
|
|
86
|
-
| `Spacer` | Empty space |
|
|
87
|
-
|
|
88
69
|
## Documentation
|
|
89
70
|
|
|
90
71
|
Visit [docs.pythonnative.com](https://docs.pythonnative.com/) for the full documentation, including getting started guides, platform-specific instructions for Android and iOS, API reference, and working examples.
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythonnative"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Cross-platform native UI toolkit for Android and iOS"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Owen Carey" }
|
|
11
11
|
]
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
14
|
license = { file = "LICENSE" }
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 2 - Pre-Alpha",
|
|
@@ -18,7 +18,6 @@ classifiers = [
|
|
|
18
18
|
"License :: OSI Approved :: MIT License",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
-
"Programming Language :: Python :: 3.9",
|
|
22
21
|
"Programming Language :: Python :: 3.10",
|
|
23
22
|
"Programming Language :: Python :: 3.11",
|
|
24
23
|
"Programming Language :: Python :: 3.12",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""PythonNative — declarative native UI for Android and iOS.
|
|
2
|
+
|
|
3
|
+
Public API::
|
|
4
|
+
|
|
5
|
+
import pythonnative as pn
|
|
6
|
+
|
|
7
|
+
@pn.component
|
|
8
|
+
def App():
|
|
9
|
+
count, set_count = pn.use_state(0)
|
|
10
|
+
return pn.Column(
|
|
11
|
+
pn.Text(f"Count: {count}", style={"font_size": 24}),
|
|
12
|
+
pn.Button("+", on_click=lambda: set_count(count + 1)),
|
|
13
|
+
style={"spacing": 12},
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__version__ = "0.7.0"
|
|
18
|
+
|
|
19
|
+
from .components import (
|
|
20
|
+
ActivityIndicator,
|
|
21
|
+
Button,
|
|
22
|
+
Column,
|
|
23
|
+
FlatList,
|
|
24
|
+
Image,
|
|
25
|
+
Modal,
|
|
26
|
+
Pressable,
|
|
27
|
+
ProgressBar,
|
|
28
|
+
Row,
|
|
29
|
+
SafeAreaView,
|
|
30
|
+
ScrollView,
|
|
31
|
+
Slider,
|
|
32
|
+
Spacer,
|
|
33
|
+
Switch,
|
|
34
|
+
Text,
|
|
35
|
+
TextInput,
|
|
36
|
+
View,
|
|
37
|
+
WebView,
|
|
38
|
+
)
|
|
39
|
+
from .element import Element
|
|
40
|
+
from .hooks import (
|
|
41
|
+
Provider,
|
|
42
|
+
component,
|
|
43
|
+
create_context,
|
|
44
|
+
use_callback,
|
|
45
|
+
use_context,
|
|
46
|
+
use_effect,
|
|
47
|
+
use_memo,
|
|
48
|
+
use_navigation,
|
|
49
|
+
use_ref,
|
|
50
|
+
use_state,
|
|
51
|
+
)
|
|
52
|
+
from .page import create_page
|
|
53
|
+
from .style import StyleSheet, ThemeContext
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
# Components
|
|
57
|
+
"ActivityIndicator",
|
|
58
|
+
"Button",
|
|
59
|
+
"Column",
|
|
60
|
+
"FlatList",
|
|
61
|
+
"Image",
|
|
62
|
+
"Modal",
|
|
63
|
+
"Pressable",
|
|
64
|
+
"ProgressBar",
|
|
65
|
+
"Row",
|
|
66
|
+
"SafeAreaView",
|
|
67
|
+
"ScrollView",
|
|
68
|
+
"Slider",
|
|
69
|
+
"Spacer",
|
|
70
|
+
"Switch",
|
|
71
|
+
"Text",
|
|
72
|
+
"TextInput",
|
|
73
|
+
"View",
|
|
74
|
+
"WebView",
|
|
75
|
+
# Core
|
|
76
|
+
"Element",
|
|
77
|
+
"create_page",
|
|
78
|
+
# Hooks
|
|
79
|
+
"component",
|
|
80
|
+
"create_context",
|
|
81
|
+
"use_callback",
|
|
82
|
+
"use_context",
|
|
83
|
+
"use_effect",
|
|
84
|
+
"use_memo",
|
|
85
|
+
"use_navigation",
|
|
86
|
+
"use_ref",
|
|
87
|
+
"use_state",
|
|
88
|
+
"Provider",
|
|
89
|
+
# Styling
|
|
90
|
+
"StyleSheet",
|
|
91
|
+
"ThemeContext",
|
|
92
|
+
]
|
|
@@ -2,6 +2,7 @@ import argparse
|
|
|
2
2
|
import hashlib
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
import sys
|
|
@@ -44,47 +45,38 @@ def init_project(args: argparse.Namespace) -> None:
|
|
|
44
45
|
main_page_py = os.path.join(app_dir, "main_page.py")
|
|
45
46
|
if not os.path.exists(main_page_py) or args.force:
|
|
46
47
|
with open(main_page_py, "w", encoding="utf-8") as f:
|
|
47
|
-
f.write(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def render(self):
|
|
60
|
-
return pn.ScrollView(
|
|
61
|
-
pn.Column(
|
|
62
|
-
pn.Text("Hello from PythonNative!", font_size=24, bold=True),
|
|
63
|
-
pn.Text(f"Tapped {self.state['count']} times"),
|
|
64
|
-
pn.Button("Tap me", on_click=self.increment),
|
|
65
|
-
spacing=12,
|
|
66
|
-
padding=16,
|
|
67
|
-
alignment="fill",
|
|
68
|
-
)
|
|
48
|
+
f.write("""import pythonnative as pn
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pn.component
|
|
52
|
+
def MainPage():
|
|
53
|
+
count, set_count = pn.use_state(0)
|
|
54
|
+
return pn.ScrollView(
|
|
55
|
+
pn.Column(
|
|
56
|
+
pn.Text("Hello from PythonNative!", style={"font_size": 24, "bold": True}),
|
|
57
|
+
pn.Text(f"Tapped {count} times"),
|
|
58
|
+
pn.Button("Tap me", on_click=lambda: set_count(count + 1)),
|
|
59
|
+
style={"spacing": 12, "padding": 16, "align_items": "stretch"},
|
|
69
60
|
)
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
)
|
|
62
|
+
""")
|
|
72
63
|
|
|
73
64
|
# Create config
|
|
74
65
|
config = {
|
|
75
66
|
"name": project_name,
|
|
76
67
|
"appId": "com.example." + project_name.replace(" ", "").lower(),
|
|
77
68
|
"entryPoint": "app/main_page.py",
|
|
69
|
+
"pythonVersion": "3.11",
|
|
78
70
|
"ios": {},
|
|
79
71
|
"android": {},
|
|
80
72
|
}
|
|
81
73
|
with open(config_path, "w", encoding="utf-8") as f:
|
|
82
74
|
json.dump(config, f, indent=2)
|
|
83
75
|
|
|
84
|
-
# Requirements
|
|
76
|
+
# Requirements (third-party packages only; pythonnative itself is bundled by the CLI)
|
|
85
77
|
if not os.path.exists(requirements_path) or args.force:
|
|
86
78
|
with open(requirements_path, "w", encoding="utf-8") as f:
|
|
87
|
-
f.write("
|
|
79
|
+
f.write("")
|
|
88
80
|
|
|
89
81
|
# .gitignore
|
|
90
82
|
default_gitignore = "# PythonNative\n" "__pycache__/\n" "*.pyc\n" ".venv/\n" "build/\n" ".DS_Store\n"
|
|
@@ -158,7 +150,11 @@ def _copy_bundled_template_dir(template_dir: str, destination: str) -> None:
|
|
|
158
150
|
|
|
159
151
|
|
|
160
152
|
def _github_json(url: str) -> Any:
|
|
161
|
-
|
|
153
|
+
headers: dict[str, str] = {"User-Agent": "pythonnative-cli"}
|
|
154
|
+
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
155
|
+
if token:
|
|
156
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
157
|
+
req = urllib.request.Request(url, headers=headers)
|
|
162
158
|
with urllib.request.urlopen(req) as r:
|
|
163
159
|
return json.loads(r.read().decode("utf-8"))
|
|
164
160
|
|
|
@@ -213,6 +209,43 @@ def create_ios_project(project_name: str, destination: str) -> None:
|
|
|
213
209
|
_copy_bundled_template_dir("ios_template", destination)
|
|
214
210
|
|
|
215
211
|
|
|
212
|
+
def _read_project_config() -> dict:
|
|
213
|
+
"""Read pythonnative.json from the current working directory."""
|
|
214
|
+
config_path = os.path.join(os.getcwd(), "pythonnative.json")
|
|
215
|
+
if os.path.exists(config_path):
|
|
216
|
+
with open(config_path, encoding="utf-8") as f:
|
|
217
|
+
return json.load(f)
|
|
218
|
+
return {}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _read_requirements(requirements_path: str) -> list[str]:
|
|
222
|
+
"""Read a requirements file and return non-empty, non-comment lines.
|
|
223
|
+
|
|
224
|
+
Exits with an error if pythonnative is listed — the CLI bundles it
|
|
225
|
+
directly, so it must not be installed separately via pip/Chaquopy.
|
|
226
|
+
"""
|
|
227
|
+
if not os.path.exists(requirements_path):
|
|
228
|
+
return []
|
|
229
|
+
with open(requirements_path, encoding="utf-8") as f:
|
|
230
|
+
lines = f.readlines()
|
|
231
|
+
result: list[str] = []
|
|
232
|
+
for line in lines:
|
|
233
|
+
stripped = line.strip()
|
|
234
|
+
if not stripped or stripped.startswith("#") or stripped.startswith("-"):
|
|
235
|
+
continue
|
|
236
|
+
pkg_name = re.split(r"[\[><=!;]", stripped)[0].strip()
|
|
237
|
+
if pkg_name.lower().replace("-", "_") == "pythonnative":
|
|
238
|
+
print(
|
|
239
|
+
"Error: 'pythonnative' must not be in requirements.txt.\n"
|
|
240
|
+
"The pn CLI automatically bundles the installed pythonnative into your app.\n"
|
|
241
|
+
"requirements.txt is for third-party packages only (e.g. humanize, requests).\n"
|
|
242
|
+
"Remove the pythonnative line from requirements.txt and try again."
|
|
243
|
+
)
|
|
244
|
+
sys.exit(1)
|
|
245
|
+
result.append(stripped)
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
|
|
216
249
|
def run_project(args: argparse.Namespace) -> None:
|
|
217
250
|
"""
|
|
218
251
|
Run the specified project.
|
|
@@ -220,9 +253,15 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
220
253
|
# Determine the platform
|
|
221
254
|
platform: str = args.platform
|
|
222
255
|
prepare_only: bool = getattr(args, "prepare_only", False)
|
|
256
|
+
hot_reload: bool = getattr(args, "hot_reload", False)
|
|
257
|
+
|
|
258
|
+
# Read project configuration and save project root before any chdir
|
|
259
|
+
project_dir: str = os.getcwd()
|
|
260
|
+
config = _read_project_config()
|
|
261
|
+
python_version: str = config.get("pythonVersion", "3.11")
|
|
223
262
|
|
|
224
263
|
# Define the build directory
|
|
225
|
-
build_dir: str = os.path.join(
|
|
264
|
+
build_dir: str = os.path.join(project_dir, "build", platform)
|
|
226
265
|
|
|
227
266
|
# Create the build directory if it doesn't exist
|
|
228
267
|
os.makedirs(build_dir, exist_ok=True)
|
|
@@ -266,10 +305,30 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
266
305
|
# Non-fatal; fallback to the packaged PyPI dependency if present
|
|
267
306
|
pass
|
|
268
307
|
|
|
269
|
-
#
|
|
308
|
+
# Validate and read the user's requirements.txt
|
|
309
|
+
requirements_path = os.path.join(project_dir, "requirements.txt")
|
|
310
|
+
pip_reqs = _read_requirements(requirements_path)
|
|
311
|
+
|
|
312
|
+
if platform == "android":
|
|
313
|
+
# Patch the Android build.gradle with the configured Python version
|
|
314
|
+
app_build_gradle = os.path.join(build_dir, "android_template", "app", "build.gradle")
|
|
315
|
+
if os.path.exists(app_build_gradle):
|
|
316
|
+
with open(app_build_gradle, encoding="utf-8") as f:
|
|
317
|
+
content = f.read()
|
|
318
|
+
content = content.replace('version "3.11"', f'version "{python_version}"')
|
|
319
|
+
with open(app_build_gradle, "w", encoding="utf-8") as f:
|
|
320
|
+
f.write(content)
|
|
321
|
+
# Copy requirements.txt into the Android project for Chaquopy
|
|
322
|
+
android_reqs_path = os.path.join(build_dir, "android_template", "app", "requirements.txt")
|
|
323
|
+
if os.path.exists(requirements_path):
|
|
324
|
+
shutil.copy2(requirements_path, android_reqs_path)
|
|
325
|
+
else:
|
|
326
|
+
with open(android_reqs_path, "w", encoding="utf-8") as f:
|
|
327
|
+
f.write("")
|
|
328
|
+
|
|
329
|
+
# Install any necessary Python packages into the host environment
|
|
270
330
|
# Skip installation during prepare-only to avoid network access and speed up scaffolding
|
|
271
331
|
if not prepare_only:
|
|
272
|
-
requirements_path = os.path.join(os.getcwd(), "requirements.txt")
|
|
273
332
|
if os.path.exists(requirements_path):
|
|
274
333
|
subprocess.run([sys.executable, "-m", "pip", "install", "-r", requirements_path], check=False)
|
|
275
334
|
|
|
@@ -521,6 +580,29 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
521
580
|
except Exception:
|
|
522
581
|
# Non-fatal; if metadata isn't present, rubicon import may fail and fallback UI will appear
|
|
523
582
|
pass
|
|
583
|
+
# Install user's pip requirements (pure-Python packages) into the app bundle
|
|
584
|
+
if pip_reqs:
|
|
585
|
+
try:
|
|
586
|
+
reqs_tmp = os.path.join(build_dir, "ios_requirements.txt")
|
|
587
|
+
with open(reqs_tmp, "w", encoding="utf-8") as f:
|
|
588
|
+
f.write("\n".join(pip_reqs) + "\n")
|
|
589
|
+
tmp_reqs_dir = os.path.join(build_dir, "ios_user_packages")
|
|
590
|
+
if os.path.isdir(tmp_reqs_dir):
|
|
591
|
+
shutil.rmtree(tmp_reqs_dir)
|
|
592
|
+
os.makedirs(tmp_reqs_dir, exist_ok=True)
|
|
593
|
+
subprocess.run(
|
|
594
|
+
[sys.executable, "-m", "pip", "install", "-t", tmp_reqs_dir, "-r", reqs_tmp],
|
|
595
|
+
check=False,
|
|
596
|
+
)
|
|
597
|
+
for entry in os.listdir(tmp_reqs_dir):
|
|
598
|
+
src_entry = os.path.join(tmp_reqs_dir, entry)
|
|
599
|
+
dst_entry = os.path.join(platform_site_dir, entry)
|
|
600
|
+
if os.path.isdir(src_entry):
|
|
601
|
+
shutil.copytree(src_entry, dst_entry, dirs_exist_ok=True)
|
|
602
|
+
else:
|
|
603
|
+
shutil.copy2(src_entry, dst_entry)
|
|
604
|
+
except Exception:
|
|
605
|
+
pass
|
|
524
606
|
# Note: Python.xcframework provides a static library for Simulator; it must be linked at build time.
|
|
525
607
|
# We copy the XCFramework into the project directory above so Xcode can link it.
|
|
526
608
|
except Exception:
|
|
@@ -567,6 +649,39 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
567
649
|
except Exception:
|
|
568
650
|
print("Failed to auto-run on Simulator; open the project in Xcode to run.")
|
|
569
651
|
|
|
652
|
+
# Hot-reload file watcher
|
|
653
|
+
if hot_reload and not prepare_only:
|
|
654
|
+
_run_hot_reload(platform, project_dir, build_dir)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def _run_hot_reload(platform: str, project_dir: str, build_dir: str) -> None:
|
|
658
|
+
"""Watch ``app/`` for changes and push updated files to the device."""
|
|
659
|
+
from .hot_reload import FileWatcher
|
|
660
|
+
|
|
661
|
+
app_dir = os.path.join(project_dir, "app")
|
|
662
|
+
|
|
663
|
+
def on_change(changed_files: List[str]) -> None:
|
|
664
|
+
for fpath in changed_files:
|
|
665
|
+
rel = os.path.relpath(fpath, project_dir)
|
|
666
|
+
print(f"[hot-reload] Changed: {rel}")
|
|
667
|
+
if platform == "android":
|
|
668
|
+
dest = f"/data/data/com.pythonnative.android_template/files/{rel}"
|
|
669
|
+
subprocess.run(["adb", "push", fpath, dest], check=False, capture_output=True)
|
|
670
|
+
elif platform == "ios":
|
|
671
|
+
pass # simctl file push would go here
|
|
672
|
+
|
|
673
|
+
print("[hot-reload] Watching app/ for changes. Press Ctrl+C to stop.")
|
|
674
|
+
watcher = FileWatcher(app_dir, on_change, interval=1.0)
|
|
675
|
+
watcher.start()
|
|
676
|
+
try:
|
|
677
|
+
import time
|
|
678
|
+
|
|
679
|
+
while True:
|
|
680
|
+
time.sleep(1)
|
|
681
|
+
except KeyboardInterrupt:
|
|
682
|
+
watcher.stop()
|
|
683
|
+
print("\n[hot-reload] Stopped.")
|
|
684
|
+
|
|
570
685
|
|
|
571
686
|
def clean_project(args: argparse.Namespace) -> None:
|
|
572
687
|
"""
|
|
@@ -601,6 +716,11 @@ def main() -> None:
|
|
|
601
716
|
action="store_true",
|
|
602
717
|
help="Extract templates and stage app without building",
|
|
603
718
|
)
|
|
719
|
+
parser_run.add_argument(
|
|
720
|
+
"--hot-reload",
|
|
721
|
+
action="store_true",
|
|
722
|
+
help="Watch app/ for changes and push updates to the running app",
|
|
723
|
+
)
|
|
604
724
|
parser_run.set_defaults(func=run_project)
|
|
605
725
|
|
|
606
726
|
# Create a new command 'clean' that calls clean_project
|