pythonnative 0.28.0__py3-none-any.whl → 0.30.0__py3-none-any.whl
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/__init__.py +1 -1
- pythonnative/cli/pn.py +59 -13
- pythonnative/project/doctor.py +21 -1
- {pythonnative-0.28.0.dist-info → pythonnative-0.30.0.dist-info}/METADATA +1 -1
- {pythonnative-0.28.0.dist-info → pythonnative-0.30.0.dist-info}/RECORD +9 -9
- {pythonnative-0.28.0.dist-info → pythonnative-0.30.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.28.0.dist-info → pythonnative-0.30.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.28.0.dist-info → pythonnative-0.30.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.28.0.dist-info → pythonnative-0.30.0.dist-info}/top_level.txt +0 -0
pythonnative/__init__.py
CHANGED
pythonnative/cli/pn.py
CHANGED
|
@@ -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`` +
|
|
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
|
|
109
|
+
"""Scaffold a new PythonNative project.
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 {
|
|
146
|
-
|
|
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 (
|
|
877
|
-
parser_init.add_argument("--force", action="store_true", help="Overwrite existing files
|
|
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")
|
pythonnative/project/doctor.py
CHANGED
|
@@ -61,8 +61,17 @@ def _which_version(tool: str, version_args: List[str]) -> Optional[str]:
|
|
|
61
61
|
return text[0] if text else path
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def _tkinter_available() -> bool:
|
|
65
|
+
"""Return whether the host Python can import Tkinter."""
|
|
66
|
+
try:
|
|
67
|
+
import tkinter # noqa: F401
|
|
68
|
+
except ImportError:
|
|
69
|
+
return False
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
|
|
64
73
|
def check_common() -> List[CheckResult]:
|
|
65
|
-
"""Run platform-agnostic checks (interpreter
|
|
74
|
+
"""Run platform-agnostic checks (interpreter and optional dependencies).
|
|
66
75
|
|
|
67
76
|
Returns:
|
|
68
77
|
Check results for the host Python and optional dependencies.
|
|
@@ -89,6 +98,17 @@ def check_common() -> List[CheckResult]:
|
|
|
89
98
|
"not installed; run: pip install 'pythonnative[build]'",
|
|
90
99
|
)
|
|
91
100
|
)
|
|
101
|
+
if _tkinter_available():
|
|
102
|
+
results.append(CheckResult("Tkinter (desktop preview)", OK))
|
|
103
|
+
else:
|
|
104
|
+
results.append(
|
|
105
|
+
CheckResult(
|
|
106
|
+
"Tkinter (desktop preview)",
|
|
107
|
+
WARN,
|
|
108
|
+
"not installed; macOS: brew install python-tk; Debian/Ubuntu: sudo apt-get install python3-tk; "
|
|
109
|
+
"Windows: reinstall Python with the 'tcl/tk' option checked",
|
|
110
|
+
)
|
|
111
|
+
)
|
|
92
112
|
return results
|
|
93
113
|
|
|
94
114
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pythonnative/__init__.py,sha256=
|
|
1
|
+
pythonnative/__init__.py,sha256=e-p6QNv7CzeBTJnU-SP2ccgdrU4jeVoS0R7sFvlg_S8,8680
|
|
2
2
|
pythonnative/_ios_log.py,sha256=Oi7V28VxcVoZyrpAirvLeEmUW18McqnU87V4d37Zzlw,2582
|
|
3
3
|
pythonnative/alerts.py,sha256=mIANysFlaHwL5EqKnvNcyiJN9rGiZi9XDrD9Jpz1RFM,9340
|
|
4
4
|
pythonnative/animated.py,sha256=A4IOBf_GD6lUn0H8ArFH6URf0-m0PG__8uaxcw6-ECE,60373
|
|
@@ -27,7 +27,7 @@ pythonnative/suspense.py,sha256=IxWGXFP3Odriy0uQe9zhrNrObdKer0_WT6v9Vi8Vatg,1466
|
|
|
27
27
|
pythonnative/utils.py,sha256=-hwe_YS19ebpjeygdl3dGeVsYzO4G74rYD53svSi0rI,7593
|
|
28
28
|
pythonnative/virtual_rows.py,sha256=4YK3CeZobW-6F6GCXzGNFfmOaAiigmH4HXtqjYDRbyU,5056
|
|
29
29
|
pythonnative/cli/__init__.py,sha256=NM1psvKe8jT0vzp8Ak4MMoygZz4P_msk5g-YEsY8xLk,232
|
|
30
|
-
pythonnative/cli/pn.py,sha256=
|
|
30
|
+
pythonnative/cli/pn.py,sha256=1ZHvuqxW_K9MWn4_1RkVt_oCK-Y0F_bRNq_EYcG57Hc,37022
|
|
31
31
|
pythonnative/native_modules/__init__.py,sha256=pgigpHuzT-rqwcjlwJvu93_4L8Nozal1HJid0S_JlmM,2636
|
|
32
32
|
pythonnative/native_modules/app_state.py,sha256=CKAwDQIwoGElSLE3l7Bekk0qRbFVFOTSuSCBybumZEk,2742
|
|
33
33
|
pythonnative/native_modules/battery.py,sha256=-lJu8LURw-yEObXUyhNq_jHFDa1_33lgzbJzXaGqUL4,4429
|
|
@@ -53,7 +53,7 @@ pythonnative/project/android.py,sha256=ynRQwVvv1trFB2eFaUv38-48q4m0_Z4cTbAFEOiKn
|
|
|
53
53
|
pythonnative/project/builder.py,sha256=TTN_ymdSyF4wVtSRRwOnSmc91LMn_g_M7_jTI0NtbVU,23946
|
|
54
54
|
pythonnative/project/config.py,sha256=CmN6VojcuDRVnLwKFnQyd9B20_HL9l0RWDnO91G_JaY,25001
|
|
55
55
|
pythonnative/project/devices.py,sha256=OkPzqaD6l4xNJer-rKzorAS9IgvsE87g0JK-2V8FIgE,9593
|
|
56
|
-
pythonnative/project/doctor.py,sha256=
|
|
56
|
+
pythonnative/project/doctor.py,sha256=cNCUWyzu4WBJ9peARIgQ2AyoAGp81NKKM5VkIx7dpFE,8623
|
|
57
57
|
pythonnative/project/icons.py,sha256=GaXpktQ9v4v2JZPM3ppueNPmwjTuFiLanHJDaZbLGuo,7945
|
|
58
58
|
pythonnative/project/ios.py,sha256=Xrx2Jh1v4-3ftLkvKAHQuXCA5nuBdKV5nmTTXkDFaNA,13077
|
|
59
59
|
pythonnative/project/permissions.py,sha256=WyApoqZ0mCTeAtffYbpCGV-bgLvOG7SlPcV7zSO_IV8,14227
|
|
@@ -116,9 +116,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
|
|
|
116
116
|
pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
|
|
117
117
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=HLRr3cmouRqQeXG13WrZ2XSAmgMgCsNXfZpbwkxeFcs,1385
|
|
118
118
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
|
|
119
|
-
pythonnative-0.
|
|
120
|
-
pythonnative-0.
|
|
121
|
-
pythonnative-0.
|
|
122
|
-
pythonnative-0.
|
|
123
|
-
pythonnative-0.
|
|
124
|
-
pythonnative-0.
|
|
119
|
+
pythonnative-0.30.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
|
|
120
|
+
pythonnative-0.30.0.dist-info/METADATA,sha256=SEAFFisEYUwHRzNmwMffz2ngrHcENxeTnK35CySNegE,11895
|
|
121
|
+
pythonnative-0.30.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
122
|
+
pythonnative-0.30.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
|
|
123
|
+
pythonnative-0.30.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
|
|
124
|
+
pythonnative-0.30.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|