portacode 1.4.16.dev8__py3-none-any.whl → 1.4.16.dev10__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.
portacode/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.4.16.dev8'
32
- __version_tuple__ = version_tuple = (1, 4, 16, 'dev8')
31
+ __version__ = version = '1.4.16.dev10'
32
+ __version_tuple__ = version_tuple = (1, 4, 16, 'dev10')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -259,21 +259,157 @@ def _bytes_to_gib(value: Any) -> int:
259
259
  return int(round(_normalize_bytes(value) / (1024**3)))
260
260
 
261
261
 
262
+ def _normalize_storage_name(name: Any) -> str:
263
+ if not name:
264
+ return ""
265
+ return str(name).strip().lower()
266
+
267
+ def _size_token_to_gib(token: str) -> float:
268
+ match = re.match(r"^\s*([0-9]+(?:\.[0-9]+)?)\s*([KMGTP])?([iI]?[bB])?\s*$", token)
269
+ if not match:
270
+ return 0.0
271
+ number = float(match.group(1))
272
+ unit = (match.group(2) or "").upper()
273
+ scale = {
274
+ "": 1,
275
+ "K": 1024 ** 1,
276
+ "M": 1024 ** 2,
277
+ "G": 1024 ** 3,
278
+ "T": 1024 ** 4,
279
+ "P": 1024 ** 5,
280
+ }.get(unit, 1)
281
+ return (number * scale) / 1024**3
282
+
283
+
284
+ def _extract_size_gib(value: Any) -> float:
285
+ if not value:
286
+ return 0.0
287
+ text = str(value)
288
+ for part in text.split(","):
289
+ if "size=" in part:
290
+ token = part.split("=", 1)[1]
291
+ return _size_token_to_gib(token)
292
+ return _size_token_to_gib(text)
293
+
294
+
262
295
  def _extract_storage(value: Any) -> str:
263
296
  if not value:
264
297
  return "unknown"
