Skip to content

Commit 9ef67f5

Browse files
committed
refactor: remove debug logging, fix unused imports and SDK/CLI boundary, ruff
1 parent 423cdc7 commit 9ef67f5

7 files changed

Lines changed: 78 additions & 138 deletions

File tree

centml/cli/shell.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from centml.sdk.api import get_centml_client
1111
from centml.sdk.config import settings
1212
from centml.sdk.shell import ShellError
13-
from centml.sdk.shell.session import build_ws_url, exec_session, interactive_session, resolve_pod, setup_debug_log
13+
from centml.sdk.shell.session import build_ws_url, exec_session, interactive_session, resolve_pod
1414

1515

1616
@click.command(help="Open an interactive shell to a deployment pod")
@@ -19,7 +19,6 @@
1919
@click.option("--shell", "shell_type", default=None, type=click.Choice(["bash", "sh", "zsh"]), help="Shell type")
2020
@handle_exception
2121
def shell(deployment_id, pod, shell_type):
22-
setup_debug_log()
2322
if not sys.stdin.isatty():
2423
raise click.ClickException("Interactive shell requires a terminal (TTY)")
2524

@@ -29,7 +28,7 @@ def shell(deployment_id, pod, shell_type):
2928
except ShellError as exc:
3029
raise click.ClickException(str(exc)) from exc
3130
if warning:
32-
click.echo(warning, err=True)
31+
click.echo(f"{warning} Use --pod to specify a different pod.", err=True)
3332

3433
ws_url = build_ws_url(settings.CENTML_PLATFORM_API_URL, deployment_id, pod_name, shell_type)
3534
token = auth.get_centml_token()
@@ -44,14 +43,13 @@ def shell(deployment_id, pod, shell_type):
4443
@click.option("--shell", "shell_type", default=None, type=click.Choice(["bash", "sh", "zsh"]), help="Shell type")
4544
@handle_exception
4645
def exec_cmd(deployment_id, command, pod, shell_type):
47-
setup_debug_log()
4846
with get_centml_client() as cclient:
4947
try:
5048
pod_name, warning = resolve_pod(cclient, deployment_id, pod)
5149
except ShellError as exc:
5250
raise click.ClickException(str(exc)) from exc
5351
if warning:
54-
click.echo(warning, err=True)
52+
click.echo(f"{warning} Use --pod to specify a different pod.", err=True)
5553

5654
ws_url = build_ws_url(settings.CENTML_PLATFORM_API_URL, deployment_id, pod_name, shell_type)
5755
token = auth.get_centml_token()

centml/sdk/shell/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
forward_io,
1313
interactive_session,
1414
resolve_pod,
15-
setup_debug_log,
1615
)
1716

