Skip to content

Commit 0985851

Browse files
feat(api): update via SDK Studio
1 parent 6d8571b commit 0985851

File tree

6 files changed

+84
-35
lines changed

6 files changed

+84
-35
lines changed

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 16
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-b4a3f35e4a44e5a5034508ced15d7b44c1924000062e0f5293797413d26ee412.yml
33
openapi_spec_hash: f17b1091020f90126e6cefc2d38ff85f
4-
config_hash: 1156f6f6fb7245e7b021daddf23153e3
4+
config_hash: e2d21e779cfc4e26a99b9e4e75de3f50

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,32 @@ async def main() -> None:
8080
asyncio.run(main())
8181
```
8282

83+
## Streaming responses
84+
85+
We provide support for streaming responses using Server Side Events (SSE).
86+
87+
```python
88+
from opencode_ai import Opencode
89+
90+
client = Opencode()
91+
92+
stream = client.event.list()
93+
for events in stream:
94+
print(events)
95+
```
96+
97+
The async client uses the exact same interface.
98+
99+
```python
100+
from opencode_ai import AsyncOpencode
101+
102+
client = AsyncOpencode()
103+
104+
stream = await client.event.list()
105+
async for events in stream:
106+
print(events)
107+
```
108+
83109
## Using types
84110

85111
Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:

src/opencode_ai/_client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ def __init__(
9292
_strict_response_validation=_strict_response_validation,
9393
)
9494

95+
self._default_stream_cls = Stream
96+
9597
self.event = event.EventResource(self)
9698
self.app = app.AppResource(self)
9799
self.file = file.FileResource(self)
@@ -247,6 +249,8 @@ def __init__(
247249
_strict_response_validation=_strict_response_validation,
248250
)
249251

252+
self._default_stream_cls = AsyncStream
253+
250254
self.event = event.AsyncEventResource(self)
251255
self.app = app.AsyncAppResource(self)
252256
self.file = file.AsyncFileResource(self)

src/opencode_ai/resources/event.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
async_to_raw_response_wrapper,
1616
async_to_streamed_response_wrapper,
1717
)
18+
from .._streaming import Stream, AsyncStream
1819
from .._base_client import make_request_options
1920
from ..types.event_list_response import EventListResponse
2021

@@ -50,17 +51,16 @@ def list(
5051
extra_query: Query | None = None,
5152
extra_body: Body | None = None,
5253
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
53-
) -> EventListResponse:
54+
) -> Stream[EventListResponse]:
5455
"""Get events"""
55-
return cast(
56-
EventListResponse,
57-
self._get(
58-
"/event",
59-
options=make_request_options(
60-
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
61-
),
62-
cast_to=cast(Any, EventListResponse), # Union types cannot be passed in as arguments in the type system
56+
return self._get(
57+
"/event",
58+
options=make_request_options(
59+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
6360
),
61+
cast_to=cast(Any, EventListResponse), # Union types cannot be passed in as arguments in the type system
62+
stream=True,
63+
stream_cls=Stream[EventListResponse],
6464
)
6565

6666

@@ -93,17 +93,16 @@ async def list(
9393
extra_query: Query | None = None,
9494
extra_body: Body | None = None,
9595
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
96-
) -> EventListResponse:
96+
) -> AsyncStream[EventListResponse]:
9797
"""Get events"""
98-
return cast(
99-
EventListResponse,
100-
await self._get(
101-
"/event",
102-
options=make_request_options(
103-
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
104-
),
105-
cast_to=cast(Any, EventListResponse), # Union types cannot be passed in as arguments in the type system
98+
return await self._get(
99+
"/event",
100+
options=make_request_options(
101+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
106102
),
103+
cast_to=cast(Any, EventListResponse), # Union types cannot be passed in as arguments in the type system
104+
stream=True,
105+
stream_cls=AsyncStream[EventListResponse],
107106
)
108107

109108

tests/api_resources/test_event.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import pytest
99

1010
from opencode_ai import Opencode, AsyncOpencode
11-
from tests.utils import assert_matches_type
12-
from opencode_ai.types import EventListResponse
1311

1412
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
1513

@@ -20,18 +18,17 @@ class TestEvent:
2018
@pytest.mark.skip()
2119
@parametrize
2220
def test_method_list(self, client: Opencode) -> None:
23-
event = client.event.list()
24-
assert_matches_type(EventListResponse, event, path=["response"])
21+
event_stream = client.event.list()
22+
event_stream.response.close()
2523

2624
@pytest.mark.skip()
2725
@parametrize
2826
def test_raw_response_list(self, client: Opencode) -> None:
2927
response = client.event.with_raw_response.list()
3028

31-
assert response.is_closed is True
3229
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
33-
event = response.parse()
34-
assert_matches_type(EventListResponse, event, path=["response"])
30+
stream = response.parse()
31+
stream.close()
3532

3633
@pytest.mark.skip()
3734
@parametrize
@@ -40,8 +37,8 @@ def test_streaming_response_list(self, client: Opencode) -> None:
4037
assert not response.is_closed
4138
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
4239

43-
event = response.parse()
44-
assert_matches_type(EventListResponse, event, path=["response"])
40+
stream = response.parse()
41+
stream.close()
4542

4643
assert cast(Any, response.is_closed) is True
4744

@@ -54,18 +51,17 @@ class TestAsyncEvent:
5451
@pytest.mark.skip()
5552
@parametrize
5653
async def test_method_list(self, async_client: AsyncOpencode) -> None:
57-
event = await async_client.event.list()
58-
assert_matches_type(EventListResponse, event, path=["response"])
54+
event_stream = await async_client.event.list()
55+
await event_stream.response.aclose()
5956

6057
@pytest.mark.skip()
6158
@parametrize
6259
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
6360
response = await async_client.event.with_raw_response.list()
6461

65-
assert response.is_closed is True
6662
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
67-
event = await response.parse()
68-
assert_matches_type(EventListResponse, event, path=["response"])
63+
stream = await response.parse()
64+
await stream.close()
6965

7066
@pytest.mark.skip()
7167
@parametrize
@@ -74,7 +70,7 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
7470
assert not response.is_closed
7571
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
7672

77-
event = await response.parse()
78-
assert_matches_type(EventListResponse, event, path=["response"])
73+
stream = await response.parse()
74+
await stream.close()
7975

8076
assert cast(Any, response.is_closed) is True

tests/test_client.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from opencode_ai import Opencode, AsyncOpencode, APIResponseValidationError
2525
from opencode_ai._types import Omit
2626
from opencode_ai._models import BaseModel, FinalRequestOptions
27+
from opencode_ai._streaming import Stream, AsyncStream
2728
from opencode_ai._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
2829
from opencode_ai._base_client import (
2930
DEFAULT_TIMEOUT,
@@ -624,6 +625,17 @@ def test_client_max_retries_validation(self) -> None:
624625
with pytest.raises(TypeError, match=r"max_retries cannot be None"):
625626
Opencode(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
626627

628+
@pytest.mark.respx(base_url=base_url)
629+
def test_default_stream_cls(self, respx_mock: MockRouter) -> None:
630+
class Model(BaseModel):
631+
name: str
632+
633+
respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
634+
635+
stream = self.client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model])
636+
assert isinstance(stream, Stream)
637+
stream.response.close()
638+
627639
@pytest.mark.respx(base_url=base_url)
628640
def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
629641
class Model(BaseModel):
@@ -1390,6 +1402,18 @@ async def test_client_max_retries_validation(self) -> None:
13901402
with pytest.raises(TypeError, match=r"max_retries cannot be None"):
13911403
AsyncOpencode(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
13921404

1405+
@pytest.mark.respx(base_url=base_url)
1406+
@pytest.mark.asyncio
1407+
async def test_default_stream_cls(self, respx_mock: MockRouter) -> None:
1408+
class Model(BaseModel):
1409+
name: str
1410+
1411+
respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
1412+
1413+
stream = await self.client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model])
1414+
assert isinstance(stream, AsyncStream)
1415+
await stream.response.aclose()
1416+
13931417
@pytest.mark.respx(base_url=base_url)
13941418
@pytest.mark.asyncio
13951419
async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:

0 commit comments

Comments
 (0)