Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.66.1"
".": "0.67.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 94
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f60ee2123156a4db87ddfa4e1b8dd6379e26e8f8cf533946ca25b76559b6aa4d.yml
openapi_spec_hash: d80fdfaf40c65ce8c1962c4f6d35acc6
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-91cec4aeec2421487d5eeece4804ae3b8b47e22bdbb9fc7460feb64a8c10e42f.yml
openapi_spec_hash: 3d8d782e2450d46b8ce6573bad488ea1
config_hash: 95facb8cef59b5a1b05763b871bf6a4b
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.67.0 (2025-11-14)

Full Changelog: [v0.66.1...v0.67.0](https://github.com/runloopai/api-client-python/compare/v0.66.1...v0.67.0)

### Features

* **api:** api update ([516c20b](https://github.com/runloopai/api-client-python/commit/516c20b3095d7e75b0e15647621bf92e0d79f5f4))
* **blueprint:** adds queued state ([5893559](https://github.com/runloopai/api-client-python/commit/5893559e8839260876947d31f1090dd343f1cf43))


### Bug Fixes

* **client:** close streams without requiring full consumption ([30f9ee5](https://github.com/runloopai/api-client-python/commit/30f9ee5b6cc5f42ce642b918c4e0194a4ab8bc7a))


### Chores

* **internal/tests:** avoid race condition with implicit client cleanup ([7152280](https://github.com/runloopai/api-client-python/commit/71522809d211f5bbad89be807559ca2de591729f))
* **internal:** grammar fix (it's -> its) ([fd6963f](https://github.com/runloopai/api-client-python/commit/fd6963f1777dedc2db5b86dc222a5e70521134ba))
* **package:** drop Python 3.8 support ([5026669](https://github.com/runloopai/api-client-python/commit/50266693caae9b3c6e6506cb58f096f1d439dcd0))

## 0.66.1 (2025-10-23)

Full Changelog: [v0.66.0...v0.66.1](https://github.com/runloopai/api-client-python/compare/v0.66.0...v0.66.1)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- prettier-ignore -->
[![PyPI version](https://img.shields.io/pypi/v/runloop_api_client.svg?label=pypi%20(stable))](https://pypi.org/project/runloop_api_client/)

The Runloop Python library provides convenient access to the Runloop REST API from any Python 3.8+
The Runloop Python library provides convenient access to the Runloop REST API from any Python 3.9+
application. The library includes type definitions for all request params and response fields,
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).

Expand Down Expand Up @@ -456,7 +456,7 @@ print(runloop_api_client.__version__)

## Requirements

Python 3.8 or higher.
Python 3.9 or higher.

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "runloop_api_client"
version = "0.66.1"
version = "0.67.0"
description = "The official Python library for the runloop API"
dynamic = ["readme"]
license = "MIT"
Expand Down
51 changes: 11 additions & 40 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
-e .
annotated-types==0.7.0
# via pydantic
anyio==4.5.2 ; python_full_version < '3.9'
# via
# httpx
# runloop-api-client
anyio==4.8.0 ; python_full_version >= '3.9'
anyio==4.8.0
# via
# httpx
# runloop-api-client
Expand Down Expand Up @@ -38,8 +34,7 @@ idna==3.10
# via
# anyio
# httpx
importlib-metadata==8.5.0 ; python_full_version < '3.9'
importlib-metadata==8.6.1 ; python_full_version >= '3.9'
importlib-metadata==8.6.1
iniconfig==2.0.0
# via pytest
markdown-it-py==3.0.0
Expand All @@ -55,30 +50,23 @@ packaging==24.2
# via pytest
pluggy==1.5.0
# via pytest
pydantic==2.10.6 ; python_full_version < '3.9'
pydantic==2.10.3
# via runloop-api-client
pydantic==2.11.9 ; python_full_version >= '3.9'
# via runloop-api-client
pydantic-core==2.27.2 ; python_full_version < '3.9'
# via pydantic
pydantic-core==2.33.2 ; python_full_version >= '3.9'
pydantic-core==2.27.1
# via pydantic
pygments==2.19.1
# via
# pytest
# rich
pyright==1.1.399
pytest==8.3.3
# via pytest-asyncio
# via pytest-timeout
# via pytest-xdist
pytest==8.4.1
# via
# pytest-asyncio
# pytest-xdist
pytest-asyncio==0.24.0
pytest-timeout==2.3.1
pytest-xdist==3.7.0
python-dateutil==2.8.2
python-dateutil==2.9.0.post0
# via time-machine
pytz==2024.2 ; python_full_version < '3.9'
# via dirty-equals
respx==0.22.0
rich==13.9.4
ruff==0.9.4
Expand All @@ -88,36 +76,19 @@ sniffio==1.3.1
# via
# anyio
# runloop-api-client
time-machine==2.15.0 ; python_full_version < '3.9'
time-machine==2.16.0 ; python_full_version >= '3.9'
time-machine==2.16.0
tomli==2.2.1 ; python_full_version < '3.11'
# via
# mypy
# pytest
typing-extensions==4.12.2
# via
# annotated-types
# anyio
# mypy
# pydantic
# pydantic-core
# pyright
# rich
# runloop-api-client
# typing-inspection
typing-inspection==0.4.1 ; python_full_version >= '3.9'
# via pydantic
# via pydantic-core
# via pyright
# via runloop-api-client
# via typing-inspection
typing-inspection==0.4.1
# via pydantic
uuid-utils==0.11.0
# via runloop-api-client
virtualenv==20.24.5
# via nox
yarl==1.20.0
# via aiohttp
zipp>=3.20
zipp==3.21.0
# via importlib-metadata
10 changes: 4 additions & 6 deletions src/runloop_api_client/_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,8 @@ def __stream__(self) -> Iterator[_T]:

yield process_data(data=sse.json(), cast_to=cast_to, response=response)

# Ensure the entire stream is consumed
for _sse in iterator:
...
# As we might not fully consume the response stream, we need to close it explicitly
response.close()

def __enter__(self) -> Self:
return self
Expand Down Expand Up @@ -162,9 +161,8 @@ async def __stream__(self) -> AsyncIterator[_T]:

yield process_data(data=sse.json(), cast_to=cast_to, response=response)

# Ensure the entire stream is consumed
async for _sse in iterator:
...
# As we might not fully consume the response stream, we need to close it explicitly
await response.aclose()

async def __aenter__(self) -> Self:
return self
Expand Down
34 changes: 3 additions & 31 deletions src/runloop_api_client/_utils/_sync.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from __future__ import annotations

import sys
import asyncio
import functools
import contextvars
from typing import Any, TypeVar, Callable, Awaitable
from typing import TypeVar, Callable, Awaitable
from typing_extensions import ParamSpec

import anyio
Expand All @@ -15,34 +13,11 @@
T_ParamSpec = ParamSpec("T_ParamSpec")


if sys.version_info >= (3, 9):
_asyncio_to_thread = asyncio.to_thread
else:
# backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
# for Python 3.8 support
async def _asyncio_to_thread(
func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> Any:
"""Asynchronously run function *func* in a separate thread.

Any *args and **kwargs supplied for this function are directly passed
to *func*. Also, the current :class:`contextvars.Context` is propagated,
allowing context variables from the main thread to be accessed in the
separate thread.

Returns a coroutine that can be awaited to get the eventual result of *func*.
"""
loop = asyncio.events.get_running_loop()
ctx = contextvars.copy_context()
func_call = functools.partial(ctx.run, func, *args, **kwargs)
return await loop.run_in_executor(None, func_call)


async def to_thread(
func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> T_Retval:
if sniffio.current_async_library() == "asyncio":
return await _asyncio_to_thread(func, *args, **kwargs)
return await asyncio.to_thread(func, *args, **kwargs)

return await anyio.to_thread.run_sync(
functools.partial(func, *args, **kwargs),
Expand All @@ -53,10 +28,7 @@ async def to_thread(
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
"""
Take a blocking function and create an async one that receives the same
positional and keyword arguments. For python version 3.9 and above, it uses
asyncio.to_thread to run the function in a separate thread. For python version
3.8, it uses locally defined copy of the asyncio.to_thread function which was
introduced in python 3.9.
positional and keyword arguments.

Usage:

Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/_utils/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]:
# Type safe methods for narrowing types with TypeVars.
# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
# however this cause Pyright to rightfully report errors. As we know we don't
# care about the contained types we can safely use `object` in it's place.
# care about the contained types we can safely use `object` in its place.
#
# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
# `is_*` is for when you're dealing with an unknown input
Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "runloop_api_client"
__version__ = "0.66.1" # x-release-please-version
__version__ = "0.67.0" # x-release-please-version
12 changes: 8 additions & 4 deletions src/runloop_api_client/resources/devboxes/executions.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@ def stream_stderr_updates(
Tails the stderr logs for the given execution with SSE streaming

Args:
offset: The byte offset to start the stream from
offset: The byte offset to start the stream from (if unspecified, starts from the
beginning of the stream)

extra_headers: Send extra headers

Expand Down Expand Up @@ -503,7 +504,8 @@ def stream_stdout_updates(
Tails the stdout logs for the given execution with SSE streaming

Args:
offset: The byte offset to start the stream from
offset: The byte offset to start the stream from (if unspecified, starts from the
beginning of the stream)

extra_headers: Send extra headers

Expand Down Expand Up @@ -939,7 +941,8 @@ async def stream_stderr_updates(
Tails the stderr logs for the given execution with SSE streaming

Args:
offset: The byte offset to start the stream from
offset: The byte offset to start the stream from (if unspecified, starts from the
beginning of the stream)

extra_headers: Send extra headers

Expand Down Expand Up @@ -1023,7 +1026,8 @@ async def stream_stdout_updates(
Tails the stdout logs for the given execution with SSE streaming

Args:
offset: The byte offset to start the stream from
offset: The byte offset to start the stream from (if unspecified, starts from the
beginning of the stream)

extra_headers: Send extra headers

Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/types/blueprint_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class BlueprintView(BaseModel):
state: Literal["created", "deleted"]
"""The state of the Blueprint."""

status: Literal["provisioning", "building", "failed", "build_complete"]
status: Literal["queued", "provisioning", "building", "failed", "build_complete"]
"""The status of the Blueprint build."""

base_blueprint_id: Optional[str] = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ class DevboxAsyncExecutionDetailView(BaseModel):
This field will remain unset until the execution has completed.
"""

stderr_truncated: Optional[bool] = None
"""Indicates whether the stderr was truncated due to size limits."""

stdout: Optional[str] = None
"""Standard out generated by command.

This field will remain unset until the execution has completed.
"""

stdout_truncated: Optional[bool] = None
"""Indicates whether the stdout was truncated due to size limits."""
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ class ExecutionStreamStderrUpdatesParams(TypedDict, total=False):
devbox_id: Required[str]

offset: str
"""The byte offset to start the stream from"""
"""
The byte offset to start the stream from (if unspecified, starts from the
beginning of the stream)
"""
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ class ExecutionStreamStdoutUpdatesParams(TypedDict, total=False):
devbox_id: Required[str]

offset: str
"""The byte offset to start the stream from"""
"""
The byte offset to start the stream from (if unspecified, starts from the
beginning of the stream)
"""
2 changes: 1 addition & 1 deletion src/runloop_api_client/types/shared/launch_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class LaunchParameters(BaseModel):
keep_alive_time_seconds: Optional[int] = None
"""Time in seconds after which Devbox will automatically shutdown.

Default is 1 hour.
Default is 1 hour. Maximum is 48 hours (172800 seconds).
"""

launch_commands: Optional[List[str]] = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class LaunchParameters(TypedDict, total=False):
keep_alive_time_seconds: Optional[int]
"""Time in seconds after which Devbox will automatically shutdown.

Default is 1 hour.
Default is 1 hour. Maximum is 48 hours (172800 seconds).
"""

launch_commands: Optional[SequenceNotStr[str]]
Expand Down
Loading