portacode 1.4.15.dev6__py3-none-any.whl → 1.4.15.dev8__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.15.dev6'
32
- __version_tuple__ = version_tuple = (1, 4, 15, 'dev6')
31
+ __version__ = version = '1.4.15.dev8'
32
+ __version_tuple__ = version_tuple = (1, 4, 15, 'dev8')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import json
7
7
  import logging
8
+ import math
8
9
  import os
9
10
  import secrets
10
11
  import shlex
@@ -46,6 +47,9 @@ UNIT_DIR = Path("/etc/systemd/system")
46
47
  _MANAGED_CONTAINERS_CACHE_TTL_S = 30.0
47
48
  _MANAGED_CONTAINERS_CACHE: Dict[str, Any] = {"timestamp": 0.0, "summary": None}
48
49
  _MANAGED_CONTAINERS_CACHE_LOCK = threading.Lock()
50
+ _STARTUP_TEMPLATES: List[str] | None = None
51
+ _STARTUP_TEMPLATE_KEY: tuple[Any, ...] | None = None
52
+ _STARTUP_TEMPLATES_LOCK = threading.Lock()
49
53
 
50
54
  ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
51
55
 
@@ -178,6 +182,70 @@ def _list_templates(client: Any, node: str, storages: Iterable[Dict[str, Any]])
178
182
  return templates
179
183
 
180
184
 
185
+ def _template_key(config: Dict[str, Any]) -> tuple[Any, ...]:
186
+ return (
187
+ config.get("host"),
188
+ config.get("node"),
189
+ config.get("user"),
190
+ config.get("token_name"),
191
+ config.get("verify_ssl"),
192
+ )
193
+
194
+
195
+ def _build_proxmox_client_from_config(config: Dict[str, Any]):
196
+ user = config.get("user")
197
+ token_name = config.get("token_name")
198
+ token_value = config.get("token_value")
199
+ if not user or not token_name or not token_value:
200
+ raise RuntimeError("Proxmox API credentials are missing")
201
+ ProxmoxAPI = _ensure_proxmoxer()
202
+ return ProxmoxAPI(
203
+ config.get("host", DEFAULT_HOST),
204
+ user=user,
205
+ token_name=token_name,
206
+ token_value=token_value,
207
+ verify_ssl=config.get("verify_ssl", False),
208
+ timeout=30,
209
+ )
210
+
211
+
212
+ def _ensure_startup_templates(config: Dict[str, Any]) -> None:
213
+ global _STARTUP_TEMPLATES, _STARTUP_TEMPLATE_KEY
214
+ if not config:
215
+ with _STARTUP_TEMPLATES_LOCK:
216
+ _STARTUP_TEMPLATES = []
217
+ _STARTUP_TEMPLATE_KEY = None
218
+ return
219
+ with _STARTUP_TEMPLATES_LOCK:
220
+ if _STARTUP_TEMPLATE_KEY == _template_key(config) and _STARTUP_TEMPLATES is not None:
221
+ return
222
+ key = _template_key(config)
223
+ templates: List[str] = []
224
+ try:
225
+ client = _build_proxmox_client_from_config(config)
226
+ node = config.get("node") or _pick_node(client)
227
+ storages = client.nodes(node).storage.get()
228
+ templates = _list_templates(client, node, storages)
229
+ except Exception as exc:
230
+ logger.warning("Unable to refresh Proxmox templates: %s", exc)
231
+ with _STARTUP_TEMPLATES_LOCK:
232
+ _STARTUP_TEMPLATES = list(templates)
233
+ _STARTUP_TEMPLATE_KEY = key
234
+
235
+
236
+ def _get_startup_templates(config: Dict[str, Any]) -> List[str]:
237
+ _ensure_startup_templates(config)
238
+ with _STARTUP_TEMPLATES_LOCK:
239
+ return list(_STARTUP_TEMPLATES or [])
240
+
241
+
242
+ def _clear_startup_templates() -> None:
243
+ global _STARTUP_TEMPLATES, _STARTUP_TEMPLATE_KEY
244
+ with _STARTUP_TEMPLATES_LOCK:
245
+ _STARTUP_TEMPLATES = None
246
+ _STARTUP_TEMPLATE_KEY = None
247
+
248
+
181
249
  def _pick_storage(storages: Iterable[Dict[str, Any]]) -> str:
182
250
  candidates = [s for s in storages if "rootdir" in s.get("content", "") and s.get("avail", 0) > 0]
183
251
  if not candidates:
@@ -596,7 +664,7 @@ def _remove_container_record(vmid: int) -> None:
596
664
 
597
665
 
598
666
  def _build_container_payload(message: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]:
599
- templates = config.get("templates") or []
667
+ templates = _get_startup_templates(config)
600
668
  default_template = templates[0] if templates else ""
601
669
  template = message.get("template") or default_template
602
670
  if not template:
@@ -1024,13 +1092,14 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
1024
1092
  "user": config.get("user"),
1025
1093
  "token_name": config.get("token_name"),
1026
1094
  "default_storage": config.get("default_storage"),
1027
- "templates": config.get("templates") or [],
1095
+ "templates": _get_startup_templates(config),
1028
1096
  "last_verified": config.get("last_verified"),
1029
1097
  "network": base_network,
1030
1098
  }
1031
1099
 
1032
1100
 
1033
1101
  def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl: bool = False) -> Dict[str, Any]:
1102
+ _clear_startup_templates()
1034
1103
  ProxmoxAPI = _ensure_proxmoxer()
1035
1104
  user, token_name = _parse_token(token_identifier)
1036
1105
  client = ProxmoxAPI(
@@ -1066,12 +1135,12 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
1066
1135
  "token_value": token_value,
1067
1136
  "verify_ssl": verify_ssl,
1068
1137
  "default_storage": default_storage,
1069
- "templates": templates,
1070
1138
  "last_verified": datetime.utcnow().isoformat() + "Z",
1071
1139
  "network": network,
1072
1140
  "node_status": status,
1073
1141
  }
1074
1142
  _save_config(config)
1143
+ _ensure_startup_templates(config)
1075
1144
  snapshot = build_snapshot(config)
1076
1145
  snapshot["node_status"] = status
1077
1146
  snapshot["managed_containers"] = _get_managed_containers_summary(force=True)
@@ -1091,6 +1160,7 @@ def revert_infrastructure() -> Dict[str, Any]:
1091
1160
  _revert_bridge()
1092
1161
  if CONFIG_PATH.exists():
1093
1162
  CONFIG_PATH.unlink()
1163
+ _clear_startup_templates()
1094
1164
  snapshot = build_snapshot({})
1095
1165
  snapshot["network"] = snapshot.get("network", {})
1096
1166
  snapshot["network"]["applied"] = False
@@ -1121,7 +1191,7 @@ def _instantiate_container(proxmox: Any, node: str, payload: Dict[str, Any]) ->
1121
1191
  memory=int(payload["ram_mib"]),
1122
1192
  swap=int(payload.get("swap_mb", 0)),
1123
1193
  cores=max(int(payload.get("cores", 1)), 1),
1124
- cpuunits=int(payload.get("cpuunits", 256)),
1194
+ cpulimit=float(payload.get("cpulimit", payload.get("cpus", 1))),
1125
1195
  net0=payload["net0"],
1126
1196
  unprivileged=int(payload.get("unprivileged", 1)),
1127
1197
  description=payload.get("description", MANAGED_MARKER),
@@ -1235,7 +1305,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
1235
1305
  proxmox = _connect_proxmox(config)
1236
1306
  node = config.get("node") or DEFAULT_NODE_NAME
1237
1307
  payload = _build_container_payload(message, config)
1238
- payload["cpuunits"] = max(int(payload["cpus"] * 1024), 10)
1308
+ payload["cpulimit"] = float(payload["cpus"])
1309
+ payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
1239
1310
  payload["memory"] = int(payload["ram_mib"])
1240
1311
  payload["node"] = node
1241
1312
  logger.debug(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.15.dev6
3
+ Version: 1.4.15.dev8
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=VfdRURf59q24xrxCKaIpQKaRdnFEAgLbc9xW5UpNolM,719
4
+ portacode/_version.py,sha256=wz7cM9xrg98SOlEfHeMTG7xaJCZ3Le5OiMq1M5iYGqc,719
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=dLKV2Epd7VCL89EmV_DalUOsxsTuLbnJopjPRbmTzUg,62370
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=QALeGkuvNdhpLidrEzZOVgowAXh3L2vk4YbdssGIHKk,64781
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.15.dev6.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.15.dev8.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.15.dev6.dist-info/METADATA,sha256=KusMfGXKhY6aEbqR2olZ2VDfrPYPOvHlCzhpck5myKk,13051
95
- portacode-1.4.15.dev6.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
- portacode-1.4.15.dev6.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.15.dev6.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.15.dev6.dist-info/RECORD,,
94
+ portacode-1.4.15.dev8.dist-info/METADATA,sha256=KL7PnPejNsSu4P3ATAsmYLVmFpw8kGml7RjJypiLvHQ,13051
95
+ portacode-1.4.15.dev8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.15.dev8.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.15.dev8.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.15.dev8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5