Skip to content

Commit 2801d62

Browse files
stainless-app[bot]Stainless Bot
authored andcommitted
feat(api): manual updates (#118)
1 parent 5222c74 commit 2801d62

6 files changed

Lines changed: 98 additions & 2 deletions

File tree

src/runloop_api_client/_client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def __init__(
108108
_strict_response_validation=_strict_response_validation,
109109
)
110110

111+
self._default_stream_cls = Stream
112+
111113
self.blueprints = resources.BlueprintsResource(self)
112114
self.deployments = resources.DeploymentsResource(self)
113115
self.devboxes = resources.DevboxesResource(self)
@@ -284,6 +286,8 @@ def __init__(
284286
_strict_response_validation=_strict_response_validation,
285287
)
286288

289+
self._default_stream_cls = AsyncStream
290+
287291
self.blueprints = resources.AsyncBlueprintsResource(self)
288292
self.deployments = resources.AsyncDeploymentsResource(self)
289293
self.devboxes = resources.AsyncDevboxesResource(self)

src/runloop_api_client/_streaming.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,29 @@ def __stream__(self) -> Iterator[_T]:
5555
iterator = self._iter_events()
5656

5757
for sse in iterator:
58-
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
58+
if sse.event == "completion":
59+
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
60+
61+
if sse.event == "message_start" or sse.event == "content_block_stop":
62+
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
63+
64+
if sse.event == "ping":
65+
continue
66+
67+
if sse.event == "error":
68+
body = sse.data
69+
70+
try:
71+
body = sse.json()
72+
err_msg = f"{body}"
73+
except Exception:
74+
err_msg = sse.data or f"Error code: {response.status_code}"
75+
76+
raise self._client._make_status_error(
77+
err_msg,
78+
body=body,
79+
response=self.response,
80+
)
5981

6082
# Ensure the entire stream is consumed
6183
for _sse in iterator:
@@ -119,7 +141,29 @@ async def __stream__(self) -> AsyncIterator[_T]:
119141
iterator = self._iter_events()
120142

121143
async for sse in iterator:
122-
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
144+
if sse.event == "completion":
145+
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
146+
147+
if sse.event == "message_start" or sse.event == "content_block_stop":
148+
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
149+
150+
if sse.event == "ping":
151+
continue
152+
153+
if sse.event == "error":
154+
body = sse.data
155+
156+
try:
157+
body = sse.json()
158+
err_msg = f"{body}"
159+
except Exception:
160+
err_msg = sse.data or f"Error code: {response.status_code}"
161+
162+
raise self._client._make_status_error(
163+
err_msg,
164+
body=body,
165+
response=self.response,
166+
)
123167

124168
# Ensure the entire stream is consumed
125169
async for _sse in iterator:

tests/api_resources/devboxes/test_executions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ def test_path_params_logs(self, client: Runloop) -> None:
266266
id="id",
267267
)
268268

269+
@pytest.mark.skip(reason="cannot test text/event-stream")
269270
@parametrize
270271
def test_method_tail(self, client: Runloop) -> None:
271272
execution = client.devboxes.executions.tail(
@@ -274,6 +275,7 @@ def test_method_tail(self, client: Runloop) -> None:
274275
)
275276
assert execution is None
276277

