Skip to content

Commit a517fab

Browse files
committed
fix tests and add transport param to query
Signed-off-by: Rushil Patel <rpatel@codegen.com>
1 parent 97ee71e commit a517fab

File tree

5 files changed

+83
-57
lines changed

5 files changed

+83
-57
lines changed

src/claude_code_sdk/query.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
from typing import Any
66

77
from ._internal.client import InternalClient
8+
from ._internal.transport import Transport
89
from .types import ClaudeCodeOptions, Message
910

1011

1112
async def query(
1213
*,
1314
prompt: str | AsyncIterable[dict[str, Any]],
1415
options: ClaudeCodeOptions | None = None,
16+
transport: Transport | None = None,
1517
) -> AsyncIterator[Message]:
1618
"""
1719
Query Claude Code for one-shot or unidirectional streaming interactions.
@@ -56,6 +58,9 @@ async def query(
5658
- 'acceptEdits': Auto-accept file edits
5759
- 'bypassPermissions': Allow all tools (use with caution)
5860
Set options.cwd for working directory.
61+
transport: Optional transport implementation. If provided, this will be used
62+
instead of the default transport selection based on options.
63+
The transport will be automatically configured with the prompt and options.
5964
6065
Yields:
6166
Messages from the conversation
@@ -90,6 +95,22 @@ async def prompts():
9095
async for message in query(prompt=prompts()):
9196
print(message)
9297
```
98+
99+
Example - With custom transport:
100+
```python
101+
from claude_code_sdk import query, Transport
102+
103+
class MyCustomTransport(Transport):
104+
# Implement custom transport logic
105+
pass
106+
107+
transport = MyCustomTransport()
108+
async for message in query(
109+
prompt="Hello",
110+
transport=transport
111+
):
112+
print(message)
113+
```
93114
"""
94115
if options is None:
95116
options = ClaudeCodeOptions()
@@ -98,5 +119,5 @@ async def prompts():
98119

99120
client = InternalClient()
100121

101-
async for message in client.process_query(prompt=prompt, options=options):
122+
async for message in client.process_query(prompt=prompt, options=options, transport=transport):
102123
yield message

tests/test_client.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ async def mock_receive():
106106
messages.append(msg)
107107

108108
# Verify transport was created with correct parameters
109-
mock_transport_class.assert_called_once_with() # No parameters to constructor
110-
mock_transport.configure.assert_called_once_with("test", options)
109+
mock_transport_class.assert_called_once()
110+
call_kwargs = mock_transport_class.call_args.kwargs
111+
assert call_kwargs["prompt"] == "test"
112+
assert call_kwargs["options"].cwd == "/custom/path"
111113

112-
anyio.run(_test)
114+
anyio.run(_test)

tests/test_integration.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,8 @@ async def mock_receive():
195195
messages.append(msg)
196196

197197
# Verify transport was created with continuation option
198-
mock_transport_class.assert_called_once_with() # No parameters to constructor
199-
mock_transport.configure.assert_called_once()
200-
configure_call_args = mock_transport.configure.call_args
201-
assert configure_call_args[0][0] == "Continue" # prompt argument
202-
assert configure_call_args[0][1].continue_conversation is True # options argument
198+
mock_transport_class.assert_called_once()
199+
call_kwargs = mock_transport_class.call_args.kwargs
200+
assert call_kwargs["options"].continue_conversation is True
203201

204-
anyio.run(_test)
202+
anyio.run(_test)

tests/test_subprocess_buffering.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ async def _test() -> None:
5050

5151
buffered_line = json.dumps(json_obj1) + "\n" + json.dumps(json_obj2)
5252

53-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
54-
transport.configure("test", ClaudeCodeOptions())
53+
transport = SubprocessCLITransport(
54+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
55+
)
5556

5657
mock_process = MagicMock()
5758
mock_process.returncode = None
@@ -84,8 +85,9 @@ async def _test() -> None:
8485

8586
buffered_line = json.dumps(json_obj1) + "\n" + json.dumps(json_obj2)
8687

87-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
88-
transport.configure("test", ClaudeCodeOptions())
88+
transport = SubprocessCLITransport(
89+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
90+
)
8991

9092
mock_process = MagicMock()
9193
mock_process.returncode = None
@@ -113,8 +115,9 @@ async def _test() -> None:
113115

114116
buffered_line = json.dumps(json_obj1) + "\n\n\n" + json.dumps(json_obj2)
115117

116-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
117-
transport.configure("test", ClaudeCodeOptions())
118+
transport = SubprocessCLITransport(
119+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
120+
)
118121

119122
mock_process = MagicMock()
120123
mock_process.returncode = None
@@ -158,8 +161,9 @@ async def _test() -> None:
158161
part2 = complete_json[100:250]
159162
part3 = complete_json[250:]
160163

161-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
162-
transport.configure("test", ClaudeCodeOptions())
164+
transport = SubprocessCLITransport(
165+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
166+
)
163167

164168
mock_process = MagicMock()
165169
mock_process.returncode = None
@@ -205,8 +209,9 @@ async def _test() -> None:
205209
for i in range(0, len(complete_json), chunk_size)
206210
]
207211

208-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
209-
transport.configure("test", ClaudeCodeOptions())
212+
transport = SubprocessCLITransport(
213+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
214+
)
210215

211216
mock_process = MagicMock()
212217
mock_process.returncode = None
@@ -234,8 +239,9 @@ def test_buffer_size_exceeded(self) -> None:
234239
async def _test() -> None:
235240
huge_incomplete = '{"data": "' + "x" * (_MAX_BUFFER_SIZE + 1000)
236241