1817
__all__ = [
@@ -23,7 +22,6 @@
2322
"char_to_sgr",
2423
"render_dirty",
2524
"pyte_extract_text",
26-
"setup_debug_log",
2725
"build_ws_url",
2826
"resolve_pod",
2927
"forward_io",

centml/sdk/shell/renderer.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Pyte terminal screen renderer -- converts pyte's in-memory buffer to ANSI."""
22

3-
import pyte
4-
53
_PYTE_FG_TO_SGR = {
64
"default": "39",
75
"black": "30",

centml/sdk/shell/session.py

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import asyncio
44
import json
5-
import logging
6-
import os
75
import shutil
86
import signal
97
import sys
@@ -19,8 +17,6 @@
1917
from centml.sdk.shell.exceptions import NoPodAvailableError, PodNotFoundError
2018
from centml.sdk.shell.renderer import pyte_extract_text, render_dirty
2119

22-
_log = logging.getLogger("centml.sdk.shell")
23-
2420
BEGIN_MARKER = "__CENTML_BEGIN_5f3a__"
2521
END_MARKER = "__CENTML_END_5f3a__"
2622

@@ -30,16 +26,6 @@
3026
PRINTF_END = r"\137\137CENTML_END_5f3a\137\137"
3127

3228

33-
def setup_debug_log():
34-
"""Configure file-based debug logging (stdout unusable in raw mode)."""
35-
log_path = os.environ.get("CENTML_SHELL_DEBUG_LOG", "/tmp/centml_shell_debug.log")
36-
handler = logging.FileHandler(log_path, mode="w")
37-
handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d %(message)s", datefmt="%H:%M:%S"))
38-
_log.addHandler(handler)
39-
_log.setLevel(logging.DEBUG)
40-
_log.debug("=== shell debug log started (pid=%d) ===", os.getpid())
41-
42-
4329
def build_ws_url(api_url, deployment_id, pod_name, shell_type=None):
4430
"""Build the WebSocket URL for a terminal connection."""
4531
parsed = urllib.parse.urlparse(api_url)
@@ -84,9 +70,7 @@ def resolve_pod(cclient, deployment_id, pod_name=None) -> Tuple[str, Optional[st
8470

8571
warning = None
8672
if len(running_pods) > 1:
87-
warning = (
88-
f"Multiple running pods found, connecting to {running_pods[0]}. " f"Use --pod to specify a different pod."
89-
)
73+
warning = f"Multiple running pods found, connecting to {running_pods[0]}."
9074
return running_pods[0], warning
9175

9276

@@ -114,38 +98,28 @@ async def forward_io(ws, screen, stream, shutdown):
11498
stdin_closed = asyncio.Event()
11599

116100
async def _read_ws():
117-
_log.debug("[read_ws] started")
118-
msg_count = 0
119101
try:
120102
while True:
121103
raw_msg = await ws.recv()
122-
msg_count += 1
123104
msg = json.loads(raw_msg)
124-
keys = list(msg.keys())
125105
data = msg.get("data", "")
126-
data_snippet = repr(data[:120]) if data else ""
127-
_log.debug("[read_ws] msg#%d keys=%s data=%s", msg_count, keys, data_snippet)
128106
if data:
129107
stream.feed(data.replace("\n", "\r\n"))
130108
render_dirty(screen, sys.stdout.buffer)
131109
elif msg.get("error"):
132-
_log.debug("[read_ws] error: %s", msg["error"])
133110
stream.feed(f"Error: {msg['error']}\r\n")
134111
render_dirty(screen, sys.stdout.buffer)
135-
except websockets.ConnectionClosed as exc:
136-
_log.debug("[read_ws] ConnectionClosed after %d msgs: %s", msg_count, exc)
112+
except websockets.ConnectionClosed:
137113
return
138114

139115
async def _read_stdin():
140-
_log.debug("[read_stdin] started")
141116
read_queue = asyncio.Queue()
142117

143118
def _on_stdin_ready():
144119
data = sys.stdin.buffer.read1(4096)
145120
if data:
146121
read_queue.put_nowait(data)
147122
else:
148-
_log.debug("[read_stdin] stdin EOF")
149123
stdin_closed.set()
150124

151125
loop.add_reader(stdin_fd, _on_stdin_ready)
@@ -155,7 +129,6 @@ def _on_stdin_ready():
155129
data = await asyncio.wait_for(read_queue.get(), timeout=0.5)
156130
except asyncio.TimeoutError:
157131
continue
158-
_log.debug("[read_stdin] sending %d bytes: %s", len(data), repr(data[:40]))
159132
try:
160133
await ws.send(
161134
json.dumps(
@@ -168,29 +141,20 @@ def _on_stdin_ready():
168141
)
169142
)
170143
except websockets.ConnectionClosed:
171-
_log.debug("[read_stdin] ws closed on send")
172144
return
173-
_log.debug(
174-
"[read_stdin] loop exited: stdin_closed=%s shutdown=%s", stdin_closed.is_set(), shutdown.is_set()
175-
)
176145
finally:
177146
loop.remove_reader(stdin_fd)
178147

179148
async def _watch_shutdown():
180149
while not shutdown.is_set():
181150
await asyncio.sleep(0.2)
182151

183-
_log.debug("[forward_io] creating tasks")
184152
task_ws = asyncio.create_task(_read_ws())
185153
task_stdin = asyncio.create_task(_read_stdin())
186154
task_shutdown = asyncio.create_task(_watch_shutdown())
187155
tasks = [task_ws, task_stdin, task_shutdown]
188-
task_names = {id(task_ws): "read_ws", id(task_stdin): "read_stdin", id(task_shutdown): "watch_shutdown"}
189156

190157
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
191-
done_names = [task_names[id(t)] for t in done]
192-
pending_names = [task_names[id(t)] for t in pending]
193-
_log.debug("[forward_io] first completed: done=%s pending=%s", done_names, pending_names)
194158

195159
for t in pending:
196160
t.cancel()
@@ -201,9 +165,7 @@ async def _watch_shutdown():
201165
pass
202166
for t in done:
203167
if t.exception() is not None:
204-
_log.debug("[forward_io] task exception: %s", t.exception())
205168
raise t.exception()
206-
_log.debug("[forward_io] returning exit_code=0")
207169
return 0
208170

209171

@@ -234,9 +196,7 @@ async def interactive_session(ws_url, token):
234196
loop.add_signal_handler(signal.SIGHUP, shutdown.set)
235197

236198
headers = {"Authorization": f"Bearer {token}"}
237-
_log.debug("[session] connecting to %s", ws_url)
238199
async with websockets.connect(ws_url, additional_headers=headers, close_timeout=2) as ws:
239-
_log.debug("[session] connected, sending resize %dx%d", cols, rows)
240200

241201
def _send_resize():
242202
c, r = shutil.get_terminal_size()
@@ -252,7 +212,6 @@ def _send_resize():
252212
finally:
253213
loop.remove_signal_handler(signal.SIGWINCH)
254214

255-
_log.debug("[session] exiting with code %d", exit_code)
256215
return exit_code
257216
finally:
258217
loop.remove_signal_handler(signal.SIGTERM)
@@ -297,20 +256,15 @@ async def exec_session(ws_url, token, command):
297256
buffer = ""
298257
is_capturing = False
299258
is_done = False
300-
msg_count = 0
301259
try:
302260
async for raw_msg in ws:
303-
msg_count += 1
304261
msg = json.loads(raw_msg)
305-
keys = list(msg.keys())
306-
_log.debug("[exec] msg#%d keys=%s data_len=%d", msg_count, keys, len(msg.get("data", "")))
307262
if msg.get("data"):
308263
buffer += msg["data"]
309264
while "\n" in buffer:
310265
line, buffer = buffer.split("\n", 1)
311266
clean = pyte_extract_text(line_stream, line_screen, line.rstrip("\r"))
312267
if BEGIN_MARKER in clean:
313-
_log.debug("[exec] BEGIN marker found")
314268
is_capturing = True
315269
continue
316270
if END_MARKER in clean:
@@ -320,20 +274,16 @@ async def exec_session(ws_url, token, command):
320274
exit_code = int(parts[1].strip())
321275
except ValueError:
322276
pass
323-
_log.debug("[exec] END marker, exit_code=%d", exit_code)
324277
is_done = True
325278
break
326279
if is_capturing:
327280
sys.stdout.write(line + "\n")
328281
sys.stdout.flush()
329282
elif msg.get("error"):
330-
_log.debug("[exec] error: %s", msg["error"])
331283
sys.stderr.write(f"Error: {msg['error']}\n")
332284
return 1
333285
if is_done:
334-
_log.debug("[exec] done, breaking")
335286
break
336-
except websockets.ConnectionClosed as exc:
337-
_log.debug("[exec] ConnectionClosed: %s", exc)
338-
_log.debug("[exec] returning exit_code=%d", exit_code)
287+
except websockets.ConnectionClosed:
288+
pass
339289
return exit_code

tests/test_cli_shell.py

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
from unittest.mock import MagicMock, patch
44

5-
import click
6-
import pytest
7-
85
from centml.sdk.shell.exceptions import NoPodAvailableError, PodNotFoundError
96

107
# ===========================================================================
@@ -26,16 +23,14 @@ def test_shell_option_forwarded(self):
2623
from centml.cli.shell import shell
2724
from click.testing import CliRunner
2825

29-
with patch("centml.cli.shell.resolve_pod", return_value=("pod-a", None)), patch(
30-
"centml.cli.shell.get_centml_client"
31-
) as mock_ctx, patch("centml.cli.shell.auth") as mock_auth, patch(
32-
"centml.cli.shell.settings"
33-
) as mock_settings, patch(
34-
"centml.cli.shell.asyncio"
35-
) as mock_asyncio, patch(
36-
"centml.cli.shell.sys"
37-
) as mock_sys:
38-
26+
with (
27+
patch("centml.cli.shell.resolve_pod", return_value=("pod-a", None)),
28+
patch("centml.cli.shell.get_centml_client") as mock_ctx,
29+
patch("centml.cli.shell.auth") as mock_auth,
30+
patch("centml.cli.shell.settings") as mock_settings,
31+
patch("centml.cli.shell.asyncio") as mock_asyncio,
32+
patch("centml.cli.shell.sys") as mock_sys,
33+
):
3934
mock_ctx.return_value.__enter__ = MagicMock(return_value=MagicMock())
4035
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
4136
mock_auth.get_centml_token.return_value = "token"
@@ -52,16 +47,14 @@ def test_pod_option_forwarded(self):
5247
from centml.cli.shell import shell
5348
from click.testing import CliRunner
5449

55-
with patch("centml.cli.shell.resolve_pod") as mock_resolve, patch(
56-
"centml.cli.shell.get_centml_client"
57-
) as mock_ctx, patch("centml.cli.shell.auth") as mock_auth, patch(
58-
"centml.cli.shell.settings"
59-
) as mock_settings, patch(
60-
"centml.cli.shell.asyncio"
61-
) as mock_asyncio, patch(
62-
"centml.cli.shell.sys"
63-
) as mock_sys:
64-
50+
with (
51+
patch("centml.cli.shell.resolve_pod") as mock_resolve,
52+
patch("centml.cli.shell.get_centml_client") as mock_ctx,
53+
patch("centml.cli.shell.auth") as mock_auth,
54+
patch("centml.cli.shell.settings") as mock_settings,
55+
patch("centml.cli.shell.asyncio") as mock_asyncio,
56+
patch("centml.cli.shell.sys") as mock_sys,
57+
):
6558
mock_ctx.return_value.__enter__ = MagicMock(return_value=MagicMock())
6659
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
6760
mock_resolve.return_value = ("my-pod", None)
@@ -79,10 +72,11 @@ def test_shell_error_converts_to_click_exception(self):
7972
from centml.cli.shell import shell
8073
from click.testing import CliRunner
8174

82-
with patch("centml.cli.shell.resolve_pod", side_effect=NoPodAvailableError("No running pods found")), patch(
83-
"centml.cli.shell.get_centml_client"
84-
) as mock_ctx, patch("centml.cli.shell.setup_debug_log"), patch("centml.cli.shell.sys") as mock_sys:
85-
75+
with (
76+
patch("centml.cli.shell.resolve_pod", side_effect=NoPodAvailableError("No running pods found")),
77+
patch("centml.cli.shell.get_centml_client") as mock_ctx,
78+
patch("centml.cli.shell.sys") as mock_sys,
79+
):
8680
mock_ctx.return_value.__enter__ = MagicMock(return_value=MagicMock())
8781
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
8882
mock_sys.stdin.isatty.return_value = True
@@ -99,14 +93,13 @@ def test_passes_command(self):
9993
from centml.cli.shell import exec_cmd
10094
from click.testing import CliRunner
10195

102-
with patch("centml.cli.shell.resolve_pod", return_value=("pod-a", None)), patch(
103-
"centml.cli.shell.get_centml_client"
104-
) as mock_ctx, patch("centml.cli.shell.auth") as mock_auth, patch(
105-
"centml.cli.shell.settings"
106-
) as mock_settings, patch(
107-
"centml.cli.shell.asyncio"
108-
) as mock_asyncio:
109-
96+
with (
97+
patch("centml.cli.shell.resolve_pod", return_value=("pod-a", None)),
98+
patch("centml.cli.shell.get_centml_client") as mock_ctx,
99+
patch("centml.cli.shell.auth") as mock_auth,
100+
patch("centml.cli.shell.settings") as mock_settings,
101+
patch("centml.cli.shell.asyncio") as mock_asyncio,
102+
):
110103
mock_ctx.return_value.__enter__ = MagicMock(return_value=MagicMock())
111104
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
112105
mock_auth.get_centml_token.return_value = "token"
@@ -122,14 +115,13 @@ def test_shell_option_forwarded(self):
122115
from centml.cli.shell import exec_cmd
123116
from click.testing import CliRunner
124117

125-
with patch("centml.cli.shell.resolve_pod", return_value=("pod-a", None)), patch(
126-
"centml.cli.shell.get_centml_client"
127-
) as mock_ctx, patch("centml.cli.shell.auth") as mock_auth, patch(
128-
"centml.cli.shell.settings"
129-
) as mock_settings, patch(
130-
"centml.cli.shell.asyncio"
131-
) as mock_asyncio:
132-
118+
with (
119+
patch("centml.cli.shell.resolve_pod", return_value=("pod-a", None)),
120+
patch("centml.cli.shell.get_centml_client") as mock_ctx,
121+
patch("centml.cli.shell.auth") as mock_auth,
122+
patch("centml.cli.shell.settings") as mock_settings,
123+
patch("centml.cli.shell.asyncio") as mock_asyncio,
124+
):
133125
mock_ctx.return_value.__enter__ = MagicMock(return_value=MagicMock())
134126
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
135127
mock_auth.get_centml_token.return_value = "token"
@@ -145,10 +137,10 @@ def test_shell_error_converts_to_click_exception(self):
145137
from centml.cli.shell import exec_cmd
146138
from click.testing import CliRunner
147139

148-
with patch("centml.cli.shell.resolve_pod", side_effect=PodNotFoundError("Pod 'x' not found")), patch(
149-
"centml.cli.shell.get_centml_client"
150-
) as mock_ctx, patch("centml.cli.shell.setup_debug_log"):
151-
140+
with (
141+
patch("centml.cli.shell.resolve_pod", side_effect=PodNotFoundError("Pod 'x' not found")),
142+
patch("centml.cli.shell.get_centml_client") as mock_ctx,
143+
):
152144
mock_ctx.return_value.__enter__ = MagicMock(return_value=MagicMock())
153145
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
154146

0 commit comments

Comments
 (0)