From a3ef1141526815f0bfd186b8368b5d8f041865a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:03:26 +0000 Subject: [PATCH 1/7] chore(internal): codegen related update --- mypy.ini | 50 ------------------------------------------------ pyproject.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 0a62aa9d6..000000000 --- a/mypy.ini +++ /dev/null @@ -1,50 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -# -# We also exclude our `tests` as mypy doesn't always infer -# types correctly and Pyright will still catch any type errors. -exclude = ^(src/runloop_api_client/_files\.py|_dev/.*\.py|tests/.*)$ - -strict_equality = True -implicit_reexport = True -check_untyped_defs = True -no_implicit_optional = True - -warn_return_any = True -warn_unreachable = True -warn_unused_configs = True - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value,overload-cannot-match - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index 52e917e86..cf2752640 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -166,6 +166,58 @@ reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +[tool.mypy] +pretty = true +show_error_codes = true + +# Exclude _files.py because mypy isn't smart enough to apply +# the correct type narrowing and as this is an internal module +# it's fine to just use Pyright. +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +exclude = ['src/runloop_api_client/_files.py', '_dev/.*.py', 'tests/.*'] + +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true + +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true + + [tool.ruff] line-length = 120 output-format = "grouped" From 9dcd7d5200270490c81547f342328d6abe691380 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:03:35 +0000 Subject: [PATCH 2/7] feat(api): api update --- .stats.yml | 8 +- api.md | 9 +- .../resources/devboxes/executions.py | 38 ++-- .../types/devboxes/__init__.py | 14 +- ...execution_stream_stderr_updates_params.py} | 4 +- ...xecution_stream_stderr_updates_response.py | 7 + .../execution_stream_stdout_updates_params.py | 14 ++ ...xecution_stream_stdout_updates_response.py | 7 + .../types/devboxes/execution_update_chunk.py | 40 ---- .../api_resources/devboxes/test_executions.py | 180 +++++++++++++----- 10 files changed, 205 insertions(+), 116 deletions(-) rename src/runloop_api_client/types/devboxes/{execution_stream_updates_params.py => execution_stream_stderr_updates_params.py} (70%) create mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py create mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py create mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py delete mode 100644 src/runloop_api_client/types/devboxes/execution_update_chunk.py diff --git a/.stats.yml b/.stats.yml index 900ce23a3..3d29bf629 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 101 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-6b4c63a026f224ec02ccd715e063e07107b545bb859218afaac2b3df84cd227a.yml -openapi_spec_hash: 76072cd766a9c45cff8890bb2bb8b1d5 -config_hash: a8ac5e38099129b07ae4decb0774719d +configured_endpoints: 102 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-d709c5c5b17fec3a155d3367a00e123712be9fabdc802d7f2069b77b79883057.yml +openapi_spec_hash: 7a92b2d1612b211cc7507bdc49adf657 +config_hash: 3b21dd730a91da5e18dd16ab3e1870a8 diff --git a/api.md b/api.md index 0c827fe6b..7098ecd64 100644 --- a/api.md +++ b/api.md @@ -257,7 +257,11 @@ Methods: Types: ```python -from runloop_api_client.types.devboxes import ExecutionUpdateChunk +from runloop_api_client.types.devboxes import ( + ExecutionUpdateChunk, + ExecutionStreamStderrUpdatesResponse, + ExecutionStreamStdoutUpdatesResponse, +) ``` Methods: @@ -266,7 +270,8 @@ Methods: - client.devboxes.executions.execute_async(id, \*\*params) -> DevboxAsyncExecutionDetailView - client.devboxes.executions.execute_sync(id, \*\*params) -> DevboxExecutionDetailView - client.devboxes.executions.kill(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView -- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView +- client.devboxes.executions.stream_stderr_updates(execution_id, \*, devbox_id, \*\*params) -> str +- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> str # Scenarios diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index 0002113ff..2bd7a728d 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -26,12 +26,14 @@ execution_retrieve_params, execution_execute_sync_params, execution_execute_async_params, - execution_stream_updates_params, + execution_stream_stderr_updates_params, + execution_stream_stdout_updates_params, ) from ...lib.polling_async import async_poll_until from ...types.devbox_execution_detail_view import DevboxExecutionDetailView -from ...types.devboxes.execution_update_chunk import ExecutionUpdateChunk from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView +from ...types.devboxes.execution_stream_stderr_updates_response import ExecutionStreamStderrUpdatesResponse +from ...types.devboxes.execution_stream_stdout_updates_response import ExecutionStreamStdoutUpdatesResponse __all__ = ["ExecutionsResource", "AsyncExecutionsResource"] @@ -338,7 +340,7 @@ def stream_stdout_updates( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Stream[ExecutionUpdateChunk]: + ) -> Stream[ExecutionStreamStdoutUpdatesResponse]: """ Tails the stdout logs for the given execution with SSE streaming @@ -366,7 +368,7 @@ def stream_stdout_updates( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -384,7 +386,7 @@ def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]: extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": new_offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -445,7 +447,7 @@ def stream_stderr_updates( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -463,7 +465,7 @@ def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]: extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -765,7 +767,7 @@ async def kill( cast_to=DevboxAsyncExecutionDetailView, ) - async def stream_updates( + async def stream_stderr_updates( self, execution_id: str, *, @@ -777,9 +779,9 @@ async def stream_updates( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncStream[ExecutionUpdateChunk]: + ) -> AsyncStream[ExecutionStreamStderrUpdatesResponse]: """ - Tails the logs for the given execution with SSE streaming + Tails the stderr logs for the given execution with SSE streaming Args: offset: The byte offset to start the stream from @@ -799,14 +801,14 @@ async def stream_updates( # If caller requested a raw or streaming response wrapper, return the underlying stream as-is if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_updates", + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -817,14 +819,14 @@ async def stream_updates( async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]: new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_updates", + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -885,7 +887,7 @@ async def stream_stdout_updates( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -903,7 +905,7 @@ async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateC extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -964,7 +966,7 @@ async def stream_stderr_updates( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, @@ -982,7 +984,7 @@ async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateC extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams + {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), ), cast_to=DevboxAsyncExecutionDetailView, diff --git a/src/runloop_api_client/types/devboxes/__init__.py b/src/runloop_api_client/types/devboxes/__init__.py index 43e642744..7ee0b171d 100644 --- a/src/runloop_api_client/types/devboxes/__init__.py +++ b/src/runloop_api_client/types/devboxes/__init__.py @@ -52,7 +52,6 @@ from .watched_file_response import WatchedFileResponse as WatchedFileResponse from .code_description_param import CodeDescriptionParam as CodeDescriptionParam from .computer_create_params import ComputerCreateParams as ComputerCreateParams -from .execution_update_chunk import ExecutionUpdateChunk as ExecutionUpdateChunk from .file_contents_response import FileContentsResponse as FileContentsResponse from .health_status_response import HealthStatusResponse as HealthStatusResponse from .lsp_diagnostics_params import LspDiagnosticsParams as LspDiagnosticsParams @@ -77,7 +76,6 @@ from .code_action_application_result import CodeActionApplicationResult as CodeActionApplicationResult from .execution_execute_async_params import ExecutionExecuteAsyncParams as ExecutionExecuteAsyncParams from .lsp_set_watch_directory_params import LspSetWatchDirectoryParams as LspSetWatchDirectoryParams -from .execution_stream_updates_params import ExecutionStreamUpdatesParams as ExecutionStreamUpdatesParams from .lsp_get_code_segment_info_params import LspGetCodeSegmentInfoParams as LspGetCodeSegmentInfoParams from .lsp_set_watch_directory_response import LspSetWatchDirectoryResponse as LspSetWatchDirectoryResponse from .computer_mouse_interaction_params import ComputerMouseInteractionParams as ComputerMouseInteractionParams @@ -91,6 +89,18 @@ from .computer_keyboard_interaction_response import ( ComputerKeyboardInteractionResponse as ComputerKeyboardInteractionResponse, ) +from .execution_stream_stderr_updates_params import ( + ExecutionStreamStderrUpdatesParams as ExecutionStreamStderrUpdatesParams, +) +from .execution_stream_stdout_updates_params import ( + ExecutionStreamStdoutUpdatesParams as ExecutionStreamStdoutUpdatesParams, +) +from .execution_stream_stderr_updates_response import ( + ExecutionStreamStderrUpdatesResponse as ExecutionStreamStderrUpdatesResponse, +) +from .execution_stream_stdout_updates_response import ( + ExecutionStreamStdoutUpdatesResponse as ExecutionStreamStdoutUpdatesResponse, +) from .lsp_get_code_actions_for_diagnostic_params import ( LspGetCodeActionsForDiagnosticParams as LspGetCodeActionsForDiagnosticParams, ) diff --git a/src/runloop_api_client/types/devboxes/execution_stream_updates_params.py b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py similarity index 70% rename from src/runloop_api_client/types/devboxes/execution_stream_updates_params.py rename to src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py index 3473cbc2c..b82639556 100644 --- a/src/runloop_api_client/types/devboxes/execution_stream_updates_params.py +++ b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py @@ -4,10 +4,10 @@ from typing_extensions import Required, TypedDict -__all__ = ["ExecutionStreamUpdatesParams"] +__all__ = ["ExecutionStreamStderrUpdatesParams"] -class ExecutionStreamUpdatesParams(TypedDict, total=False): +class ExecutionStreamStderrUpdatesParams(TypedDict, total=False): devbox_id: Required[str] offset: str diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py new file mode 100644 index 000000000..1f7ec2adc --- /dev/null +++ b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["ExecutionStreamStderrUpdatesResponse"] + +ExecutionStreamStderrUpdatesResponse: TypeAlias = str diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py new file mode 100644 index 000000000..7296156b0 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ExecutionStreamStdoutUpdatesParams"] + + +class ExecutionStreamStdoutUpdatesParams(TypedDict, total=False): + devbox_id: Required[str] + + offset: str + """The byte offset to start the stream from""" diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py new file mode 100644 index 000000000..da6e2bd20 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["ExecutionStreamStdoutUpdatesResponse"] + +ExecutionStreamStdoutUpdatesResponse: TypeAlias = str diff --git a/src/runloop_api_client/types/devboxes/execution_update_chunk.py b/src/runloop_api_client/types/devboxes/execution_update_chunk.py deleted file mode 100644 index f5366d0cb..000000000 --- a/src/runloop_api_client/types/devboxes/execution_update_chunk.py +++ /dev/null @@ -1,40 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from typing_extensions import Literal - -from ..._models import BaseModel - -__all__ = ["ExecutionUpdateChunk"] - - -class ExecutionUpdateChunk(BaseModel): - devbox_id: str - """Devbox id where command was executed.""" - - execution_id: str - """Ephemeral id of the execution in progress.""" - - status: Literal["queued", "running", "completed"] - """Current status of the execution.""" - - exit_status: Optional[int] = None - """Exit code of command execution. - - This field will remain unset until the execution has completed. - """ - - shell_name: Optional[str] = None - """Shell name.""" - - stderr: Optional[str] = None - """Standard error generated by command. - - This field will remain unset until the execution has completed. - """ - - stdout: Optional[str] = None - """Standard out generated by command. - - This field will remain unset until the execution has completed. - """ diff --git a/tests/api_resources/devboxes/test_executions.py b/tests/api_resources/devboxes/test_executions.py index 9b932b160..0dd236647 100755 --- a/tests/api_resources/devboxes/test_executions.py +++ b/tests/api_resources/devboxes/test_executions.py @@ -239,22 +239,16 @@ def test_path_params_kill(self, client: Runloop) -> None: ) @parametrize - def test_method_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - execution_stream = client.devboxes.executions.stream_stdout_updates( + def test_method_stream_stderr_updates(self, client: Runloop) -> None: + execution_stream = client.devboxes.executions.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", ) execution_stream.response.close() @parametrize - def test_method_stream_stdout_updates_with_all_params(self, client: Runloop, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - execution_stream = client.devboxes.executions.stream_stdout_updates( + def test_method_stream_stderr_updates_with_all_params(self, client: Runloop) -> None: + execution_stream = client.devboxes.executions.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", offset="offset", @@ -262,11 +256,8 @@ def test_method_stream_stdout_updates_with_all_params(self, client: Runloop, res execution_stream.response.close() @parametrize - def test_raw_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - response = client.devboxes.executions.with_raw_response.stream_stdout_updates( + def test_raw_response_stream_stderr_updates(self, client: Runloop) -> None: + response = client.devboxes.executions.with_raw_response.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", ) @@ -276,11 +267,8 @@ def test_raw_response_stream_stdout_updates(self, client: Runloop, respx_mock: M stream.close() @parametrize - def test_streaming_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - with client.devboxes.executions.with_streaming_response.stream_stdout_updates( + def test_streaming_response_stream_stderr_updates(self, client: Runloop) -> None: + with client.devboxes.executions.with_streaming_response.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", ) as response: @@ -293,9 +281,9 @@ def test_streaming_response_stream_stdout_updates(self, client: Runloop, respx_m assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_stream_stdout_updates(self, client: Runloop) -> None: + def test_path_params_stream_stderr_updates(self, client: Runloop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): - client.devboxes.executions.with_raw_response.stream_stdout_updates( + client.devboxes.executions.with_raw_response.stream_stderr_updates( execution_id="execution_id", devbox_id="", ) @@ -362,6 +350,62 @@ def fake_get(_path: str, *, options: Any, **_kwargs: Any): assert calls[1] is not None assert seen_offsets == ["5", "9", "10"] + with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): + client.devboxes.executions.with_raw_response.stream_stderr_updates( + execution_id="", + devbox_id="devbox_id", + ) + + @parametrize + def test_method_stream_stdout_updates(self, client: Runloop) -> None: + execution_stream = client.devboxes.executions.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + ) + execution_stream.response.close() + + @parametrize + def test_method_stream_stdout_updates_with_all_params(self, client: Runloop) -> None: + execution_stream = client.devboxes.executions.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + offset="offset", + ) + execution_stream.response.close() + + @parametrize + def test_raw_response_stream_stdout_updates(self, client: Runloop) -> None: + response = client.devboxes.executions.with_raw_response.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_stream_stdout_updates(self, client: Runloop) -> None: + with client.devboxes.executions.with_streaming_response.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_stream_stdout_updates(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): + client.devboxes.executions.with_raw_response.stream_stdout_updates( + execution_id="execution_id", + devbox_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="", @@ -755,24 +799,16 @@ async def test_path_params_kill(self, async_client: AsyncRunloop) -> None: ) @parametrize - async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - execution_stream = await async_client.devboxes.executions.stream_stdout_updates( + async def test_method_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: + execution_stream = await async_client.devboxes.executions.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", ) await execution_stream.response.aclose() @parametrize - async def test_method_stream_stdout_updates_with_all_params( - self, async_client: AsyncRunloop, respx_mock: MockRouter - ) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - execution_stream = await async_client.devboxes.executions.stream_stdout_updates( + async def test_method_stream_stderr_updates_with_all_params(self, async_client: AsyncRunloop) -> None: + execution_stream = await async_client.devboxes.executions.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", offset="offset", @@ -780,11 +816,8 @@ async def test_method_stream_stdout_updates_with_all_params( await execution_stream.response.aclose() @parametrize - async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( + async def test_raw_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.executions.with_raw_response.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", ) @@ -794,13 +827,8 @@ async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunlo await stream.close() @parametrize - async def test_streaming_response_stream_stdout_updates( - self, async_client: AsyncRunloop, respx_mock: MockRouter - ) -> None: - respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( - return_value=httpx.Response(200) - ) - async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates( + async def test_streaming_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.executions.with_streaming_response.stream_stderr_updates( execution_id="execution_id", devbox_id="devbox_id", ) as response: @@ -813,9 +841,9 @@ async def test_streaming_response_stream_stdout_updates( assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: + async def test_path_params_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): - await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( + await async_client.devboxes.executions.with_raw_response.stream_stderr_updates( execution_id="execution_id", devbox_id="", ) @@ -883,6 +911,62 @@ async def fake_get(_path: str, *, options: Any, **_kwargs: Any): assert calls[1] is not None assert seen_offsets == ["5", "9", "10"] + with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): + await async_client.devboxes.executions.with_raw_response.stream_stderr_updates( + execution_id="", + devbox_id="devbox_id", + ) + + @parametrize + async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: + execution_stream = await async_client.devboxes.executions.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + ) + await execution_stream.response.aclose() + + @parametrize + async def test_method_stream_stdout_updates_with_all_params(self, async_client: AsyncRunloop) -> None: + execution_stream = await async_client.devboxes.executions.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + offset="offset", + ) + await execution_stream.response.aclose() + + @parametrize + async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = await response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates( + execution_id="execution_id", + devbox_id="devbox_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): + await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( + execution_id="execution_id", + devbox_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="", From 9a11b39772e3980738d78a06efdfba60edbab3a7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 03:46:08 +0000 Subject: [PATCH 3/7] chore(tests): simplify `get_platform` test `nest_asyncio` is archived and broken on some platforms so it's not worth keeping in our test suite. --- pyproject.toml | 1 - requirements-dev.lock | 1 - tests/test_client.py | 53 +++++-------------------------------------- 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cf2752640..3d68d633a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,6 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0", "pytest-xdist>=3.6.1", ] diff --git a/requirements-dev.lock b/requirements-dev.lock index de857a2d6..2a74d01aa 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -75,7 +75,6 @@ multidict==6.4.4 mypy==1.14.1 mypy-extensions==1.0.0 # via mypy -nest-asyncio==1.6.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 diff --git a/tests/test_client.py b/tests/test_client.py index cb84f9ebe..ca53896b3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,13 +6,10 @@ import os import sys import json -import time import asyncio import inspect -import subprocess import tracemalloc from typing import Any, Union, cast -from textwrap import dedent from unittest import mock from typing_extensions import Literal @@ -23,14 +20,17 @@ from runloop_api_client import Runloop, AsyncRunloop, APIResponseValidationError from runloop_api_client._types import Omit +from runloop_api_client._utils import asyncify from runloop_api_client._models import BaseModel, FinalRequestOptions from runloop_api_client._exceptions import RunloopError, APIStatusError, APITimeoutError, APIResponseValidationError from runloop_api_client._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + OtherPlatform, DefaultHttpxClient, DefaultAsyncHttpxClient, + get_platform, make_request_options, ) @@ -1727,50 +1727,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" - def test_get_platform(self) -> None: - # A previous implementation of asyncify could leave threads unterminated when - # used with nest_asyncio. - # - # Since nest_asyncio.apply() is global and cannot be un-applied, this - # test is run in a separate process to avoid affecting other tests. - test_code = dedent(""" - import asyncio - import nest_asyncio - import threading - - from runloop_api_client._utils import asyncify - from runloop_api_client._base_client import get_platform - - async def test_main() -> None: - result = await asyncify(get_platform)() - print(result) - for thread in threading.enumerate(): - print(thread.name) - - nest_asyncio.apply() - asyncio.run(test_main()) - """) - with subprocess.Popen( - [sys.executable, "-c", test_code], - text=True, - ) as process: - timeout = 10 # seconds - - start_time = time.monotonic() - while True: - return_code = process.poll() - if return_code is not None: - if return_code != 0: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - - # success - break - - if time.monotonic() - start_time > timeout: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") - - time.sleep(0.1) + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly From 6f4e3974439b534a205abcc347656dd349b7e256 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:51:53 +0000 Subject: [PATCH 4/7] feat(api): api update --- .stats.yml | 6 +- api.md | 10 +- .../resources/devboxes/executions.py | 333 ++++-------------- .../types/devboxes/__init__.py | 7 +- ...xecution_stream_stderr_updates_response.py | 7 - ...xecution_stream_stdout_updates_response.py | 7 - .../types/devboxes/execution_update_chunk.py | 15 + 7 files changed, 84 insertions(+), 301 deletions(-) delete mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py delete mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py create mode 100644 src/runloop_api_client/types/devboxes/execution_update_chunk.py diff --git a/.stats.yml b/.stats.yml index 3d29bf629..3426ad757 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 102 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-d709c5c5b17fec3a155d3367a00e123712be9fabdc802d7f2069b77b79883057.yml -openapi_spec_hash: 7a92b2d1612b211cc7507bdc49adf657 -config_hash: 3b21dd730a91da5e18dd16ab3e1870a8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-aa2f6b7bd5f38374ef743af2f5399fc37358408730dcbb1305e181bb91a2e903.yml +openapi_spec_hash: a3f793428f6b5de9a59ba8c80ee8bbea +config_hash: a74944d06979c9df5e08aee874ae5caa diff --git a/api.md b/api.md index 7098ecd64..807a478ed 100644 --- a/api.md +++ b/api.md @@ -257,11 +257,7 @@ Methods: Types: ```python -from runloop_api_client.types.devboxes import ( - ExecutionUpdateChunk, - ExecutionStreamStderrUpdatesResponse, - ExecutionStreamStdoutUpdatesResponse, -) +from runloop_api_client.types.devboxes import ExecutionUpdateChunk ``` Methods: @@ -270,8 +266,8 @@ Methods: - client.devboxes.executions.execute_async(id, \*\*params) -> DevboxAsyncExecutionDetailView - client.devboxes.executions.execute_sync(id, \*\*params) -> DevboxExecutionDetailView - client.devboxes.executions.kill(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView -- client.devboxes.executions.stream_stderr_updates(execution_id, \*, devbox_id, \*\*params) -> str -- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> str +- client.devboxes.executions.stream_stderr_updates(execution_id, \*, devbox_id, \*\*params) -> ExecutionUpdateChunk +- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> ExecutionUpdateChunk # Scenarios diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index 2bd7a728d..c8a40a4e6 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional, cast +from typing import Optional import httpx @@ -16,8 +16,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._constants import DEFAULT_TIMEOUT, RAW_RESPONSE_HEADER -from ..._streaming import Stream, AsyncStream, ReconnectingStream, AsyncReconnectingStream +from ..._constants import DEFAULT_TIMEOUT +from ..._streaming import Stream, AsyncStream from ..._exceptions import APIStatusError, APITimeoutError from ...lib.polling import PollingConfig, poll_until from ..._base_client import make_request_options @@ -31,9 +31,8 @@ ) from ...lib.polling_async import async_poll_until from ...types.devbox_execution_detail_view import DevboxExecutionDetailView +from ...types.devboxes.execution_update_chunk import ExecutionUpdateChunk from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView -from ...types.devboxes.execution_stream_stderr_updates_response import ExecutionStreamStderrUpdatesResponse -from ...types.devboxes.execution_stream_stdout_updates_response import ExecutionStreamStdoutUpdatesResponse __all__ = ["ExecutionsResource", "AsyncExecutionsResource"] @@ -328,7 +327,7 @@ def kill( cast_to=DevboxAsyncExecutionDetailView, ) - def stream_stdout_updates( + def stream_stderr_updates( self, execution_id: str, *, @@ -340,9 +339,9 @@ def stream_stdout_updates( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Stream[ExecutionStreamStdoutUpdatesResponse]: + ) -> Stream[ExecutionUpdateChunk]: """ - Tails the stdout logs for the given execution with SSE streaming + Tails the stderr logs for the given execution with SSE streaming Args: offset: The byte offset to start the stream from @@ -359,55 +358,23 @@ def stream_stdout_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): - return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams - ), - ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=Stream[ExecutionUpdateChunk], - ) - - def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]: - new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) - return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"offset": new_offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams - ), + return self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=Stream[ExecutionUpdateChunk], - ) - - initial_stream = create_stream(None) - - def get_offset(item: ExecutionUpdateChunk) -> str | None: - value = getattr(item, "offset", None) - if value is None: - return None - return str(value) - - return cast( - Stream[ExecutionUpdateChunk], - ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), + ), + cast_to=ExecutionUpdateChunk, + stream=True, + stream_cls=Stream[ExecutionUpdateChunk], ) - def stream_stderr_updates( + def stream_stdout_updates( self, execution_id: str, *, @@ -421,7 +388,7 @@ def stream_stderr_updates( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Stream[ExecutionUpdateChunk]: """ - Tails the stderr logs for the given execution with SSE streaming + Tails the stdout logs for the given execution with SSE streaming Args: offset: The byte offset to start the stream from @@ -438,52 +405,20 @@ def stream_stderr_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): - return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), - ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=Stream[ExecutionUpdateChunk], - ) - - def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]: - new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) - return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), + return self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=Stream[ExecutionUpdateChunk], - ) - - initial_stream = create_stream(None) - - def get_offset(item: ExecutionUpdateChunk) -> str | None: - value = getattr(item, "offset", None) - if value is None: - return None - return str(value) - - return cast( - Stream[ExecutionUpdateChunk], - ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), + ), + cast_to=ExecutionUpdateChunk, + stream=True, + stream_cls=Stream[ExecutionUpdateChunk], ) @@ -779,7 +714,7 @@ async def stream_stderr_updates( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncStream[ExecutionStreamStderrUpdatesResponse]: + ) -> AsyncStream[ExecutionUpdateChunk]: """ Tails the stderr logs for the given execution with SSE streaming @@ -798,53 +733,20 @@ async def stream_stderr_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - # If caller requested a raw or streaming response wrapper, return the underlying stream as-is - if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), - ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], - ) - - async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]: - new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), + return await self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], - ) - - initial_stream = await create_stream(None) - - def get_offset(item: ExecutionUpdateChunk) -> str | None: - value = getattr(item, "offset", None) - if value is None: - return None - return str(value) - - return cast( - AsyncStream[ExecutionUpdateChunk], - AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), + ), + cast_to=ExecutionUpdateChunk, + stream=True, + stream_cls=AsyncStream[ExecutionUpdateChunk], ) async def stream_stdout_updates( @@ -878,131 +780,20 @@ async def stream_stdout_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), - ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], - ) - - async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]: - new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), - ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], - ) - - initial_stream = await create_stream(None) - - def get_offset(item: ExecutionUpdateChunk) -> str | None: - value = getattr(item, "offset", None) - if value is None: - return None - return str(value) - - return cast( - AsyncStream[ExecutionUpdateChunk], - AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), - ) - - async def stream_stderr_updates( - self, - execution_id: str, - *, - devbox_id: str, - offset: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncStream[ExecutionUpdateChunk]: - """ - Tails the stderr logs for the given execution with SSE streaming - - Args: - offset: The byte offset to start the stream from - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not devbox_id: - raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") - if not execution_id: - raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), - ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], - ) - - async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]: - new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams - ), + return await self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams ), - cast_to=DevboxAsyncExecutionDetailView, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], - ) - - initial_stream = await create_stream(None) - - def get_offset(item: ExecutionUpdateChunk) -> str | None: - value = getattr(item, "offset", None) - if value is None: - return None - return str(value) - - return cast( - AsyncStream[ExecutionUpdateChunk], - AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), + ), + cast_to=ExecutionUpdateChunk, + stream=True, + stream_cls=AsyncStream[ExecutionUpdateChunk], ) diff --git a/src/runloop_api_client/types/devboxes/__init__.py b/src/runloop_api_client/types/devboxes/__init__.py index 7ee0b171d..472d68e9a 100644 --- a/src/runloop_api_client/types/devboxes/__init__.py +++ b/src/runloop_api_client/types/devboxes/__init__.py @@ -52,6 +52,7 @@ from .watched_file_response import WatchedFileResponse as WatchedFileResponse from .code_description_param import CodeDescriptionParam as CodeDescriptionParam from .computer_create_params import ComputerCreateParams as ComputerCreateParams +from .execution_update_chunk import ExecutionUpdateChunk as ExecutionUpdateChunk from .file_contents_response import FileContentsResponse as FileContentsResponse from .health_status_response import HealthStatusResponse as HealthStatusResponse from .lsp_diagnostics_params import LspDiagnosticsParams as LspDiagnosticsParams @@ -95,12 +96,6 @@ from .execution_stream_stdout_updates_params import ( ExecutionStreamStdoutUpdatesParams as ExecutionStreamStdoutUpdatesParams, ) -from .execution_stream_stderr_updates_response import ( - ExecutionStreamStderrUpdatesResponse as ExecutionStreamStderrUpdatesResponse, -) -from .execution_stream_stdout_updates_response import ( - ExecutionStreamStdoutUpdatesResponse as ExecutionStreamStdoutUpdatesResponse, -) from .lsp_get_code_actions_for_diagnostic_params import ( LspGetCodeActionsForDiagnosticParams as LspGetCodeActionsForDiagnosticParams, ) diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py deleted file mode 100644 index 1f7ec2adc..000000000 --- a/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import TypeAlias - -__all__ = ["ExecutionStreamStderrUpdatesResponse"] - -ExecutionStreamStderrUpdatesResponse: TypeAlias = str diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py deleted file mode 100644 index da6e2bd20..000000000 --- a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import TypeAlias - -__all__ = ["ExecutionStreamStdoutUpdatesResponse"] - -ExecutionStreamStdoutUpdatesResponse: TypeAlias = str diff --git a/src/runloop_api_client/types/devboxes/execution_update_chunk.py b/src/runloop_api_client/types/devboxes/execution_update_chunk.py new file mode 100644 index 000000000..eda7add55 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/execution_update_chunk.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["ExecutionUpdateChunk"] + + +class ExecutionUpdateChunk(BaseModel): + output: str + """The latest log stream chunk.""" + + offset: Optional[int] = None + """The byte offset of this chunk of log stream.""" From 2cb906951cf082a14adc7cf27a1d27816341f2a7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:35:09 +0000 Subject: [PATCH 5/7] feat(api): api update --- .stats.yml | 6 +++--- api.md | 1 + src/runloop_api_client/resources/devboxes/executions.py | 6 ++++++ .../types/devboxes/execution_kill_params.py | 4 ++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3426ad757..1eaf9aeec 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 102 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-aa2f6b7bd5f38374ef743af2f5399fc37358408730dcbb1305e181bb91a2e903.yml -openapi_spec_hash: a3f793428f6b5de9a59ba8c80ee8bbea -config_hash: a74944d06979c9df5e08aee874ae5caa +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-a8e2289b32b616e0bd7e15995a6553d0335bd21e30617a013d07450f8f5a7a20.yml +openapi_spec_hash: cd13a7af1ffe64c7ce8753f87786e01f +config_hash: 457068371129b47b67f0f2d6b8267368 diff --git a/api.md b/api.md index 807a478ed..3076ddf36 100644 --- a/api.md +++ b/api.md @@ -73,6 +73,7 @@ Types: from runloop_api_client.types import ( DevboxAsyncExecutionDetailView, DevboxExecutionDetailView, + DevboxKillExecutionRequest, DevboxListView, DevboxSnapshotListView, DevboxSnapshotView, diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index c8a40a4e6..0cc776a7f 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -300,6 +300,9 @@ def kill( killing the launched process. Optionally kill the entire process group. Args: + kill_process_group: Whether to kill the entire process group (default: false). If true, kills all + processes in the same process group as the target process. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -673,6 +676,9 @@ async def kill( killing the launched process. Optionally kill the entire process group. Args: + kill_process_group: Whether to kill the entire process group (default: false). If true, kills all + processes in the same process group as the target process. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/runloop_api_client/types/devboxes/execution_kill_params.py b/src/runloop_api_client/types/devboxes/execution_kill_params.py index 384da945c..0df5c8615 100644 --- a/src/runloop_api_client/types/devboxes/execution_kill_params.py +++ b/src/runloop_api_client/types/devboxes/execution_kill_params.py @@ -12,3 +12,7 @@ class ExecutionKillParams(TypedDict, total=False): devbox_id: Required[str] kill_process_group: Optional[bool] + """Whether to kill the entire process group (default: false). + + If true, kills all processes in the same process group as the target process. + """ From 0ddad80f6bf03558e2b501ffd6931995a5baa782 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:35:28 +0000 Subject: [PATCH 6/7] release: 0.59.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ pyproject.toml | 2 +- src/runloop_api_client/_version.py | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2fbefb942..85c311828 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.58.0" + ".": "0.59.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5095bbba2..7b00c7c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.59.0 (2025-09-08) + +Full Changelog: [v0.58.0...v0.59.0](https://github.com/runloopai/api-client-python/compare/v0.58.0...v0.59.0) + +### Features + +* **api:** api update ([d28f600](https://github.com/runloopai/api-client-python/commit/d28f60090e9c8c3c508812bfaf4bacbdd0b64330)) +* **api:** api update ([a7b44bc](https://github.com/runloopai/api-client-python/commit/a7b44bc091060518d507dbd806694de9ba09c1f8)) +* **api:** api update ([39c93bd](https://github.com/runloopai/api-client-python/commit/39c93bd7061e77c01fbb1cd913ede94ce90664c4)) + + +### Chores + +* **internal:** codegen related update ([4ed8b7a](https://github.com/runloopai/api-client-python/commit/4ed8b7a3ab98378214f74e79202051260f8488da)) +* **tests:** simplify `get_platform` test ([6b38a37](https://github.com/runloopai/api-client-python/commit/6b38a37e0e3630b360090fd4ab91b4ffc431c46c)) + ## 0.58.0 (2025-09-04) Full Changelog: [v0.57.0...v0.58.0](https://github.com/runloopai/api-client-python/compare/v0.57.0...v0.58.0) diff --git a/pyproject.toml b/pyproject.toml index 3d68d633a..3e52c5ea7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "0.58.0" +version = "0.59.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index d2b85015b..533907a8a 100644 --- a/src/runloop_api_client/_version.py +++ b/src/runloop_api_client/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runloop_api_client" -__version__ = "0.58.0" # x-release-please-version +__version__ = "0.59.0" # x-release-please-version From b8c6195ee311cdaa4734bc1ea2458b7b5271ce4d Mon Sep 17 00:00:00 2001 From: Albert Li Date: Tue, 9 Sep 2025 13:01:03 -0700 Subject: [PATCH 7/7] Fix merge conflicts --- .../resources/devboxes/executions.py | 243 ++++++++++++++---- .../api_resources/devboxes/test_executions.py | 180 ++++--------- 2 files changed, 236 insertions(+), 187 deletions(-) diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index 0cc776a7f..27cd21c79 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Optional, cast import httpx @@ -16,8 +16,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._constants import DEFAULT_TIMEOUT -from ..._streaming import Stream, AsyncStream +from ..._constants import DEFAULT_TIMEOUT, RAW_RESPONSE_HEADER +from ..._streaming import Stream, AsyncStream, ReconnectingStream, AsyncReconnectingStream from ..._exceptions import APIStatusError, APITimeoutError from ...lib.polling import PollingConfig, poll_until from ..._base_client import make_request_options @@ -361,20 +361,53 @@ def stream_stderr_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams + if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): + return self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams + ), ), - ), - cast_to=ExecutionUpdateChunk, - stream=True, - stream_cls=Stream[ExecutionUpdateChunk], + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=Stream[ExecutionUpdateChunk], + ) + + def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]: + new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) + return self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": new_offset}, + execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams, + ), + ), + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=Stream[ExecutionUpdateChunk], + ) + + initial_stream = create_stream(None) + + def get_offset(item: ExecutionUpdateChunk) -> str | None: + value = getattr(item, "offset", None) + if value is None: + return None + return str(value) + + return cast( + Stream[ExecutionUpdateChunk], + ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), ) def stream_stdout_updates( @@ -408,20 +441,53 @@ def stream_stdout_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams + if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): + return self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams + ), ), - ), - cast_to=ExecutionUpdateChunk, - stream=True, - stream_cls=Stream[ExecutionUpdateChunk], + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=Stream[ExecutionUpdateChunk], + ) + + def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]: + new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) + return self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": new_offset}, + execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams, + ), + ), + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=Stream[ExecutionUpdateChunk], + ) + + initial_stream = create_stream(None) + + def get_offset(item: ExecutionUpdateChunk) -> str | None: + value = getattr(item, "offset", None) + if value is None: + return None + return str(value) + + return cast( + Stream[ExecutionUpdateChunk], + ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), ) @@ -739,20 +805,53 @@ async def stream_stderr_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams + if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): + return await self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams + ), ), - ), - cast_to=ExecutionUpdateChunk, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=AsyncStream[ExecutionUpdateChunk], + ) + + async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]: + new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) + return await self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": new_offset}, + execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams, + ), + ), + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=AsyncStream[ExecutionUpdateChunk], + ) + + initial_stream = await create_stream(None) + + def get_offset(item: ExecutionUpdateChunk) -> str | None: + value = getattr(item, "offset", None) + if value is None: + return None + return str(value) + + return cast( + AsyncStream[ExecutionUpdateChunk], + AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), ) async def stream_stdout_updates( @@ -786,20 +885,54 @@ async def stream_stdout_updates( raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}") if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") - return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams + # If caller requested a raw or streaming response wrapper, return the underlying stream as-is + if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER): + return await self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams + ), ), - ), - cast_to=ExecutionUpdateChunk, - stream=True, - stream_cls=AsyncStream[ExecutionUpdateChunk], + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=AsyncStream[ExecutionUpdateChunk], + ) + + async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]: + new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset) + return await self._get( + f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": new_offset}, + execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams, + ), + ), + cast_to=DevboxAsyncExecutionDetailView, + stream=True, + stream_cls=AsyncStream[ExecutionUpdateChunk], + ) + + initial_stream = await create_stream(None) + + def get_offset(item: ExecutionUpdateChunk) -> str | None: + value = getattr(item, "offset", None) + if value is None: + return None + return str(value) + + return cast( + AsyncStream[ExecutionUpdateChunk], + AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset), ) diff --git a/tests/api_resources/devboxes/test_executions.py b/tests/api_resources/devboxes/test_executions.py index 0dd236647..9b932b160 100755 --- a/tests/api_resources/devboxes/test_executions.py +++ b/tests/api_resources/devboxes/test_executions.py @@ -239,16 +239,22 @@ def test_path_params_kill(self, client: Runloop) -> None: ) @parametrize - def test_method_stream_stderr_updates(self, client: Runloop) -> None: - execution_stream = client.devboxes.executions.stream_stderr_updates( + def test_method_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + execution_stream = client.devboxes.executions.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", ) execution_stream.response.close() @parametrize - def test_method_stream_stderr_updates_with_all_params(self, client: Runloop) -> None: - execution_stream = client.devboxes.executions.stream_stderr_updates( + def test_method_stream_stdout_updates_with_all_params(self, client: Runloop, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + execution_stream = client.devboxes.executions.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", offset="offset", @@ -256,8 +262,11 @@ def test_method_stream_stderr_updates_with_all_params(self, client: Runloop) -> execution_stream.response.close() @parametrize - def test_raw_response_stream_stderr_updates(self, client: Runloop) -> None: - response = client.devboxes.executions.with_raw_response.stream_stderr_updates( + def test_raw_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + response = client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", ) @@ -267,8 +276,11 @@ def test_raw_response_stream_stderr_updates(self, client: Runloop) -> None: stream.close() @parametrize - def test_streaming_response_stream_stderr_updates(self, client: Runloop) -> None: - with client.devboxes.executions.with_streaming_response.stream_stderr_updates( + def test_streaming_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + with client.devboxes.executions.with_streaming_response.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", ) as response: @@ -281,9 +293,9 @@ def test_streaming_response_stream_stderr_updates(self, client: Runloop) -> None assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_stream_stderr_updates(self, client: Runloop) -> None: + def test_path_params_stream_stdout_updates(self, client: Runloop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): - client.devboxes.executions.with_raw_response.stream_stderr_updates( + client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="execution_id", devbox_id="", ) @@ -350,62 +362,6 @@ def fake_get(_path: str, *, options: Any, **_kwargs: Any): assert calls[1] is not None assert seen_offsets == ["5", "9", "10"] - with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): - client.devboxes.executions.with_raw_response.stream_stderr_updates( - execution_id="", - devbox_id="devbox_id", - ) - - @parametrize - def test_method_stream_stdout_updates(self, client: Runloop) -> None: - execution_stream = client.devboxes.executions.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - ) - execution_stream.response.close() - - @parametrize - def test_method_stream_stdout_updates_with_all_params(self, client: Runloop) -> None: - execution_stream = client.devboxes.executions.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - offset="offset", - ) - execution_stream.response.close() - - @parametrize - def test_raw_response_stream_stdout_updates(self, client: Runloop) -> None: - response = client.devboxes.executions.with_raw_response.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - ) - - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - stream = response.parse() - stream.close() - - @parametrize - def test_streaming_response_stream_stdout_updates(self, client: Runloop) -> None: - with client.devboxes.executions.with_streaming_response.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - stream = response.parse() - stream.close() - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_stream_stdout_updates(self, client: Runloop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): - client.devboxes.executions.with_raw_response.stream_stdout_updates( - execution_id="execution_id", - devbox_id="", - ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="", @@ -799,16 +755,24 @@ async def test_path_params_kill(self, async_client: AsyncRunloop) -> None: ) @parametrize - async def test_method_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: - execution_stream = await async_client.devboxes.executions.stream_stderr_updates( + async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + execution_stream = await async_client.devboxes.executions.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", ) await execution_stream.response.aclose() @parametrize - async def test_method_stream_stderr_updates_with_all_params(self, async_client: AsyncRunloop) -> None: - execution_stream = await async_client.devboxes.executions.stream_stderr_updates( + async def test_method_stream_stdout_updates_with_all_params( + self, async_client: AsyncRunloop, respx_mock: MockRouter + ) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + execution_stream = await async_client.devboxes.executions.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", offset="offset", @@ -816,8 +780,11 @@ async def test_method_stream_stderr_updates_with_all_params(self, async_client: await execution_stream.response.aclose() @parametrize - async def test_raw_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: - response = await async_client.devboxes.executions.with_raw_response.stream_stderr_updates( + async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", ) @@ -827,8 +794,13 @@ async def test_raw_response_stream_stderr_updates(self, async_client: AsyncRunlo await stream.close() @parametrize - async def test_streaming_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: - async with async_client.devboxes.executions.with_streaming_response.stream_stderr_updates( + async def test_streaming_response_stream_stdout_updates( + self, async_client: AsyncRunloop, respx_mock: MockRouter + ) -> None: + respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock( + return_value=httpx.Response(200) + ) + async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates( execution_id="execution_id", devbox_id="devbox_id", ) as response: @@ -841,9 +813,9 @@ async def test_streaming_response_stream_stderr_updates(self, async_client: Asyn assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_stream_stderr_updates(self, async_client: AsyncRunloop) -> None: + async def test_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): - await async_client.devboxes.executions.with_raw_response.stream_stderr_updates( + await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="execution_id", devbox_id="", ) @@ -911,62 +883,6 @@ async def fake_get(_path: str, *, options: Any, **_kwargs: Any): assert calls[1] is not None assert seen_offsets == ["5", "9", "10"] - with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): - await async_client.devboxes.executions.with_raw_response.stream_stderr_updates( - execution_id="", - devbox_id="devbox_id", - ) - - @parametrize - async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: - execution_stream = await async_client.devboxes.executions.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - ) - await execution_stream.response.aclose() - - @parametrize - async def test_method_stream_stdout_updates_with_all_params(self, async_client: AsyncRunloop) -> None: - execution_stream = await async_client.devboxes.executions.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - offset="offset", - ) - await execution_stream.response.aclose() - - @parametrize - async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: - response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - ) - - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - stream = await response.parse() - await stream.close() - - @parametrize - async def test_streaming_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: - async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates( - execution_id="execution_id", - devbox_id="devbox_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - stream = await response.parse() - await stream.close() - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"): - await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( - execution_id="execution_id", - devbox_id="", - ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): await async_client.devboxes.executions.with_raw_response.stream_stdout_updates( execution_id="",