237-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
238-
transport.configure("test", ClaudeCodeOptions())
242+
transport = SubprocessCLITransport(
243+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
244+
)
239245

240246
mock_process = MagicMock()
241247
mock_process.returncode = None
@@ -275,8 +281,9 @@ async def _test() -> None:
275281
large_json[3000:] + "\n" + msg3,
276282
]
277283

278-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
279-
transport.configure("test", ClaudeCodeOptions())
284+
transport = SubprocessCLITransport(
285+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
286+
)
280287

281288
mock_process = MagicMock()
282289
mock_process.returncode = None
@@ -297,4 +304,4 @@ async def _test() -> None:
297304
assert messages[2]["type"] == "system"
298305
assert messages[2]["subtype"] == "end"
299306

300-
anyio.run(_test)
307+
anyio.run(_test)

tests/test_transport.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ def test_find_cli_not_found(self):
2121
patch("pathlib.Path.exists", return_value=False),
2222
pytest.raises(CLINotFoundError) as exc_info,
2323
):
24-
SubprocessCLITransport()
24+
SubprocessCLITransport(prompt="test", options=ClaudeCodeOptions())
2525

2626
assert "Claude Code requires Node.js" in str(exc_info.value)
2727

2828
def test_build_command_basic(self):
2929
"""Test building basic CLI command."""
30-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
31-
transport.configure("Hello", ClaudeCodeOptions())
30+
transport = SubprocessCLITransport(
31+
prompt="Hello", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
32+
)
3233

3334
cmd = transport._build_command()
3435
assert cmd[0] == "/usr/bin/claude"
@@ -41,23 +42,27 @@ def test_cli_path_accepts_pathlib_path(self):
4142
"""Test that cli_path accepts pathlib.Path objects."""
4243
from pathlib import Path
4344

44-
transport = SubprocessCLITransport(cli_path=Path("/usr/bin/claude"))
45+
transport = SubprocessCLITransport(
46+
prompt="Hello",
47+
options=ClaudeCodeOptions(),
48+
cli_path=Path("/usr/bin/claude"),
49+
)
4550

4651
assert transport._cli_path == "/usr/bin/claude"
4752

4853
def test_build_command_with_options(self):
4954
"""Test building CLI command with options."""
50-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
51-
transport.configure(
52-
"test",
53-
ClaudeCodeOptions(
55+
transport = SubprocessCLITransport(
56+
prompt="test",
57+
options=ClaudeCodeOptions(
5458
system_prompt="Be helpful",
5559
allowed_tools=["Read", "Write"],
5660
disallowed_tools=["Bash"],
5761
model="claude-3-5-sonnet",
5862
permission_mode="acceptEdits",
5963
max_turns=5,
6064
),
65+
cli_path="/usr/bin/claude",
6166
)
6267

6368
cmd = transport._build_command()
@@ -76,10 +81,10 @@ def test_build_command_with_options(self):
7681

7782
def test_session_continuation(self):
7883
"""Test session continuation options."""
79-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
80-
transport.configure(
81-
"Continue from before",
82-
ClaudeCodeOptions(continue_conversation=True, resume="session-123"),
84+
transport = SubprocessCLITransport(
85+
prompt="Continue from before",
86+
options=ClaudeCodeOptions(continue_conversation=True, resume="session-123"),
87+
cli_path="/usr/bin/claude",
8388
)
8489

8590
cmd = transport._build_command()
@@ -106,8 +111,11 @@ async def _test():
106111

107112
mock_exec.return_value = mock_process
108113

109-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
110-
transport.configure("test", ClaudeCodeOptions())
114+
transport = SubprocessCLITransport(
115+
prompt="test",
116+
options=ClaudeCodeOptions(),
117+
cli_path="/usr/bin/claude",
118+
)
111119

112120
await transport.connect()
113121
assert transport._process is not None
@@ -122,8 +130,9 @@ def test_receive_messages(self):
122130
"""Test parsing messages from CLI output."""
123131
# This test is simplified to just test the parsing logic
124132
# The full async stream handling is tested in integration tests
125-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
126-
transport.configure("test", ClaudeCodeOptions())
133+
transport = SubprocessCLITransport(
134+
prompt="test", options=ClaudeCodeOptions(), cli_path="/usr/bin/claude"
135+
)
127136

128137
# The actual message parsing is done by the client, not the transport
129138
# So we just verify the transport can be created and basic structure is correct
@@ -135,26 +144,15 @@ def test_connect_with_nonexistent_cwd(self):
135144
from claude_code_sdk._errors import CLIConnectionError
136145

137146
async def _test():
138-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
139-
transport.configure(
140-
"test",
141-
ClaudeCodeOptions(cwd="/this/directory/does/not/exist"),
147+
transport = SubprocessCLITransport(
148+
prompt="test",
149+
options=ClaudeCodeOptions(cwd="/this/directory/does/not/exist"),
150+
cli_path="/usr/bin/claude",
142151
)
143152

144153
with pytest.raises(CLIConnectionError) as exc_info:
145154
await transport.connect()
146155

147156
assert "/this/directory/does/not/exist" in str(exc_info.value)
148157

149-
anyio.run(_test)
150-
151-
def test_build_command_without_configure(self):
152-
"""Test that _build_command raises error if not configured."""
153-
from claude_code_sdk._errors import CLIConnectionError
154-
155-
transport = SubprocessCLITransport(cli_path="/usr/bin/claude")
156-
157-
with pytest.raises(CLIConnectionError) as exc_info:
158-
transport._build_command()
159-
160-
assert "Transport not configured" in str(exc_info.value)
158+
anyio.run(_test)

0 commit comments

Comments
 (0)