portacode 1.4.15.dev7__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 +2 -2
- portacode/connection/handlers/proxmox_infra.py +106 -135
- {portacode-1.4.15.dev7.dist-info → portacode-1.4.15.dev8.dist-info}/METADATA +1 -1
- {portacode-1.4.15.dev7.dist-info → portacode-1.4.15.dev8.dist-info}/RECORD +8 -8
- {portacode-1.4.15.dev7.dist-info → portacode-1.4.15.dev8.dist-info}/WHEEL +0 -0
- {portacode-1.4.15.dev7.dist-info → portacode-1.4.15.dev8.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.15.dev7.dist-info → portacode-1.4.15.dev8.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.15.dev7.dist-info → portacode-1.4.15.dev8.dist-info}/top_level.txt +0 -0
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 15, '
|
|
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
|
|
@@ -47,6 +47,9 @@ UNIT_DIR = Path("/etc/systemd/system")
|
|
|
47
47
|
_MANAGED_CONTAINERS_CACHE_TTL_S = 30.0
|
|
48
48
|
_MANAGED_CONTAINERS_CACHE: Dict[str, Any] = {"timestamp": 0.0, "summary": None}
|
|
49
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()
|
|
50
53
|
|
|
51
54
|
ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
|
|
52
55
|
|
|
@@ -179,6 +182,70 @@ def _list_templates(client: Any, node: str, storages: Iterable[Dict[str, Any]])
|
|
|
179
182
|
return templates
|
|
180
183
|
|
|
181
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
|
+
|
|
182
249
|
def _pick_storage(storages: Iterable[Dict[str, Any]]) -> str:
|
|
183
250
|
candidates = [s for s in storages if "rootdir" in s.get("content", "") and s.get("avail", 0) > 0]
|
|
184
251
|
if not candidates:
|
|
@@ -437,119 +504,48 @@ def _friendly_step_label(step_name: str) -> str:
|
|
|
437
504
|
return normalized.capitalize()
|
|
438
505
|
|
|
439
506
|
|
|
440
|
-
_PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
|
|
441
|
-
"apt": {
|
|
442
|
-
"update_cmd": "apt-get update -y",
|
|
443
|
-
"update_step_name": "apt_update",
|
|
444
|
-
"install_cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
|
|
445
|
-
"install_step_name": "install_deps",
|
|
446
|
-
"update_retries": 4,
|
|
447
|
-
"install_retries": 5,
|
|
448
|
-
},
|
|
449
|
-
"dnf": {
|
|
450
|
-
"update_cmd": "dnf check-update || true",
|
|
451
|
-
"update_step_name": "dnf_update",
|
|
452
|
-
"install_cmd": "dnf install -y python3 python3-pip sudo",
|
|
453
|
-
"install_step_name": "install_deps",
|
|
454
|
-
"update_retries": 3,
|
|
455
|
-
"install_retries": 5,
|
|
456
|
-
},
|
|
457
|
-
"yum": {
|
|
458
|
-
"update_cmd": "yum makecache",
|
|
459
|
-
"update_step_name": "yum_update",
|
|
460
|
-
"install_cmd": "yum install -y python3 python3-pip sudo",
|
|
461
|
-
"install_step_name": "install_deps",
|
|
462
|
-
"update_retries": 3,
|
|
463
|
-
"install_retries": 5,
|
|
464
|
-
},
|
|
465
|
-
"apk": {
|
|
466
|
-
"update_cmd": "apk update",
|
|
467
|
-
"update_step_name": "apk_update",
|
|
468
|
-
"install_cmd": "apk add --no-cache python3 py3-pip sudo",
|
|
469
|
-
"install_step_name": "install_deps",
|
|
470
|
-
"update_retries": 3,
|
|
471
|
-
"install_retries": 5,
|
|
472
|
-
},
|
|
473
|
-
"pacman": {
|
|
474
|
-
"update_cmd": "pacman -Sy --noconfirm",
|
|
475
|
-
"update_step_name": "pacman_update",
|
|
476
|
-
"install_cmd": "pacman -S --noconfirm python python-pip sudo",
|
|
477
|
-
"install_step_name": "install_deps",
|
|
478
|
-
"update_retries": 3,
|
|
479
|
-
"install_retries": 5,
|
|
480
|
-
},
|
|
481
|
-
"zypper": {
|
|
482
|
-
"update_cmd": "zypper refresh",
|
|
483
|
-
"update_step_name": "zypper_update",
|
|
484
|
-
"install_cmd": "zypper install -y python3 python3-pip sudo",
|
|
485
|
-
"install_step_name": "install_deps",
|
|
486
|
-
"update_retries": 3,
|
|
487
|
-
"install_retries": 5,
|
|
488
|
-
},
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
_UPDATE_RETRY_ON = [
|
|
492
|
-
"Temporary failure resolving",
|
|
493
|
-
"Could not resolve",
|
|
494
|
-
"Failed to fetch",
|
|
495
|
-
]
|
|
496
|
-
|
|
497
|
-
_INSTALL_RETRY_ON = [
|
|
498
|
-
"lock-frontend",
|
|
499
|
-
"Unable to acquire the dpkg frontend lock",
|
|
500
|
-
"Temporary failure resolving",
|
|
501
|
-
"Could not resolve",
|
|
502
|
-
"Failed to fetch",
|
|
503
|
-
]
|
|
504
|
-
|
|
505
|
-
|
|
506
507
|
def _build_bootstrap_steps(
|
|
507
508
|
user: str,
|
|
508
509
|
password: str,
|
|
509
510
|
ssh_key: str,
|
|
510
511
|
include_portacode_connect: bool = True,
|
|
511
|
-
package_manager: str = "apt",
|
|
512
512
|
) -> List[Dict[str, Any]]:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
"
|
|
521
|
-
"
|
|
522
|
-
"
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
"
|
|
532
|
-
"
|
|
533
|
-
"
|
|
534
|
-
"
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
]
|
|
542
|
-
)
|
|
513
|
+
steps = [
|
|
514
|
+
{
|
|
515
|
+
"name": "apt_update",
|
|
516
|
+
"cmd": "apt-get update -y",
|
|
517
|
+
"retries": 4,
|
|
518
|
+
"retry_delay_s": 5,
|
|
519
|
+
"retry_on": [
|
|
520
|
+
"Temporary failure resolving",
|
|
521
|
+
"Could not resolve",
|
|
522
|
+
"Failed to fetch",
|
|
523
|
+
],
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
"name": "install_deps",
|
|
527
|
+
"cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
|
|
528
|
+
"retries": 5,
|
|
529
|
+
"retry_delay_s": 5,
|
|
530
|
+
"retry_on": [
|
|
531
|
+
"lock-frontend",
|
|
532
|
+
"Unable to acquire the dpkg frontend lock",
|
|
533
|
+
"Temporary failure resolving",
|
|
534
|
+
"Could not resolve",
|
|
535
|
+
"Failed to fetch",
|
|
536
|
+
],
|
|
537
|
+
},
|
|
538
|
+
{"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
|
|
539
|
+
{"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
|
|
540
|
+
]
|
|
543
541
|
if password:
|
|
544
542
|
steps.append({"name": "set_password", "cmd": f"echo '{user}:{password}' | chpasswd", "retries": 0})
|
|
545
543
|
if ssh_key:
|
|
546
|
-
steps.append(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
)
|
|
544
|
+
steps.append({
|
|
545
|
+
"name": "add_ssh_key",
|
|
546
|
+
"cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
|
|
547
|
+
"retries": 0,
|
|
548
|
+
})
|
|
553
549
|
steps.extend(
|
|
554
550
|
[
|
|
555
551
|
{"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
|
|
@@ -561,24 +557,6 @@ def _build_bootstrap_steps(
|
|
|
561
557
|
return steps
|
|
562
558
|
|
|
563
559
|
|
|
564
|
-
def _detect_package_manager(vmid: int) -> str:
|
|
565
|
-
candidates = [
|
|
566
|
-
("apt", "apt-get"),
|
|
567
|
-
("dnf", "dnf"),
|
|
568
|
-
("yum", "yum"),
|
|
569
|
-
("apk", "apk"),
|
|
570
|
-
("pacman", "pacman"),
|
|
571
|
-
("zypper", "zypper"),
|
|
572
|
-
]
|
|
573
|
-
for name, binary in candidates:
|
|
574
|
-
res = _run_pct(vmid, f"command -v {binary} >/dev/null 2>&1")
|
|
575
|
-
if res.get("returncode") == 0:
|
|
576
|
-
logger.debug("Detected package manager %s inside container %s", name, vmid)
|
|
577
|
-
return name
|
|
578
|
-
logger.warning("Unable to detect package manager inside container %s; defaulting to apt", vmid)
|
|
579
|
-
return "apt"
|
|
580
|
-
|
|
581
|
-
|
|
582
560
|
def _get_storage_type(storages: Iterable[Dict[str, Any]], storage_name: str) -> str:
|
|
583
561
|
for entry in storages:
|
|
584
562
|
if entry.get("storage") == storage_name:
|
|
@@ -686,7 +664,7 @@ def _remove_container_record(vmid: int) -> None:
|
|
|
686
664
|
|
|
687
665
|
|
|
688
666
|
def _build_container_payload(message: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]:
|
|
689
|
-
templates = config
|
|
667
|
+
templates = _get_startup_templates(config)
|
|
690
668
|
default_template = templates[0] if templates else ""
|
|
691
669
|
template = message.get("template") or default_template
|
|
692
670
|
if not template:
|
|
@@ -1056,16 +1034,7 @@ def _bootstrap_portacode(
|
|
|
1056
1034
|
total_steps: Optional[int] = None,
|
|
1057
1035
|
default_public_key: Optional[str] = None,
|
|
1058
1036
|
) -> Tuple[str, List[Dict[str, Any]]]:
|
|
1059
|
-
if steps is not None
|
|
1060
|
-
actual_steps = steps
|
|
1061
|
-
else:
|
|
1062
|
-
package_manager = _detect_package_manager(vmid)
|
|
1063
|
-
actual_steps = _build_bootstrap_steps(
|
|
1064
|
-
user,
|
|
1065
|
-
password,
|
|
1066
|
-
ssh_key,
|
|
1067
|
-
package_manager=package_manager,
|
|
1068
|
-
)
|
|
1037
|
+
actual_steps = steps if steps is not None else _build_bootstrap_steps(user, password, ssh_key)
|
|
1069
1038
|
results, ok = _run_setup_steps(
|
|
1070
1039
|
vmid,
|
|
1071
1040
|
actual_steps,
|
|
@@ -1123,13 +1092,14 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1123
1092
|
"user": config.get("user"),
|
|
1124
1093
|
"token_name": config.get("token_name"),
|
|
1125
1094
|
"default_storage": config.get("default_storage"),
|
|
1126
|
-
"templates": config
|
|
1095
|
+
"templates": _get_startup_templates(config),
|
|
1127
1096
|
"last_verified": config.get("last_verified"),
|
|
1128
1097
|
"network": base_network,
|
|
1129
1098
|
}
|
|
1130
1099
|
|
|
1131
1100
|
|
|
1132
1101
|
def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl: bool = False) -> Dict[str, Any]:
|
|
1102
|
+
_clear_startup_templates()
|
|
1133
1103
|
ProxmoxAPI = _ensure_proxmoxer()
|
|
1134
1104
|
user, token_name = _parse_token(token_identifier)
|
|
1135
1105
|
client = ProxmoxAPI(
|
|
@@ -1165,12 +1135,12 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
|
|
|
1165
1135
|
"token_value": token_value,
|
|
1166
1136
|
"verify_ssl": verify_ssl,
|
|
1167
1137
|
"default_storage": default_storage,
|
|
1168
|
-
"templates": templates,
|
|
1169
1138
|
"last_verified": datetime.utcnow().isoformat() + "Z",
|
|
1170
1139
|
"network": network,
|
|
1171
1140
|
"node_status": status,
|
|
1172
1141
|
}
|
|
1173
1142
|
_save_config(config)
|
|
1143
|
+
_ensure_startup_templates(config)
|
|
1174
1144
|
snapshot = build_snapshot(config)
|
|
1175
1145
|
snapshot["node_status"] = status
|
|
1176
1146
|
snapshot["managed_containers"] = _get_managed_containers_summary(force=True)
|
|
@@ -1190,6 +1160,7 @@ def revert_infrastructure() -> Dict[str, Any]:
|
|
|
1190
1160
|
_revert_bridge()
|
|
1191
1161
|
if CONFIG_PATH.exists():
|
|
1192
1162
|
CONFIG_PATH.unlink()
|
|
1163
|
+
_clear_startup_templates()
|
|
1193
1164
|
snapshot = build_snapshot({})
|
|
1194
1165
|
snapshot["network"] = snapshot.get("network", {})
|
|
1195
1166
|
snapshot["network"]["applied"] = False
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
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.
|
|
95
|
-
portacode-1.4.15.
|
|
96
|
-
portacode-1.4.15.
|
|
97
|
-
portacode-1.4.15.
|
|
98
|
-
portacode-1.4.15.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|