Skip to content

Commit dbfe0aa

Browse files
TTTPOBclaude
andcommitted
refactor(enums): add VariableSource for variables source field
Replace bare "dap"/"fallback" strings in variables.py with a VariableSource(str, Enum), following the existing class X(str, Enum) pattern. Update human-output f-strings in vars_cmd.py to use .value, and sync tests to use the enum members explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 88497d2 commit dbfe0aa

4 files changed

Lines changed: 42 additions & 19 deletions

File tree

jupyter_jcli/commands/vars_cmd.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def vars_cmd(ctx: Context, session_id: str, name: str | None, rich: bool, timeou
5959
Source
6060
------
6161
When the kernel advertises debugger support the DAP inspectVariables
62-
command is used (source="dap"). Otherwise a shell-channel code snippet
63-
is executed as a fallback (source="fallback").
62+
command is used (source=VariableSource.DAP). Otherwise a shell-channel
63+
code snippet is executed as a fallback (source=VariableSource.FALLBACK).
6464
"""
6565
if rich and not name:
6666
emit_error("PARSE_ERROR", "--rich requires --name", ctx.use_json)
@@ -122,7 +122,7 @@ def _emit_list(ctx: Context, result: dict, session_id: str) -> None:
122122
return
123123

124124
if not variables:
125-
emit({"_human": f"No variables found (source: {source})"}, use_json=False)
125+
emit({"_human": f"No variables found (source: {source.value})"}, use_json=False)
126126
return
127127

128128
lines = [f"{'NAME':<24} {'TYPE':<20} {'VALUE':<40}"]
@@ -137,7 +137,7 @@ def _emit_list(ctx: Context, result: dict, session_id: str) -> None:
137137
lines.append(f"{name:<24} {typ:<20} {value:<40}")
138138

139139
lines.append("")
140-
lines.append(f"source: {source} | {len(variables)} variable(s)")
140+
lines.append(f"source: {source.value} | {len(variables)} variable(s)")
141141
lines.append(f"hint: {_ORDERING_NOTE}")
142142
emit({"_human": "\n".join(lines)}, use_json=False)
143143

@@ -151,7 +151,7 @@ def _emit_single(ctx: Context, result: dict, session_id: str) -> None:
151151
f"name: {str(result['name'])}",
152152
f"type: {str(result['type'])}",
153153
f"value: {str(result['value'])}",
154-
f"source: {result['source']}",
154+
f"source: {result['source'].value}",
155155
]
156156
if "data" in result:
157157
mimetypes = list(result["data"].keys())

jupyter_jcli/variables.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
import itertools
66
import typing as t
7+
from enum import Enum
8+
9+
10+
class VariableSource(str, Enum):
11+
"""Which mechanism supplied the variable metadata.
12+
13+
Used for human-readable display only; not dispatched on.
14+
"""
15+
16+
DAP = "dap"
17+
FALLBACK = "fallback"
718

819

920
class VariablesUnavailable(Exception):
@@ -131,7 +142,7 @@ def list_variables(kernel, *, timeout: float = 5.0) -> dict[str, t.Any]:
131142
Returns:
132143
Dict with keys:
133144
- "variables": list of {"name", "type", "value", "variables_reference"}
134-
- "source": "dap" or "fallback"
145+
- "source": VariableSource (DAP or FALLBACK)
135146
136147
Raises:
137148
VariablesUnavailable: if neither path succeeds.
@@ -151,14 +162,14 @@ def list_variables(kernel, *, timeout: float = 5.0) -> dict[str, t.Any]:
151162
wsc = kernel._manager.client
152163
raw = _dap_inspect_variables(wsc, timeout=timeout)
153164
variables = [_normalise_dap_variable(v) for v in raw]
154-
return {"variables": variables, "source": "dap"}
165+
return {"variables": variables, "source": VariableSource.DAP}
155166
except Exception:
156167
pass # fall through to fallback
157168

158169
# Shell-channel fallback
159170
try:
160171
variables = _fallback_list_variables(kernel)
161-
return {"variables": variables, "source": "fallback"}
172+
return {"variables": variables, "source": VariableSource.FALLBACK}
162173
except ValueError as e:
163174
raise VariablesUnavailable(str(e)) from e
164175
except Exception as e:
@@ -184,7 +195,7 @@ def inspect_variable(
184195
Returns:
185196
Dict with keys:
186197
- "name", "type", "value", "variables_reference"
187-
- "source": "dap" or "fallback"
198+
- "source": VariableSource (DAP or FALLBACK)
188199
- "data", "metadata" (only when rich=True and DAP succeeds)
189200
190201
Raises:
@@ -206,7 +217,7 @@ def inspect_variable(
206217
}
207218
result["data"] = body.get("data", {})
208219
result["metadata"] = body.get("metadata", {})
209-
result["source"] = "dap"
220+
result["source"] = VariableSource.DAP
210221
return result
211222
else:
212223
raw_all = _dap_inspect_variables(wsc, timeout=timeout)
@@ -218,7 +229,7 @@ def inspect_variable(
218229
f"Variable '{name}' not found in kernel namespace"
219230
)
220231
result = _normalise_dap_variable(match)
221-
result["source"] = "dap"
232+
result["source"] = VariableSource.DAP
222233
return result
223234
except VariablesUnavailable:
224235
raise
@@ -233,7 +244,7 @@ def inspect_variable(
233244
raise VariablesUnavailable(
234245
f"Variable '{name}' not found in kernel namespace"
235246
)
236-
match["source"] = "fallback"
247+
match["source"] = VariableSource.FALLBACK
237248
return match
238249
except VariablesUnavailable:
239250
raise

tests/test_variables.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from jupyter_jcli.variables import (
88
VariablesUnavailable,
9+
VariableSource,
910
_fallback_list_variables,
1011
list_variables,
1112
inspect_variable,
@@ -48,7 +49,7 @@ def test_returns_dict_shape(self, live_kernel):
4849

4950
assert "variables" in result
5051
assert "source" in result
51-
assert result["source"] in ("dap", "fallback")
52+
assert result["source"] in (VariableSource.DAP, VariableSource.FALLBACK)
5253
assert isinstance(result["variables"], list)
5354

5455
def test_user_variables_present(self, live_kernel):
@@ -83,14 +84,24 @@ def test_inspect_known_variable(self, live_kernel):
8384

8485
assert result["name"] == "_ti_x"
8586
assert "42" in result["value"]
86-
assert result["source"] in ("dap", "fallback")
87+
assert result["source"] in (VariableSource.DAP, VariableSource.FALLBACK)
8788

8889
def test_inspect_missing_variable_raises(self, live_kernel):
8990
live_kernel.execute("_warmup = 1", timeout=30)
9091
with pytest.raises(VariablesUnavailable):
9192
inspect_variable(live_kernel, "__no_such_var__", timeout=15.0)
9293

9394

95+
class TestVariableSourceEnum:
96+
"""Unit tests — VariableSource enum value stability."""
97+
98+
def test_dap_value_equals_string(self):
99+
assert VariableSource.DAP == "dap"
100+
101+
def test_fallback_round_trips(self):
102+
assert VariableSource("fallback") is VariableSource.FALLBACK
103+
104+
94105
class TestListVariableValueFields:
95106
"""Integration regression — list_variables always returns string fields."""
96107

tests/test_vars_cmd.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from jupyter_jcli.cli import main
1010
from jupyter_jcli.commands.vars_cmd import _emit_list
11+
from jupyter_jcli.variables import VariableSource
1112

1213

1314
def _create_session(runner, url, token):
@@ -36,7 +37,7 @@ def test_json_output_shape(self, live_session, mock_kernel_connection):
3637
data = json.loads(result.output)
3738
assert "variables" in data
3839
assert "source" in data
39-
assert data["source"] in ("dap", "fallback")
40+
assert data["source"] in (VariableSource.DAP.value, VariableSource.FALLBACK.value)
4041

4142
def test_user_variables_in_output(self, live_session, mock_kernel_connection):
4243
runner = CliRunner()
@@ -79,7 +80,7 @@ def test_inspect_single_variable_json(self, live_session, mock_kernel_connection
7980
data = json.loads(result.output)
8081
assert data["name"] == "_vs_x"
8182
assert "42" in data["value"]
82-
assert data["source"] in ("dap", "fallback")
83+
assert data["source"] in (VariableSource.DAP.value, VariableSource.FALLBACK.value)
8384

8485
def test_inspect_missing_variable_exits_1(self, live_session, mock_kernel_connection):
8586
runner = CliRunner()
@@ -119,7 +120,7 @@ def test_emit_list_does_not_crash_on_list_value(self):
119120
ctx = Context(server_url="http://localhost:8888", token=None, use_json=False)
120121
result = {
121122
"variables": [{"name": "lst", "type": "list", "value": [1, 2, 3]}],
122-
"source": "fallback",
123+
"source": VariableSource.FALLBACK,
123124
}
124125
_emit_list(ctx, result, "fake-session-id")
125126

@@ -130,7 +131,7 @@ def test_emit_list_output_contains_variable_name(self):
130131
ctx = Context(server_url="http://localhost:8888", token=None, use_json=False)
131132
result = {
132133
"variables": [{"name": "my_list", "type": "list", "value": [1, 2, 3]}],
133-
"source": "fallback",
134+
"source": VariableSource.FALLBACK,
134135
}
135136
with CliRunner().isolated_filesystem():
136137
runner = CliRunner()
@@ -151,7 +152,7 @@ def test_emit_list_no_connection_failed_on_bad_value(self):
151152
ctx = Context(server_url="http://localhost:8888", token=None, use_json=False)
152153
result = {
153154
"variables": [{"name": "x", "type": "int", "value": 99}],
154-
"source": "fallback",
155+
"source": VariableSource.FALLBACK,
155156
}
156157
_emit_list(ctx, result, "fake-session-id")
157158

0 commit comments

Comments
 (0)