pythonnative 0.5.0__tar.gz → 0.6.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.6.0}/PKG-INFO +2 -19
- {pythonnative-0.5.0 → pythonnative-0.6.0}/README.md +0 -16
- {pythonnative-0.5.0 → pythonnative-0.6.0}/pyproject.toml +2 -3
- pythonnative-0.6.0/src/pythonnative/__init__.py +102 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/cli/pn.py +138 -10
- pythonnative-0.6.0/src/pythonnative/components.py +563 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/element.py +14 -8
- pythonnative-0.6.0/src/pythonnative/hooks.py +287 -0
- pythonnative-0.6.0/src/pythonnative/hot_reload.py +143 -0
- pythonnative-0.6.0/src/pythonnative/native_modules/__init__.py +19 -0
- pythonnative-0.6.0/src/pythonnative/native_modules/camera.py +105 -0
- pythonnative-0.6.0/src/pythonnative/native_modules/file_system.py +131 -0
- pythonnative-0.6.0/src/pythonnative/native_modules/location.py +61 -0
- pythonnative-0.6.0/src/pythonnative/native_modules/notifications.py +151 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/native_views.py +552 -18
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/page.py +1 -0
- pythonnative-0.6.0/src/pythonnative/reconciler.py +262 -0
- pythonnative-0.6.0/src/pythonnative/style.py +115 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/build.gradle +2 -7
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/build.gradle +1 -1
- {pythonnative-0.5.0 → pythonnative-0.6.0/src/pythonnative.egg-info}/PKG-INFO +2 -19
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative.egg-info/SOURCES.txt +11 -4
- {pythonnative-0.5.0 → pythonnative-0.6.0}/tests/test_components.py +106 -0
- pythonnative-0.6.0/tests/test_hooks.py +433 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/tests/test_reconciler.py +99 -8
- {pythonnative-0.5.0 → pythonnative-0.6.0}/tests/test_smoke.py +19 -0
- pythonnative-0.6.0/tests/test_style.py +58 -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.6.0}/LICENSE +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/setup.cfg +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/cli/__init__.py +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.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.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/gradlew +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative/utils.py +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative.egg-info/requires.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/src/pythonnative.egg-info/top_level.txt +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.0}/tests/test_cli.py +0 -0
- {pythonnative-0.5.0 → pythonnative-0.6.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.6.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
|
|
@@ -132,22 +131,6 @@ class MainPage(pn.Page):
|
|
|
132
131
|
)
|
|
133
132
|
```
|
|
134
133
|
|
|
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
134
|
## Documentation
|
|
152
135
|
|
|
153
136
|
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.
|
|
@@ -69,22 +69,6 @@ class MainPage(pn.Page):
|
|
|
69
69
|
)
|
|
70
70
|
```
|
|
71
71
|
|
|
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
72
|
## Documentation
|
|
89
73
|
|
|
90
74
|
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.6.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,102 @@
|
|
|
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 counter(initial=0):
|
|
9
|
+
count, set_count = pn.use_state(initial)
|
|
10
|
+
return pn.Column(
|
|
11
|
+
pn.Text(f"Count: {count}", font_size=24),
|
|
12
|
+
pn.Button("+", on_click=lambda: set_count(count + 1)),
|
|
13
|
+
spacing=12,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
class MainPage(pn.Page):
|
|
17
|
+
def __init__(self, native_instance):
|
|
18
|
+
super().__init__(native_instance)
|
|
19
|
+
|
|
20
|
+
def render(self):
|
|
21
|
+
return pn.Column(
|
|
22
|
+
counter(initial=0),
|
|
23
|
+
counter(initial=10),
|
|
24
|
+
spacing=16,
|
|
25
|
+
padding=16,
|
|
26
|
+
)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
__version__ = "0.6.0"
|
|
30
|
+
|
|
31
|
+
from .components import (
|
|
32
|
+
ActivityIndicator,
|
|
33
|
+
Button,
|
|
34
|
+
Column,
|
|
35
|
+
FlatList,
|
|
36
|
+
Image,
|
|
37
|
+
Modal,
|
|
38
|
+
Pressable,
|
|
39
|
+
ProgressBar,
|
|
40
|
+
Row,
|
|
41
|
+
SafeAreaView,
|
|
42
|
+
ScrollView,
|
|
43
|
+
Slider,
|
|
44
|
+
Spacer,
|
|
45
|
+
Switch,
|
|
46
|
+
Text,
|
|
47
|
+
TextInput,
|
|
48
|
+
View,
|
|
49
|
+
WebView,
|
|
50
|
+
)
|
|
51
|
+
from .element import Element
|
|
52
|
+
from .hooks import (
|
|
53
|
+
Provider,
|
|
54
|
+
component,
|
|
55
|
+
create_context,
|
|
56
|
+
use_callback,
|
|
57
|
+
use_context,
|
|
58
|
+
use_effect,
|
|
59
|
+
use_memo,
|
|
60
|
+
use_ref,
|
|
61
|
+
use_state,
|
|
62
|
+
)
|
|
63
|
+
from .page import Page
|
|
64
|
+
from .style import StyleSheet, ThemeContext
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
# Components
|
|
68
|
+
"ActivityIndicator",
|
|
69
|
+
"Button",
|
|
70
|
+
"Column",
|
|
71
|
+
"FlatList",
|
|
72
|
+
"Image",
|
|
73
|
+
"Modal",
|
|
74
|
+
"Pressable",
|
|
75
|
+
"ProgressBar",
|
|
76
|
+
"Row",
|
|
77
|
+
"SafeAreaView",
|
|
78
|
+
"ScrollView",
|
|
79
|
+
"Slider",
|
|
80
|
+
"Spacer",
|
|
81
|
+
"Switch",
|
|
82
|
+
"Text",
|
|
83
|
+
"TextInput",
|
|
84
|
+
"View",
|
|
85
|
+
"WebView",
|
|
86
|
+
# Core
|
|
87
|
+
"Element",
|
|
88
|
+
"Page",
|
|
89
|
+
# Hooks
|
|
90
|
+
"component",
|
|
91
|
+
"create_context",
|
|
92
|
+
"use_callback",
|
|
93
|
+
"use_context",
|
|
94
|
+
"use_effect",
|
|
95
|
+
"use_memo",
|
|
96
|
+
"use_ref",
|
|
97
|
+
"use_state",
|
|
98
|
+
"Provider",
|
|
99
|
+
# Styling
|
|
100
|
+
"StyleSheet",
|
|
101
|
+
"ThemeContext",
|
|
102
|
+
]
|
|
@@ -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,8 +45,7 @@ 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
|
-
"""import pythonnative as pn
|
|
48
|
+
f.write("""import pythonnative as pn
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class MainPage(pn.Page):
|
|
@@ -67,24 +67,24 @@ class MainPage(pn.Page):
|
|
|
67
67
|
alignment="fill",
|
|
68
68
|
)
|
|
69
69
|
)
|
|
70
|
-
"""
|
|
71
|
-
)
|
|
70
|
+
""")
|
|
72
71
|
|
|
73
72
|
# Create config
|
|
74
73
|
config = {
|
|
75
74
|
"name": project_name,
|
|
76
75
|
"appId": "com.example." + project_name.replace(" ", "").lower(),
|
|
77
76
|
"entryPoint": "app/main_page.py",
|
|
77
|
+
"pythonVersion": "3.11",
|
|
78
78
|
"ios": {},
|
|
79
79
|
"android": {},
|
|
80
80
|
}
|
|
81
81
|
with open(config_path, "w", encoding="utf-8") as f:
|
|
82
82
|
json.dump(config, f, indent=2)
|
|
83
83
|
|
|
84
|
-
# Requirements
|
|
84
|
+
# Requirements (third-party packages only; pythonnative itself is bundled by the CLI)
|
|
85
85
|
if not os.path.exists(requirements_path) or args.force:
|
|
86
86
|
with open(requirements_path, "w", encoding="utf-8") as f:
|
|
87
|
-
f.write("
|
|
87
|
+
f.write("")
|
|
88
88
|
|
|
89
89
|
# .gitignore
|
|
90
90
|
default_gitignore = "# PythonNative\n" "__pycache__/\n" "*.pyc\n" ".venv/\n" "build/\n" ".DS_Store\n"
|
|
@@ -158,7 +158,11 @@ def _copy_bundled_template_dir(template_dir: str, destination: str) -> None:
|
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
def _github_json(url: str) -> Any:
|
|
161
|
-
|
|
161
|
+
headers: dict[str, str] = {"User-Agent": "pythonnative-cli"}
|
|
162
|
+
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
163
|
+
if token:
|
|
164
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
165
|
+
req = urllib.request.Request(url, headers=headers)
|
|
162
166
|
with urllib.request.urlopen(req) as r:
|
|
163
167
|
return json.loads(r.read().decode("utf-8"))
|
|
164
168
|
|
|
@@ -213,6 +217,43 @@ def create_ios_project(project_name: str, destination: str) -> None:
|
|
|
213
217
|
_copy_bundled_template_dir("ios_template", destination)
|
|
214
218
|
|
|
215
219
|
|
|
220
|
+
def _read_project_config() -> dict:
|
|
221
|
+
"""Read pythonnative.json from the current working directory."""
|
|
222
|
+
config_path = os.path.join(os.getcwd(), "pythonnative.json")
|
|
223
|
+
if os.path.exists(config_path):
|
|
224
|
+
with open(config_path, encoding="utf-8") as f:
|
|
225
|
+
return json.load(f)
|
|
226
|
+
return {}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _read_requirements(requirements_path: str) -> list[str]:
|
|
230
|
+
"""Read a requirements file and return non-empty, non-comment lines.
|
|
231
|
+
|
|
232
|
+
Exits with an error if pythonnative is listed — the CLI bundles it
|
|
233
|
+
directly, so it must not be installed separately via pip/Chaquopy.
|
|
234
|
+
"""
|
|
235
|
+
if not os.path.exists(requirements_path):
|
|
236
|
+
return []
|
|
237
|
+
with open(requirements_path, encoding="utf-8") as f:
|
|
238
|
+
lines = f.readlines()
|
|
239
|
+
result: list[str] = []
|
|
240
|
+
for line in lines:
|
|
241
|
+
stripped = line.strip()
|
|
242
|
+
if not stripped or stripped.startswith("#") or stripped.startswith("-"):
|
|
243
|
+
continue
|
|
244
|
+
pkg_name = re.split(r"[\[><=!;]", stripped)[0].strip()
|
|
245
|
+
if pkg_name.lower().replace("-", "_") == "pythonnative":
|
|
246
|
+
print(
|
|
247
|
+
"Error: 'pythonnative' must not be in requirements.txt.\n"
|
|
248
|
+
"The pn CLI automatically bundles the installed pythonnative into your app.\n"
|
|
249
|
+
"requirements.txt is for third-party packages only (e.g. humanize, requests).\n"
|
|
250
|
+
"Remove the pythonnative line from requirements.txt and try again."
|
|
251
|
+
)
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
result.append(stripped)
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
|
|
216
257
|
def run_project(args: argparse.Namespace) -> None:
|
|
217
258
|
"""
|
|
218
259
|
Run the specified project.
|
|
@@ -220,9 +261,15 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
220
261
|
# Determine the platform
|
|
221
262
|
platform: str = args.platform
|
|
222
263
|
prepare_only: bool = getattr(args, "prepare_only", False)
|
|
264
|
+
hot_reload: bool = getattr(args, "hot_reload", False)
|
|
265
|
+
|
|
266
|
+
# Read project configuration and save project root before any chdir
|
|
267
|
+
project_dir: str = os.getcwd()
|
|
268
|
+
config = _read_project_config()
|
|
269
|
+
python_version: str = config.get("pythonVersion", "3.11")
|
|
223
270
|
|
|
224
271
|
# Define the build directory
|
|
225
|
-
build_dir: str = os.path.join(
|
|
272
|
+
build_dir: str = os.path.join(project_dir, "build", platform)
|
|
226
273
|
|
|
227
274
|
# Create the build directory if it doesn't exist
|
|
228
275
|
os.makedirs(build_dir, exist_ok=True)
|
|
@@ -266,10 +313,30 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
266
313
|
# Non-fatal; fallback to the packaged PyPI dependency if present
|
|
267
314
|
pass
|
|
268
315
|
|
|
269
|
-
#
|
|
316
|
+
# Validate and read the user's requirements.txt
|
|
317
|
+
requirements_path = os.path.join(project_dir, "requirements.txt")
|
|
318
|
+
pip_reqs = _read_requirements(requirements_path)
|
|
319
|
+
|
|
320
|
+
if platform == "android":
|
|
321
|
+
# Patch the Android build.gradle with the configured Python version
|
|
322
|
+
app_build_gradle = os.path.join(build_dir, "android_template", "app", "build.gradle")
|
|
323
|
+
if os.path.exists(app_build_gradle):
|
|
324
|
+
with open(app_build_gradle, encoding="utf-8") as f:
|
|
325
|
+
content = f.read()
|
|
326
|
+
content = content.replace('version "3.11"', f'version "{python_version}"')
|
|
327
|
+
with open(app_build_gradle, "w", encoding="utf-8") as f:
|
|
328
|
+
f.write(content)
|
|
329
|
+
# Copy requirements.txt into the Android project for Chaquopy
|
|
330
|
+
android_reqs_path = os.path.join(build_dir, "android_template", "app", "requirements.txt")
|
|
331
|
+
if os.path.exists(requirements_path):
|
|
332
|
+
shutil.copy2(requirements_path, android_reqs_path)
|
|
333
|
+
else:
|
|
334
|
+
with open(android_reqs_path, "w", encoding="utf-8") as f:
|
|
335
|
+
f.write("")
|
|
336
|
+
|
|
337
|
+
# Install any necessary Python packages into the host environment
|
|
270
338
|
# Skip installation during prepare-only to avoid network access and speed up scaffolding
|
|
271
339
|
if not prepare_only:
|
|
272
|
-
requirements_path = os.path.join(os.getcwd(), "requirements.txt")
|
|
273
340
|
if os.path.exists(requirements_path):
|
|
274
341
|
subprocess.run([sys.executable, "-m", "pip", "install", "-r", requirements_path], check=False)
|
|
275
342
|
|
|
@@ -521,6 +588,29 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
521
588
|
except Exception:
|
|
522
589
|
# Non-fatal; if metadata isn't present, rubicon import may fail and fallback UI will appear
|
|
523
590
|
pass
|
|
591
|
+
# Install user's pip requirements (pure-Python packages) into the app bundle
|
|
592
|
+
if pip_reqs:
|
|
593
|
+
try:
|
|
594
|
+
reqs_tmp = os.path.join(build_dir, "ios_requirements.txt")
|
|
595
|
+
with open(reqs_tmp, "w", encoding="utf-8") as f:
|
|
596
|
+
f.write("\n".join(pip_reqs) + "\n")
|
|
597
|
+
tmp_reqs_dir = os.path.join(build_dir, "ios_user_packages")
|
|
598
|
+
if os.path.isdir(tmp_reqs_dir):
|
|
599
|
+
shutil.rmtree(tmp_reqs_dir)
|
|
600
|
+
os.makedirs(tmp_reqs_dir, exist_ok=True)
|
|
601
|
+
subprocess.run(
|
|
602
|
+
[sys.executable, "-m", "pip", "install", "-t", tmp_reqs_dir, "-r", reqs_tmp],
|
|
603
|
+
check=False,
|
|
604
|
+
)
|
|
605
|
+
for entry in os.listdir(tmp_reqs_dir):
|
|
606
|
+
src_entry = os.path.join(tmp_reqs_dir, entry)
|
|
607
|
+
dst_entry = os.path.join(platform_site_dir, entry)
|
|
608
|
+
if os.path.isdir(src_entry):
|
|
609
|
+
shutil.copytree(src_entry, dst_entry, dirs_exist_ok=True)
|
|
610
|
+
else:
|
|
611
|
+
shutil.copy2(src_entry, dst_entry)
|
|
612
|
+
except Exception:
|
|
613
|
+
pass
|
|
524
614
|
# Note: Python.xcframework provides a static library for Simulator; it must be linked at build time.
|
|
525
615
|
# We copy the XCFramework into the project directory above so Xcode can link it.
|
|
526
616
|
except Exception:
|
|
@@ -567,6 +657,39 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
567
657
|
except Exception:
|
|
568
658
|
print("Failed to auto-run on Simulator; open the project in Xcode to run.")
|
|
569
659
|
|
|
660
|
+
# Hot-reload file watcher
|
|
661
|
+
if hot_reload and not prepare_only:
|
|
662
|
+
_run_hot_reload(platform, project_dir, build_dir)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _run_hot_reload(platform: str, project_dir: str, build_dir: str) -> None:
|
|
666
|
+
"""Watch ``app/`` for changes and push updated files to the device."""
|
|
667
|
+
from .hot_reload import FileWatcher
|
|
668
|
+
|
|
669
|
+
app_dir = os.path.join(project_dir, "app")
|
|
670
|
+
|
|
671
|
+
def on_change(changed_files: List[str]) -> None:
|
|
672
|
+
for fpath in changed_files:
|
|
673
|
+
rel = os.path.relpath(fpath, project_dir)
|
|
674
|
+
print(f"[hot-reload] Changed: {rel}")
|
|
675
|
+
if platform == "android":
|
|
676
|
+
dest = f"/data/data/com.pythonnative.android_template/files/{rel}"
|
|
677
|
+
subprocess.run(["adb", "push", fpath, dest], check=False, capture_output=True)
|
|
678
|
+
elif platform == "ios":
|
|
679
|
+
pass # simctl file push would go here
|
|
680
|
+
|
|
681
|
+
print("[hot-reload] Watching app/ for changes. Press Ctrl+C to stop.")
|
|
682
|
+
watcher = FileWatcher(app_dir, on_change, interval=1.0)
|
|
683
|
+
watcher.start()
|
|
684
|
+
try:
|
|
685
|
+
import time
|
|
686
|
+
|
|
687
|
+
while True:
|
|
688
|
+
time.sleep(1)
|
|
689
|
+
except KeyboardInterrupt:
|
|
690
|
+
watcher.stop()
|
|
691
|
+
print("\n[hot-reload] Stopped.")
|
|
692
|
+
|
|
570
693
|
|
|
571
694
|
def clean_project(args: argparse.Namespace) -> None:
|
|
572
695
|
"""
|
|
@@ -601,6 +724,11 @@ def main() -> None:
|
|
|
601
724
|
action="store_true",
|
|
602
725
|
help="Extract templates and stage app without building",
|
|
603
726
|
)
|
|
727
|
+
parser_run.add_argument(
|
|
728
|
+
"--hot-reload",
|
|
729
|
+
action="store_true",
|
|
730
|
+
help="Watch app/ for changes and push updates to the running app",
|
|
731
|
+
)
|
|
604
732
|
parser_run.set_defaults(func=run_project)
|
|
605
733
|
|
|
606
734
|
# Create a new command 'clean' that calls clean_project
|