pythonnative 0.30.0__py3-none-any.whl → 0.31.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 CHANGED
@@ -60,7 +60,7 @@ Example:
60
60
  ```
61
61
  """
62
62
 
63
- __version__ = "0.30.0"
63
+ __version__ = "0.31.0"
64
64
 
65
65
  from . import appearance, diagnostics, gestures, images, runtime, sdk
66
66
  from .alerts import Alert
pythonnative/cli/pn.py CHANGED
@@ -9,7 +9,7 @@ The console script `pn` (declared in `pyproject.toml`) dispatches to:
9
9
  - `pn preview [component]`: render the app in a desktop (Tkinter) window
10
10
  with Fast Refresh, the fast inner dev loop, no device required.
11
11
  - `pn devices [platform]`: list connected devices, emulators, and
12
- simulators.
12
+ simulators, as a table or as JSON with `--json`.
13
13
  - `pn run android|ios [--device D]`: stage + build + install + launch on
14
14
  a device, emulator, or simulator, with optional on-device hot reload.
15
15
  - `pn logs android|ios`: stream logs from the running app without
@@ -38,7 +38,7 @@ import sys
38
38
  import time
39
39
  from importlib.metadata import version as pkg_version
40
40
  from pathlib import Path
41
- from typing import Any, Dict, List, Optional
41
+ from typing import Any, Dict, List, Optional, TextIO
42
42
 
43
43
  from ..project import builder as builder_mod
44
44
  from ..project import devices as devices_mod
@@ -298,18 +298,46 @@ def _preview_entry(project_dir: Path) -> str:
298
298
  # ======================================================================
299
299
 
300
300
 
301
+ def _print_no_devices_hints(stream: TextIO) -> None:
302
+ """Print the "no devices" guidance to ``stream``.
303
+
304
+ Shared by both output modes so the wording can't drift between the
305
+ table (which sends it to stdout) and ``--json`` (stderr).
306
+
307
+ Args:
308
+ stream: Where to write, ``sys.stdout`` or ``sys.stderr``.
309
+ """
310
+ print("No devices found.", file=stream)
311
+ print("Android: start an emulator or connect a device with USB debugging enabled.", file=stream)
312
+ print("iOS: open Xcode once to install Simulators, or plug in a device.", file=stream)
313
+
314
+
301
315
  def devices_command(args: argparse.Namespace) -> None:
302
316
  """List connected devices, emulators, and simulators.
303
317
 
318
+ Prints an aligned table and exits 1 when nothing is connected.
319
+
320
+ With ``--json``, stdout carries a JSON array and nothing else, one
321
+ object per device (see ``Device.to_dict``), so it stays parseable.
322
+ The "no devices" hints go to stderr instead, an empty result prints
323
+ ``[]``, and the exit status is 0 either way, since "no devices" is a
324
+ valid answer for a script rather than a failure.
325
+
304
326
  Args:
305
- args: Parsed namespace with optional ``platform``.
327
+ args: Parsed namespace with optional ``platform`` and ``json``.
306
328
  """
307
329
  platform: Optional[str] = getattr(args, "platform", None)
330
+ as_json: bool = getattr(args, "json", False)
308
331
  devices = devices_mod.list_devices(platform)
332
+
333
+ if as_json:
334
+ if not devices:
335
+ _print_no_devices_hints(sys.stderr)
336
+ print(json.dumps([device.to_dict() for device in devices], indent=2))
337
+ return
338
+
309
339
  if not devices:
310
- print("No devices found.")
311
- print("Android: start an emulator or connect a device with USB debugging enabled.")
312
- print("iOS: open Xcode once to install Simulators, or plug in a device.")
340
+ _print_no_devices_hints(sys.stdout)
313
341
  sys.exit(1)
314
342
  print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
315
343
  for device in devices:
@@ -943,6 +971,9 @@ def _build_parser() -> argparse.ArgumentParser:
943
971
 
944
972
  parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
945
973
  parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
974
+ parser_devices.add_argument(
975
+ "--json", action="store_true", help="Print a JSON array to stdout for scripting (hints go to stderr)"
976
+ )
946
977
  parser_devices.set_defaults(func=devices_command)
947
978
 
948
979
  parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
@@ -18,7 +18,7 @@ import json
18
18
  import re
19
19
  import subprocess
20
20
  import tempfile
21
- from dataclasses import dataclass
21
+ from dataclasses import asdict, dataclass
22
22
  from pathlib import Path
23
23
  from typing import Any, Dict, List, Optional
24
24
 
@@ -64,6 +64,19 @@ class Device:
64
64
  return self.state in ("booted", "shutdown")
65
65
  return self.state in ("booted", "connected")
66
66
 
67
+ def to_dict(self) -> Dict[str, Any]:
68
+ """Return a JSON-serializable view of this target.
69
+
70
+ Derived from the dataclass fields so a field added later is
71
+ carried into the payload automatically, plus the computed
72
+ ``is_ready`` that consumers would otherwise have to re-derive
73
+ from ``kind`` and ``state``.
74
+
75
+ Returns:
76
+ The declared fields in declaration order, then ``is_ready``.
77
+ """
78
+ return {**asdict(self), "is_ready": self.is_ready}
79
+
67
80
  def format(self) -> str:
68
81
  """Return one aligned listing row for the CLI."""
69
82
  os_part = f" ({self.os_version})" if self.os_version else ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.30.0
3
+ Version: 0.31.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- pythonnative/__init__.py,sha256=e-p6QNv7CzeBTJnU-SP2ccgdrU4jeVoS0R7sFvlg_S8,8680
1
+ pythonnative/__init__.py,sha256=NYhhv97vyRgN8SNgJM-O864UVk1rDOAg8lgja3EKvyc,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=1ZHvuqxW_K9MWn4_1RkVt_oCK-Y0F_bRNq_EYcG57Hc,37022
30
+ pythonnative/cli/pn.py,sha256=FCNAjbWrOWnm5rEE0n9k2Ys1AY3O11ZuCqV1vaq9-SM,38269
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
@@ -52,7 +52,7 @@ pythonnative/project/__init__.py,sha256=kAhWLONs53H1L7xVtczLmzYcQSOOkdRZTAJnIMhp
52
52
  pythonnative/project/android.py,sha256=ynRQwVvv1trFB2eFaUv38-48q4m0_Z4cTbAFEOiKnhE,19797
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
- pythonnative/project/devices.py,sha256=OkPzqaD6l4xNJer-rKzorAS9IgvsE87g0JK-2V8FIgE,9593
55
+ pythonnative/project/devices.py,sha256=zH0v95vOqWxXGdeTDljrZTA0RWBuU79v3zTQSaXkDh8,10106
56
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
@@ -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.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,,
119
+ pythonnative-0.31.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
120
+ pythonnative-0.31.0.dist-info/METADATA,sha256=XvvvqmJj-vvntCv003W8cuH9I7dSJ5SvSlM8KDHi1r0,11895
121
+ pythonnative-0.31.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
122
+ pythonnative-0.31.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
123
+ pythonnative-0.31.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
124
+ pythonnative-0.31.0.dist-info/RECORD,,