265
- text = str(value).strip()
266
- if not text:
267
- return "unknown"
268
- primary = text.split(",", 1)[0]
269
- storage = primary.split(":", 1)[0].strip()
270
- return storage or "unknown"
298
+ text = str(value)
299
+ if ":" in text:
300
+ return text.split(":", 1)[0].strip() or "unknown"
301
+ return text.strip() or "unknown"
302
+
303
+
304
+ def _storage_from_lxc(cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
305
+ rootfs = cfg.get("rootfs") or entry.get("rootfs")
306
+ storage = _extract_storage(rootfs)
307
+ if storage != "unknown":
308
+ return storage
309
+ for idx in range(0, 10):
310
+ mp_value = cfg.get(f"mp{idx}")
311
+ storage = _extract_storage(mp_value)
312
+ if storage != "unknown":
313
+ return storage
314
+ return "unknown"
315
+
316
+
317
+ def _storage_from_qemu(cfg: Dict[str, Any]) -> str:
318
+ preferred_keys: List[str] = []
319
+ for prefix in ("scsi", "virtio", "sata", "ide"):
320
+ preferred_keys.extend(f"{prefix}{idx}" for idx in range(0, 6))
321
+ seen: Set[str] = set()
322
+ for key in preferred_keys:
323
+ value = cfg.get(key)
324
+ if value is None:
325
+ continue
326
+ seen.add(key)
327
+ text = str(value)
328
+ if "media=cdrom" in text or "cloudinit" in text:
329
+ continue
330
+ storage = _extract_storage(text)
331
+ if storage != "unknown":
332
+ return storage
333
+ for key in sorted(cfg.keys()):
334
+ if key in seen:
335
+ continue
336
+ if not any(key.startswith(prefix) for prefix in ("scsi", "virtio", "sata", "ide")):
337
+ continue
338
+ value = cfg.get(key)
339
+ if value is None:
340
+ continue
341
+ text = str(value)
342
+ if "media=cdrom" in text or "cloudinit" in text:
343
+ continue
344
+ storage = _extract_storage(text)
345
+ if storage != "unknown":
346
+ return storage
347
+ for key in ("efidisk0", "tpmstate0"):
348
+ storage = _extract_storage(cfg.get(key))
349
+ if storage != "unknown":
350
+ return storage
351
+ return "unknown"
352
+
353
+
354
+ def _primary_lxc_disk(cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
355
+ return str(cfg.get("rootfs") or entry.get("rootfs") or "")
356
+
357
+
358
+ def _primary_qemu_disk(cfg: Dict[str, Any]) -> str:
359
+ preferred_keys: List[str] = []
360
+ for prefix in ("scsi", "virtio", "sata", "ide"):
361
+ preferred_keys.extend(f"{prefix}{idx}" for idx in range(0, 6))
362
+ seen: Set[str] = set()
363
+ for key in preferred_keys:
364
+ value = cfg.get(key)
365
+ if value is None:
366
+ continue
367
+ seen.add(key)
368
+ text = str(value)
369
+ if "media=cdrom" in text or "cloudinit" in text:
370
+ continue
371
+ return text
372
+ for key in sorted(cfg.keys()):
373
+ if key in seen:
374
+ continue
375
+ if not any(key.startswith(prefix) for prefix in ("scsi", "virtio", "sata", "ide")):
376
+ continue
377
+ value = cfg.get(key)
378
+ if value is None:
379
+ continue
380
+ text = str(value)
381
+ if "media=cdrom" in text or "cloudinit" in text:
382
+ continue
383
+ return text
384
+ return ""
271
385
 
272
386
 
273
- def _normalize_storage_name(name: Any) -> str:
274
- if not name:
275
- return ""
276
- return str(name).strip().lower()
387
+ def _pick_storage(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
388
+ storage = _extract_storage(cfg.get("storage") or entry.get("storage"))
389
+ if storage != "unknown":
390
+ return storage
391
+ if kind == "lxc":
392
+ return _storage_from_lxc(cfg, entry)
393
+ return _storage_from_qemu(cfg)
394
+
395
+
396
+ def _pick_disk_gib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
397
+ if kind == "lxc":
398
+ size = _extract_size_gib(_primary_lxc_disk(cfg, entry))
399
+ if size:
400
+ return size
401
+ else:
402
+ size = _extract_size_gib(_primary_qemu_disk(cfg))
403
+ if size:
404
+ return size
405
+ for candidate in (entry.get("maxdisk"), entry.get("disk")):
406
+ if candidate in {None, 0}:
407
+ continue
408
+ return _normalize_bytes(candidate) / 1024**3
409
+ cfg_disk = cfg.get("disk")
410
+ if cfg_disk not in (None, 0):
411
+ return _normalize_bytes(cfg_disk) / 1024**3
412
+ return 0.0
277
413
 
278
414
 
279
415
  def _parse_bool_flag(value: Any) -> bool:
@@ -475,44 +611,16 @@ def _extract_host_totals(node_status: Dict[str, Any] | None) -> Tuple[Optional[i
475
611
  return host_ram, host_disk, host_cpu
476
612
 
477
613
 
478
- def _parse_disk_reference(value: Any) -> Tuple[str | None, int]:
479
- if not value:
480
- return None, 0
481
- text = str(value)
482
- primary = text.split(",", 1)[0]
483
- if ":" not in primary:
484
- return primary.strip() or None, 0
485
- storage_name, size_part = primary.split(":", 1)
486
- storage_name = storage_name.strip() or None
487
- size_text = size_part.strip()
488
- if not size_text:
489
- return storage_name, 0
490
- unit = size_text[-1].upper()
491
- number = size_text[:-1] if unit in {"G", "M"} else size_text
492
- try:
493
- value_num = float(number)
494
- except ValueError:
495
- return storage_name, 0
496
- if unit == "M":
497
- gib = value_num / 1024.0
498
- else:
499
- gib = value_num
500
- return storage_name, int(round(gib))
501
-
502
-
503
614
  def _build_unmanaged_container_entry(
504
615
  ct: Dict[str, Any],
505
616
  cfg: Dict[str, Any],
506
617
  vmid: str,
507
618
  default_storage: str | None,
508
619
  *,
509
- disk_gib_override: int | None = None,
510
- storage_override: str | None = None,
511
620
  entry_type: str = "lxc",
512
621
  ) -> Dict[str, Any]:
513
622
  ram_mib = _to_int(cfg.get("memory")) or _bytes_to_mib(ct.get("maxmem"))
514
- disk_source = cfg.get("disk") or ct.get("disk") or ct.get("maxdisk")
515
- disk_gib = disk_gib_override if disk_gib_override is not None else _bytes_to_gib(disk_source)
623
+ disk_gib = int(round(_pick_disk_gib(entry_type, cfg, ct)))
516
624
  cpu_share = _to_float(
517
625
  cfg.get("cpulimit")
518
626
  or cfg.get("cpus")
@@ -521,10 +629,7 @@ def _build_unmanaged_container_entry(
521
629
  or ct.get("cpu")
522
630
  )
523
631
  hostname = ct.get("name") or cfg.get("hostname") or f"ct{vmid}"
524
- storage_source = (
525
- storage_override or cfg.get("storage") or ct.get("storage") or ct.get("rootfs")
526
- )
527
- storage = _extract_storage(storage_source)
632
+ storage = _pick_storage(entry_type, cfg, ct)
528
633
  status = (ct.get("status") or "unknown").lower()
529
634
  reserved = _parse_bool_flag(cfg.get("onboot"))
530
635
  storage_matches_default = _storage_matches_default(storage, default_storage)
@@ -563,33 +668,10 @@ def _get_storage_snapshot(proxmox: Any, node: str, storage_name: str | None) ->
563
668
  }
564
669
 
565
670
 
566
- def _extract_qemu_disk_info(cfg: Dict[str, Any]) -> Tuple[str | None, int]:
567
- disk_keys = sorted(
568
- key
569
- for key in cfg.keys()
570
- if (
571
- key.startswith("ide")
572
- or key.startswith("sata")
573
- or key.startswith("scsi")
574
- or key.startswith("virtio")
575
- or key.startswith("efidisk")
576
- or key.startswith("raid")
577
- )
578
- and cfg.get(key)
579
- )
580
- for key in disk_keys:
581
- storage_name, size_gib = _parse_disk_reference(cfg.get(key))
582
- if storage_name or size_gib:
583
- return storage_name, size_gib
584
- return None, 0
585
-
586
-
587
671
  def _storage_matches_default(storage_name: Any, default_storage: str | None) -> bool:
588
672
  if not default_storage:
589
673
  return True
590
- normalized_storage = _normalize_storage_name(_extract_storage(storage_name))
591
- normalized_default = _normalize_storage_name(_extract_storage(default_storage))
592
- return normalized_storage == normalized_default
674
+ return _normalize_storage_name(storage_name) == _normalize_storage_name(default_storage)
593
675
 
594
676
 
595
677
  def _build_managed_containers_summary(
@@ -732,15 +814,12 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
732
814
  description = (cfg.get("description") or "")
733
815
  if MANAGED_MARKER in description:
734
816
  continue
735
- storage_name, disk_gib_override = _extract_qemu_disk_info(cfg)
736
817
  unmanaged.append(
737
818
  _build_unmanaged_container_entry(
738
819
  vm,
739
820
  cfg,
740
821
  vmid_key,
741
822
  default_storage,
742
- disk_gib_override=disk_gib_override,
743
- storage_override=storage_name,
744
823
  entry_type="qemu",
745
824
  )
746
825
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.16.dev8
3
+ Version: 1.4.16.dev10
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -1,7 +1,7 @@
1
1
  portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
2
2
  portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
3
3
  portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
4
- portacode/_version.py,sha256=VGaY-LcOZhWVvp6-lz1Mul_hv8BD77OfZQu3NhYJfE8,719
4
+ portacode/_version.py,sha256=xQIvOHyr77yXKzNXQJL1rE01nrJdvUnM7LX3jaSU684,721
5
5
  portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
@@ -22,7 +22,7 @@ portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZb
22
22
  portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
23
23
  portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
24
24
  portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
25
- portacode/connection/handlers/proxmox_infra.py,sha256=CDrPWs-WLVMd95AKRukeUwZkW4J0kcWh3Vb3oLNNDCU,84394
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=5XUUzr24bs-QOMpuVPq071ZDAmhadKiMs2E-wO62kMw,86709
26
26
  portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
27
27
  portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
28
28
  portacode/connection/handlers/system_handlers.py,sha256=fr12QpOr_Z8KYGUU-AYrTQwRPAcrLK85hvj3SEq1Kw8,14757
@@ -65,7 +65,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
65
65
  portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
66
66
  portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
67
67
  portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
68
- portacode-1.4.16.dev8.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.16.dev10.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
69
69
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
70
70
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
71
71
  test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
@@ -91,8 +91,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
91
91
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
92
92
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
93
93
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
94
- portacode-1.4.16.dev8.dist-info/METADATA,sha256=oXg32kxrBFgBp8Che47OV2D20d5zz0Vdyc517TAdzCU,13051
95
- portacode-1.4.16.dev8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.16.dev8.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.16.dev8.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.16.dev8.dist-info/RECORD,,
94
+ portacode-1.4.16.dev10.dist-info/METADATA,sha256=CYVCSXxYLiRseul0OVvOLRWWp2-Lv1u737bm-qT1hVk,13052
95
+ portacode-1.4.16.dev10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.16.dev10.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.16.dev10.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.16.dev10.dist-info/RECORD,,