diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6a197bef5..aa06f8634 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.17.0" + ".": "1.18.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 448e19886..7c298b7e4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 109 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-a1c7e69cbbf7a7cf63893358470cee52714633e6d31ce6dff2e7255c7445a1aa.yml -openapi_spec_hash: a0e88c05a9b74c2bc9192bd7d94de3c0 -config_hash: ecb1ff09d29b565ed1452b5e0362e64d +configured_endpoints: 110 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f0eb12cf4df4fa3046bd88aae4966062eb6e9703768a07a0136da2f26a2ebd56.yml +openapi_spec_hash: cdbd63a8162f1e987e937042cbd60eea +config_hash: 526cf0707adc54c690fc687a1c6db728 diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b189891..e01266433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.18.1 (2026-04-10) + +Full Changelog: [v1.17.0...v1.18.1](https://github.com/runloopai/api-client-python/compare/v1.17.0...v1.18.1) + +### Bug Fixes + +* add list events to axon ([#8648](https://github.com/runloopai/api-client-python/issues/8648)) ([1203b57](https://github.com/runloopai/api-client-python/commit/1203b577b105dc0c7cd94eae644a609a525b7c32)) +* TTL shutdown showed non-standard reason in devbox logs ([#8636](https://github.com/runloopai/api-client-python/issues/8636)) ([8ac5cc5](https://github.com/runloopai/api-client-python/commit/8ac5cc5116b771316d28b426bfa13fd6af5cedd2)) + + +### Documentation + +* clarify that we don't support git commit SHA in git agents/blueprint builds ([#8638](https://github.com/runloopai/api-client-python/issues/8638)) ([66b0895](https://github.com/runloopai/api-client-python/commit/66b0895865737ac3dd7f84be74785504fc7bede5)) + ## 1.17.0 (2026-04-09) Full Changelog: [v1.16.0...v1.17.0](https://github.com/runloopai/api-client-python/compare/v1.16.0...v1.17.0) diff --git a/api.md b/api.md index 24f4c849e..fd95076a1 100644 --- a/api.md +++ b/api.md @@ -111,6 +111,18 @@ Methods: - client.axons.publish(id, \*\*params) -> PublishResultView - client.axons.subscribe_sse(id, \*\*params) -> AxonEventView +## Events + +Types: + +```python +from runloop_api_client.types.axons import AxonEventListView +``` + +Methods: + +- client.axons.events.list(id, \*\*params) -> AxonEventListView + ## Sql Types: diff --git a/pyproject.toml b/pyproject.toml index 79180039f..52d3431d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.17.0" +version = "1.18.1" 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 e93a9c96c..4ca4fa13d 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__ = "1.17.0" # x-release-please-version +__version__ = "1.18.1" # x-release-please-version diff --git a/src/runloop_api_client/resources/axons/__init__.py b/src/runloop_api_client/resources/axons/__init__.py index 81c6cf070..dbeec351e 100644 --- a/src/runloop_api_client/resources/axons/__init__.py +++ b/src/runloop_api_client/resources/axons/__init__.py @@ -16,8 +16,22 @@ AxonsResourceWithStreamingResponse, AsyncAxonsResourceWithStreamingResponse, ) +from .events import ( + EventsResource, + AsyncEventsResource, + EventsResourceWithRawResponse, + AsyncEventsResourceWithRawResponse, + EventsResourceWithStreamingResponse, + AsyncEventsResourceWithStreamingResponse, +) __all__ = [ + "EventsResource", + "AsyncEventsResource", + "EventsResourceWithRawResponse", + "AsyncEventsResourceWithRawResponse", + "EventsResourceWithStreamingResponse", + "AsyncEventsResourceWithStreamingResponse", "SqlResource", "AsyncSqlResource", "SqlResourceWithRawResponse", diff --git a/src/runloop_api_client/resources/axons/axons.py b/src/runloop_api_client/resources/axons/axons.py index 977d2dd93..9387bc0c9 100644 --- a/src/runloop_api_client/resources/axons/axons.py +++ b/src/runloop_api_client/resources/axons/axons.py @@ -15,6 +15,14 @@ SqlResourceWithStreamingResponse, AsyncSqlResourceWithStreamingResponse, ) +from .events import ( + EventsResource, + AsyncEventsResource, + EventsResourceWithRawResponse, + AsyncEventsResourceWithRawResponse, + EventsResourceWithStreamingResponse, + AsyncEventsResourceWithStreamingResponse, +) from ...types import axon_list_params, axon_create_params, axon_publish_params, axon_subscribe_sse_params from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import path_template, maybe_transform, async_maybe_transform @@ -37,6 +45,10 @@ class AxonsResource(SyncAPIResource): + @cached_property + def events(self) -> EventsResource: + return EventsResource(self._client) + @cached_property def sql(self) -> SqlResource: return SqlResource(self._client) @@ -304,6 +316,10 @@ def subscribe_sse( class AsyncAxonsResource(AsyncAPIResource): + @cached_property + def events(self) -> AsyncEventsResource: + return AsyncEventsResource(self._client) + @cached_property def sql(self) -> AsyncSqlResource: return AsyncSqlResource(self._client) @@ -590,6 +606,10 @@ def __init__(self, axons: AxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def events(self) -> EventsResourceWithRawResponse: + return EventsResourceWithRawResponse(self._axons.events) + @cached_property def sql(self) -> SqlResourceWithRawResponse: return SqlResourceWithRawResponse(self._axons.sql) @@ -615,6 +635,10 @@ def __init__(self, axons: AsyncAxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def events(self) -> AsyncEventsResourceWithRawResponse: + return AsyncEventsResourceWithRawResponse(self._axons.events) + @cached_property def sql(self) -> AsyncSqlResourceWithRawResponse: return AsyncSqlResourceWithRawResponse(self._axons.sql) @@ -640,6 +664,10 @@ def __init__(self, axons: AxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def events(self) -> EventsResourceWithStreamingResponse: + return EventsResourceWithStreamingResponse(self._axons.events) + @cached_property def sql(self) -> SqlResourceWithStreamingResponse: return SqlResourceWithStreamingResponse(self._axons.sql) @@ -665,6 +693,10 @@ def __init__(self, axons: AsyncAxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def events(self) -> AsyncEventsResourceWithStreamingResponse: + return AsyncEventsResourceWithStreamingResponse(self._axons.events) + @cached_property def sql(self) -> AsyncSqlResourceWithStreamingResponse: return AsyncSqlResourceWithStreamingResponse(self._axons.sql) diff --git a/src/runloop_api_client/resources/axons/events.py b/src/runloop_api_client/resources/axons/events.py new file mode 100644 index 000000000..8933cfa6e --- /dev/null +++ b/src/runloop_api_client/resources/axons/events.py @@ -0,0 +1,207 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...types.axons import event_list_params +from ..._base_client import make_request_options +from ...types.axons.axon_event_list_view import AxonEventListView + +__all__ = ["EventsResource", "AsyncEventsResource"] + + +class EventsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EventsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return EventsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EventsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return EventsResourceWithStreamingResponse(self) + + def list( + self, + id: str, + *, + include_total_count: bool | Omit = omit, + limit: int | Omit = omit, + starting_after: str | Omit = omit, + # 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, + ) -> AxonEventListView: + """ + [Beta] List events from an axon's event stream, ordered by sequence descending. + + Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + + limit: The limit of items to return. Default is 20. Max is 5000. + + starting_after: Load the next page of data starting after the item with the given ID. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/v1/axons/{id}/events", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_total_count": include_total_count, + "limit": limit, + "starting_after": starting_after, + }, + event_list_params.EventListParams, + ), + ), + cast_to=AxonEventListView, + ) + + +class AsyncEventsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEventsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return AsyncEventsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEventsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return AsyncEventsResourceWithStreamingResponse(self) + + async def list( + self, + id: str, + *, + include_total_count: bool | Omit = omit, + limit: int | Omit = omit, + starting_after: str | Omit = omit, + # 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, + ) -> AxonEventListView: + """ + [Beta] List events from an axon's event stream, ordered by sequence descending. + + Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + + limit: The limit of items to return. Default is 20. Max is 5000. + + starting_after: Load the next page of data starting after the item with the given ID. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/v1/axons/{id}/events", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "include_total_count": include_total_count, + "limit": limit, + "starting_after": starting_after, + }, + event_list_params.EventListParams, + ), + ), + cast_to=AxonEventListView, + ) + + +class EventsResourceWithRawResponse: + def __init__(self, events: EventsResource) -> None: + self._events = events + + self.list = to_raw_response_wrapper( + events.list, + ) + + +class AsyncEventsResourceWithRawResponse: + def __init__(self, events: AsyncEventsResource) -> None: + self._events = events + + self.list = async_to_raw_response_wrapper( + events.list, + ) + + +class EventsResourceWithStreamingResponse: + def __init__(self, events: EventsResource) -> None: + self._events = events + + self.list = to_streamed_response_wrapper( + events.list, + ) + + +class AsyncEventsResourceWithStreamingResponse: + def __init__(self, events: AsyncEventsResource) -> None: + self._events = events + + self.list = async_to_streamed_response_wrapper( + events.list, + ) diff --git a/src/runloop_api_client/types/axons/__init__.py b/src/runloop_api_client/types/axons/__init__.py index 8ab8cf9b1..975672031 100644 --- a/src/runloop_api_client/types/axons/__init__.py +++ b/src/runloop_api_client/types/axons/__init__.py @@ -4,7 +4,9 @@ from .sql_batch_params import SqlBatchParams as SqlBatchParams from .sql_query_params import SqlQueryParams as SqlQueryParams +from .event_list_params import EventListParams as EventListParams from .sql_step_error_view import SqlStepErrorView as SqlStepErrorView +from .axon_event_list_view import AxonEventListView as AxonEventListView from .sql_column_meta_view import SqlColumnMetaView as SqlColumnMetaView from .sql_result_meta_view import SqlResultMetaView as SqlResultMetaView from .sql_statement_params import SqlStatementParams as SqlStatementParams diff --git a/src/runloop_api_client/types/axons/axon_event_list_view.py b/src/runloop_api_client/types/axons/axon_event_list_view.py new file mode 100644 index 000000000..9028af1f9 --- /dev/null +++ b/src/runloop_api_client/types/axons/axon_event_list_view.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from ..axon_event_view import AxonEventView + +__all__ = ["AxonEventListView"] + + +class AxonEventListView(BaseModel): + events: List[AxonEventView] + """List of axon events.""" + + has_more: bool + + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/axons/event_list_params.py b/src/runloop_api_client/types/axons/event_list_params.py new file mode 100644 index 000000000..6762cba5b --- /dev/null +++ b/src/runloop_api_client/types/axons/event_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["EventListParams"] + + +class EventListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + + limit: int + """The limit of items to return. Default is 20. Max is 5000.""" + + starting_after: str + """Load the next page of data starting after the item with the given ID.""" diff --git a/src/runloop_api_client/types/devbox_view.py b/src/runloop_api_client/types/devbox_view.py index 82a8c2595..068f180ad 100644 --- a/src/runloop_api_client/types/devbox_view.py +++ b/src/runloop_api_client/types/devbox_view.py @@ -113,7 +113,9 @@ class DevboxView(BaseModel): name: Optional[str] = None """The name of the Devbox.""" - shutdown_reason: Optional[Literal["api_shutdown", "keep_alive_timeout", "entrypoint_exit", "idle"]] = None + shutdown_reason: Optional[ + Literal["api_shutdown", "keep_alive_timeout", "entrypoint_exit", "idle", "ttl_expired"] + ] = None """ The shutdown reason if the Devbox shutdown, if the Devbox has a 'shutdown' status. diff --git a/src/runloop_api_client/types/shared/code_mount_parameters.py b/src/runloop_api_client/types/shared/code_mount_parameters.py index 32099b9e4..f0b395279 100644 --- a/src/runloop_api_client/types/shared/code_mount_parameters.py +++ b/src/runloop_api_client/types/shared/code_mount_parameters.py @@ -21,7 +21,7 @@ class CodeMountParameters(BaseModel): """The authentication token necessary to pull repo.""" git_ref: Optional[str] = None - """Optional git ref (branch, tag, or commit SHA) to checkout. + """Optional git ref (branch or tag) to checkout. Defaults to the repository default branch. """ diff --git a/src/runloop_api_client/types/shared/mount.py b/src/runloop_api_client/types/shared/mount.py index 8c29dbbdb..80b57f8c0 100644 --- a/src/runloop_api_client/types/shared/mount.py +++ b/src/runloop_api_client/types/shared/mount.py @@ -28,7 +28,7 @@ class CodeMount(BaseModel): """The authentication token necessary to pull repo.""" git_ref: Optional[str] = None - """Optional git ref (branch, tag, or commit SHA) to checkout. + """Optional git ref (branch or tag) to checkout. Defaults to the repository default branch. """ diff --git a/src/runloop_api_client/types/shared_params/code_mount_parameters.py b/src/runloop_api_client/types/shared_params/code_mount_parameters.py index b7d1468ad..13cb122f8 100644 --- a/src/runloop_api_client/types/shared_params/code_mount_parameters.py +++ b/src/runloop_api_client/types/shared_params/code_mount_parameters.py @@ -22,7 +22,7 @@ class CodeMountParameters(TypedDict, total=False): """The authentication token necessary to pull repo.""" git_ref: Optional[str] - """Optional git ref (branch, tag, or commit SHA) to checkout. + """Optional git ref (branch or tag) to checkout. Defaults to the repository default branch. """ diff --git a/src/runloop_api_client/types/shared_params/mount.py b/src/runloop_api_client/types/shared_params/mount.py index 1badcd396..cafbeedbd 100644 --- a/src/runloop_api_client/types/shared_params/mount.py +++ b/src/runloop_api_client/types/shared_params/mount.py @@ -28,7 +28,7 @@ class CodeMount(TypedDict, total=False): """The authentication token necessary to pull repo.""" git_ref: Optional[str] - """Optional git ref (branch, tag, or commit SHA) to checkout. + """Optional git ref (branch or tag) to checkout. Defaults to the repository default branch. """ diff --git a/tests/api_resources/axons/test_events.py b/tests/api_resources/axons/test_events.py new file mode 100644 index 000000000..0944026f0 --- /dev/null +++ b/tests/api_resources/axons/test_events.py @@ -0,0 +1,120 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from runloop_api_client import Runloop, AsyncRunloop +from runloop_api_client.types.axons import AxonEventListView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEvents: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Runloop) -> None: + event = client.axons.events.list( + id="id", + ) + assert_matches_type(AxonEventListView, event, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Runloop) -> None: + event = client.axons.events.list( + id="id", + include_total_count=True, + limit=0, + starting_after="starting_after", + ) + assert_matches_type(AxonEventListView, event, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Runloop) -> None: + response = client.axons.events.with_raw_response.list( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + event = response.parse() + assert_matches_type(AxonEventListView, event, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Runloop) -> None: + with client.axons.events.with_streaming_response.list( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + event = response.parse() + assert_matches_type(AxonEventListView, event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.axons.events.with_raw_response.list( + id="", + ) + + +class TestAsyncEvents: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncRunloop) -> None: + event = await async_client.axons.events.list( + id="id", + ) + assert_matches_type(AxonEventListView, event, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: + event = await async_client.axons.events.list( + id="id", + include_total_count=True, + limit=0, + starting_after="starting_after", + ) + assert_matches_type(AxonEventListView, event, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.events.with_raw_response.list( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + event = await response.parse() + assert_matches_type(AxonEventListView, event, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.events.with_streaming_response.list( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + event = await response.parse() + assert_matches_type(AxonEventListView, event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.axons.events.with_raw_response.list( + id="", + )