278+
@pytest.mark.skip(reason="cannot test text/event-stream")
277279
@parametrize
278280
def test_raw_response_tail(self, client: Runloop) -> None:
279281
response = client.devboxes.executions.with_raw_response.tail(
@@ -286,6 +288,7 @@ def test_raw_response_tail(self, client: Runloop) -> None:
286288
execution = response.parse()
287289
assert execution is None
288290

291+
@pytest.mark.skip(reason="cannot test text/event-stream")
289292
@parametrize
290293
def test_streaming_response_tail(self, client: Runloop) -> None:
291294
with client.devboxes.executions.with_streaming_response.tail(
@@ -300,6 +303,7 @@ def test_streaming_response_tail(self, client: Runloop) -> None:
300303

301304
assert cast(Any, response.is_closed) is True
302305

306+
@pytest.mark.skip(reason="cannot test text/event-stream")
303307
@parametrize
304308
def test_path_params_tail(self, client: Runloop) -> None:
305309
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
@@ -563,6 +567,7 @@ async def test_path_params_logs(self, async_client: AsyncRunloop) -> None:
563567
id="id",
564568
)
565569

570+
@pytest.mark.skip(reason="cannot test text/event-stream")
566571
@parametrize
567572
async def test_method_tail(self, async_client: AsyncRunloop) -> None:
568573
execution = await async_client.devboxes.executions.tail(
@@ -571,6 +576,7 @@ async def test_method_tail(self, async_client: AsyncRunloop) -> None:
571576
)
572577
assert execution is None
573578

579+
@pytest.mark.skip(reason="cannot test text/event-stream")
574580
@parametrize
575581
async def test_raw_response_tail(self, async_client: AsyncRunloop) -> None:
576582
response = await async_client.devboxes.executions.with_raw_response.tail(
@@ -583,6 +589,7 @@ async def test_raw_response_tail(self, async_client: AsyncRunloop) -> None:
583589
execution = await response.parse()
584590
assert execution is None
585591

592+
@pytest.mark.skip(reason="cannot test text/event-stream")
586593
@parametrize
587594
async def test_streaming_response_tail(self, async_client: AsyncRunloop) -> None:
588595
async with async_client.devboxes.executions.with_streaming_response.tail(
@@ -597,6 +604,7 @@ async def test_streaming_response_tail(self, async_client: AsyncRunloop) -> None
597604

598605
assert cast(Any, response.is_closed) is True
599606

607+
@pytest.mark.skip(reason="cannot test text/event-stream")
600608
@parametrize
601609
async def test_path_params_tail(self, async_client: AsyncRunloop) -> None:
602610
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):

tests/api_resources/devboxes/test_logs.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ def test_path_params_list(self, client: Runloop) -> None:
5555
"",
5656
)
5757

58+
@pytest.mark.skip(reason="cannot test text/event-stream")
5859
@parametrize
5960
def test_method_tail(self, client: Runloop) -> None:
6061
log = client.devboxes.logs.tail(
6162
"id",
6263
)
6364
assert log is None
6465

66+
@pytest.mark.skip(reason="cannot test text/event-stream")
6567
@parametrize
6668
def test_raw_response_tail(self, client: Runloop) -> None:
6769
response = client.devboxes.logs.with_raw_response.tail(
@@ -73,6 +75,7 @@ def test_raw_response_tail(self, client: Runloop) -> None:
7375
log = response.parse()
7476
assert log is None
7577

78+
@pytest.mark.skip(reason="cannot test text/event-stream")
7679
@parametrize
7780
def test_streaming_response_tail(self, client: Runloop) -> None:
7881
with client.devboxes.logs.with_streaming_response.tail(
@@ -86,6 +89,7 @@ def test_streaming_response_tail(self, client: Runloop) -> None:
8689

8790
assert cast(Any, response.is_closed) is True
8891

92+
@pytest.mark.skip(reason="cannot test text/event-stream")
8993
@parametrize
9094
def test_path_params_tail(self, client: Runloop) -> None:
9195
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
@@ -135,13 +139,15 @@ async def test_path_params_list(self, async_client: AsyncRunloop) -> None:
135139
"",
136140
)
137141

142+
@pytest.mark.skip(reason="cannot test text/event-stream")
138143
@parametrize
139144
async def test_method_tail(self, async_client: AsyncRunloop) -> None:
140145
log = await async_client.devboxes.logs.tail(
141146
"id",
142147
)
143148
assert log is None
144149

150+
@pytest.mark.skip(reason="cannot test text/event-stream")
145151
@parametrize
146152
async def test_raw_response_tail(self, async_client: AsyncRunloop) -> None:
147153
response = await async_client.devboxes.logs.with_raw_response.tail(
@@ -153,6 +159,7 @@ async def test_raw_response_tail(self, async_client: AsyncRunloop) -> None:
153159
log = await response.parse()
154160
assert log is None
155161

162+
@pytest.mark.skip(reason="cannot test text/event-stream")
156163
@parametrize
157164
async def test_streaming_response_tail(self, async_client: AsyncRunloop) -> None:
158165
async with async_client.devboxes.logs.with_streaming_response.tail(
@@ -166,6 +173,7 @@ async def test_streaming_response_tail(self, async_client: AsyncRunloop) -> None
166173

167174
assert cast(Any, response.is_closed) is True
168175

176+
@pytest.mark.skip(reason="cannot test text/event-stream")
169177
@parametrize
170178
async def test_path_params_tail(self, async_client: AsyncRunloop) -> None:
171179
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):

tests/api_resources/test_deployments.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,15 @@ def test_path_params_redeploy(self, client: Runloop) -> None:
170170
"",
171171
)
172172

173+
@pytest.mark.skip(reason="cannot test text/event-stream")
173174
@parametrize
174175
def test_method_tail(self, client: Runloop) -> None:
175176
deployment = client.deployments.tail(
176177
"deployment_id",
177178
)
178179
assert_matches_type(DeploymentTailResponse, deployment, path=["response"])
179180

181+
@pytest.mark.skip(reason="cannot test text/event-stream")
180182
@parametrize
181183
def test_raw_response_tail(self, client: Runloop) -> None:
182184
response = client.deployments.with_raw_response.tail(
@@ -188,6 +190,7 @@ def test_raw_response_tail(self, client: Runloop) -> None:
188190
deployment = response.parse()
189191
assert_matches_type(DeploymentTailResponse, deployment, path=["response"])
190192

193+
@pytest.mark.skip(reason="cannot test text/event-stream")
191194
@parametrize
192195
def test_streaming_response_tail(self, client: Runloop) -> None:
193196
with client.deployments.with_streaming_response.tail(
@@ -201,6 +204,7 @@ def test_streaming_response_tail(self, client: Runloop) -> None:
201204

202205
assert cast(Any, response.is_closed) is True
203206

207+
@pytest.mark.skip(reason="cannot test text/event-stream")
204208
@parametrize
205209
def test_path_params_tail(self, client: Runloop) -> None:
206210
with pytest.raises(ValueError, match=r"Expected a non-empty value for `deployment_id` but received ''"):
@@ -359,13 +363,15 @@ async def test_path_params_redeploy(self, async_client: AsyncRunloop) -> None:
359363
"",
360364
)
361365

366+
@pytest.mark.skip(reason="cannot test text/event-stream")
362367
@parametrize
363368
async def test_method_tail(self, async_client: AsyncRunloop) -> None:
364369
deployment = await async_client.deployments.tail(
365370
"deployment_id",
366371
)
367372
assert_matches_type(DeploymentTailResponse, deployment, path=["response"])
368373

374+
@pytest.mark.skip(reason="cannot test text/event-stream")
369375
@parametrize
370376
async def test_raw_response_tail(self, async_client: AsyncRunloop) -> None:
371377
response = await async_client.deployments.with_raw_response.tail(
@@ -377,6 +383,7 @@ async def test_raw_response_tail(self, async_client: AsyncRunloop) -> None:
377383
deployment = await response.parse()
378384
assert_matches_type(DeploymentTailResponse, deployment, path=["response"])
379385

386+
@pytest.mark.skip(reason="cannot test text/event-stream")
380387
@parametrize
381388
async def test_streaming_response_tail(self, async_client: AsyncRunloop) -> None:
382389
async with async_client.deployments.with_streaming_response.tail(
@@ -390,6 +397,7 @@ async def test_streaming_response_tail(self, async_client: AsyncRunloop) -> None
390397

391398
assert cast(Any, response.is_closed) is True
392399

400+
@pytest.mark.skip(reason="cannot test text/event-stream")
393401
@parametrize
394402
async def test_path_params_tail(self, async_client: AsyncRunloop) -> None:
395403
with pytest.raises(ValueError, match=r"Expected a non-empty value for `deployment_id` but received ''"):

tests/test_client.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from runloop_api_client._types import Omit
2121
from runloop_api_client._models import BaseModel, FinalRequestOptions
2222
from runloop_api_client._constants import RAW_RESPONSE_HEADER
23+
from runloop_api_client._streaming import Stream, AsyncStream
2324
from runloop_api_client._exceptions import RunloopError, APIStatusError, APITimeoutError, APIResponseValidationError
2425
from runloop_api_client._base_client import (
2526
DEFAULT_TIMEOUT,
@@ -685,6 +686,17 @@ def test_client_max_retries_validation(self) -> None:
685686
max_retries=cast(Any, None),
686687
)
687688

689+
@pytest.mark.respx(base_url=base_url)
690+
def test_default_stream_cls(self, respx_mock: MockRouter) -> None:
691+
class Model(BaseModel):
692+
name: str
693+
694+
respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
695+
696+
stream = self.client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model])
697+
assert isinstance(stream, Stream)
698+
stream.response.close()
699+
688700
@pytest.mark.respx(base_url=base_url)
689701
def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
690702
class Model(BaseModel):
@@ -1420,6 +1432,18 @@ async def test_client_max_retries_validation(self) -> None:
14201432
max_retries=cast(Any, None),
14211433
)
14221434

1435+
@pytest.mark.respx(base_url=base_url)
1436+
@pytest.mark.asyncio
1437+
async def test_default_stream_cls(self, respx_mock: MockRouter) -> None:
1438+
class Model(BaseModel):
1439+
name: str
1440+
1441+
respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
1442+
1443+
stream = await self.client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model])
1444+
assert isinstance(stream, AsyncStream)
1445+
await stream.response.aclose()
1446+
14231447
@pytest.mark.respx(base_url=base_url)
14241448
@pytest.mark.asyncio
14251449
async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:

0 commit comments

Comments
 (0)