hte-cli 0.2.7__py3-none-any.whl → 0.2.9__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.
- hte_cli/cli.py +10 -7
- hte_cli/image_utils.py +62 -13
- {hte_cli-0.2.7.dist-info → hte_cli-0.2.9.dist-info}/METADATA +1 -1
- {hte_cli-0.2.7.dist-info → hte_cli-0.2.9.dist-info}/RECORD +6 -6
- {hte_cli-0.2.7.dist-info → hte_cli-0.2.9.dist-info}/WHEEL +0 -0
- {hte_cli-0.2.7.dist-info → hte_cli-0.2.9.dist-info}/entry_points.txt +0 -0
hte_cli/cli.py
CHANGED
|
@@ -323,22 +323,25 @@ def session_join(ctx, session_id: str, force_setup: bool):
|
|
|
323
323
|
parts = line.split(": ", 1)
|
|
324
324
|
if len(parts) == 2:
|
|
325
325
|
layer_id = parts[0][-8:] # Last 8 chars of layer ID
|
|
326
|
-
layer_status = parts[1]
|
|
327
|
-
# Show progress bars for Downloading/Extracting
|
|
326
|
+
layer_status = parts[1] # Don't truncate - keep full progress
|
|
327
|
+
# Show progress bars for Downloading/Extracting with MB info
|
|
328
328
|
if "Downloading" in layer_status or "Extracting" in layer_status:
|
|
329
|
-
|
|
329
|
+
# Keep progress: "[====> ] 10.5MB/50MB"
|
|
330
|
+
display_text = f"{layer_id}: {layer_status[:50]}"
|
|
330
331
|
elif "Pull complete" in layer_status:
|
|
331
332
|
display_text = f"{layer_id}: done"
|
|
332
333
|
elif "Download complete" in layer_status:
|
|
333
|
-
display_text = f"{layer_id}:
|
|
334
|
+
display_text = f"{layer_id}: download done"
|
|
334
335
|
elif "Already exists" in layer_status:
|
|
335
336
|
display_text = f"{layer_id}: cached"
|
|
336
337
|
elif "Waiting" in layer_status:
|
|
337
|
-
display_text = f"{layer_id}: waiting
|
|
338
|
+
display_text = f"{layer_id}: waiting"
|
|
339
|
+
elif "Verifying" in layer_status:
|
|
340
|
+
display_text = f"{layer_id}: verifying"
|
|
338
341
|
else:
|
|
339
|
-
display_text =
|
|
342
|
+
display_text = line[:55]
|
|
340
343
|
elif line.strip():
|
|
341
|
-
display_text = line[:
|
|
344
|
+
display_text = line[:55]
|
|
342
345
|
|
|
343
346
|
if display_text and display_text != last_status[0]:
|
|
344
347
|
last_status[0] = display_text
|
hte_cli/image_utils.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"""Docker image utilities for pre-pulling compose images."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
5
|
+
import pty
|
|
6
|
+
import re
|
|
7
|
+
import select
|
|
4
8
|
import subprocess
|
|
5
9
|
from collections.abc import Callable
|
|
6
10
|
|
|
@@ -61,32 +65,77 @@ def pull_image_with_progress(
|
|
|
61
65
|
on_complete: Callable[[str, bool], None] | None = None,
|
|
62
66
|
) -> bool:
|
|
63
67
|
"""
|
|
64
|
-
Pull a Docker image with progress callbacks.
|
|
68
|
+
Pull a Docker image with progress callbacks using PTY for real progress output.
|
|
65
69
|
|
|
66
70
|
Args:
|
|
67
71
|
image: Image name to pull
|
|
68
|
-
on_progress: Callback(image, status_line) called for each
|
|
72
|
+
on_progress: Callback(image, status_line) called for each progress update
|
|
69
73
|
on_complete: Callback(image, success) called when pull completes
|
|
70
74
|
|
|
71
75
|
Returns:
|
|
72
76
|
True if pull succeeded, False otherwise
|
|
73
77
|
"""
|
|
74
78
|
try:
|
|
79
|
+
# Use PTY to get real progress output from docker
|
|
80
|
+
master_fd, slave_fd = pty.openpty()
|
|
81
|
+
|
|
75
82
|
process = subprocess.Popen(
|
|
76
83
|
["docker", "pull", image],
|
|
77
|
-
stdout=
|
|
78
|
-
stderr=
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
stdout=slave_fd,
|
|
85
|
+
stderr=slave_fd,
|
|
86
|
+
stdin=slave_fd,
|
|
87
|
+
close_fds=True,
|
|
81
88
|
)
|
|
82
89
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
os.close(slave_fd) # Close slave in parent
|
|
91
|
+
|
|
92
|
+
# Read output from master with timeout
|
|
93
|
+
output_buffer = ""
|
|
94
|
+
# Regex to parse docker progress: "abc123: Downloading [===> ] 10.5MB/50MB"
|
|
95
|
+
progress_pattern = re.compile(
|
|
96
|
+
r"([a-f0-9]+):\s*(Downloading|Extracting|Verifying Checksum|Download complete|Pull complete|Already exists|Waiting)(?:\s+\[.*?\]\s+)?(\d+\.?\d*\s*[kMG]?B)?(?:/(\d+\.?\d*\s*[kMG]?B))?"
|
|
97
|
+
)
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
while True:
|
|
100
|
+
# Check if process is done
|
|
101
|
+
ret = process.poll()
|
|
102
|
+
if ret is not None:
|
|
103
|
+
# Read any remaining output
|
|
104
|
+
try:
|
|
105
|
+
while True:
|
|
106
|
+
ready, _, _ = select.select([master_fd], [], [], 0.1)
|
|
107
|
+
if not ready:
|
|
108
|
+
break
|
|
109
|
+
chunk = os.read(master_fd, 4096)
|
|
110
|
+
if not chunk:
|
|
111
|
+
break
|
|
112
|
+
except OSError:
|
|
113
|
+
pass
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
# Read available output
|
|
117
|
+
try:
|
|
118
|
+
ready, _, _ = select.select([master_fd], [], [], 0.1)
|
|
119
|
+
if ready:
|
|
120
|
+
chunk = os.read(master_fd, 4096)
|
|
121
|
+
if chunk:
|
|
122
|
+
output_buffer += chunk.decode("utf-8", errors="replace")
|
|
123
|
+
|
|
124
|
+
# Parse and report progress
|
|
125
|
+
# Docker uses carriage returns to update lines in place
|
|
126
|
+
lines = output_buffer.replace("\r", "\n").split("\n")
|
|
127
|
+
output_buffer = lines[-1] # Keep incomplete line
|
|
128
|
+
|
|
129
|
+
for line in lines[:-1]:
|
|
130
|
+
line = line.strip()
|
|
131
|
+
# Strip ANSI escape codes
|
|
132
|
+
line = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", line)
|
|
133
|
+
if line and on_progress:
|
|
134
|
+
on_progress(image, line)
|
|
135
|
+
except OSError:
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
os.close(master_fd)
|
|
90
139
|
success = process.returncode == 0
|
|
91
140
|
|
|
92
141
|
if on_complete:
|
|
@@ -94,7 +143,7 @@ def pull_image_with_progress(
|
|
|
94
143
|
|
|
95
144
|
return success
|
|
96
145
|
|
|
97
|
-
except (
|
|
146
|
+
except (FileNotFoundError, OSError) as e:
|
|
98
147
|
logger.error(f"Failed to pull {image}: {e}")
|
|
99
148
|
if on_complete:
|
|
100
149
|
on_complete(image, False)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
hte_cli/__init__.py,sha256=fDGXp-r8bIoLtlQnn5xJ_CpwMhonvk9bGjZQsjA2mDI,914
|
|
2
2
|
hte_cli/__main__.py,sha256=63n0gNGfskidWDU0aAIF2N8lylVCLYKVIkrN9QiORoo,107
|
|
3
3
|
hte_cli/api_client.py,sha256=m42kfFZS72Nu_VuDwxRsLNy4ziCcvgk7KNWBh9gwqy0,9257
|
|
4
|
-
hte_cli/cli.py,sha256=
|
|
4
|
+
hte_cli/cli.py,sha256=W9R_jHBLhLho2GyroKzCCg6EhBluCrFJdZ9zCaKFGuo,42745
|
|
5
5
|
hte_cli/config.py,sha256=42Xv__YMSeRLs2zhGukJkIXFKtnBtYCHnONfViGyt2g,3387
|
|
6
6
|
hte_cli/errors.py,sha256=1J5PpxcUKBu6XjigMMCPOq4Zc12tnv8LhAsiaVFWLQM,2762
|
|
7
7
|
hte_cli/events.py,sha256=Zn-mroqaLHNzdT4DFf8st1Qclglshihdc09dBfCN070,5522
|
|
8
|
-
hte_cli/image_utils.py,sha256=
|
|
8
|
+
hte_cli/image_utils.py,sha256=TLwJdswUQrSD2bQcAXW03R8j8WG2pbHzd12TWcE7zy4,6418
|
|
9
9
|
hte_cli/runner.py,sha256=DhC8FMjHwfLR193iP4thLDRZrNssYA9KH1WYKU2JKeg,13535
|
|
10
10
|
hte_cli/scorers.py,sha256=sFoPJePRt-K191-Ga4cVmrldruJclYXTOLkU_C9nCDI,6025
|
|
11
11
|
hte_cli/version_check.py,sha256=WVZyGy2XfAghQYdd2N9-0Qfg-7pgp9gt4761-PnmacI,1708
|
|
12
|
-
hte_cli-0.2.
|
|
13
|
-
hte_cli-0.2.
|
|
14
|
-
hte_cli-0.2.
|
|
15
|
-
hte_cli-0.2.
|
|
12
|
+
hte_cli-0.2.9.dist-info/METADATA,sha256=CoIpqVDlDBi7xzh6hp6iNd2U6aj5W6R9Z6oaJPz33FM,3767
|
|
13
|
+
hte_cli-0.2.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
hte_cli-0.2.9.dist-info/entry_points.txt,sha256=XbyEEi1H14DFAt0Kdl22e_IRVEGzimSzYSh5HlhKlFA,41
|
|
15
|
+
hte_cli-0.2.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|