diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81f6dc20f..87c2de57f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,17 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 @@ -30,8 +31,9 @@ jobs: run: ./scripts/lint test: + timeout-minutes: 10 name: test - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f81bf9923..f04d08965 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.31.0" + ".": "0.32.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index a9ee988af..bae579201 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 79 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-29770148092f0f5030e7199308862afc7e46cb12da6081cd045e9db6734d3ac9.yml -openapi_spec_hash: b100a6769bf25a0f873ef32201eba45e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-ba1b6db5490b976ad56ecea10d380f462ec5b9f8d47d139f87d7fff3e7df330a.yml +openapi_spec_hash: bf76fd15629ea1a4c134c4af5436c78f config_hash: 4cb90c87fb61338e46c50cea9c42abd7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb0ff073..0a7838408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.32.0 (2025-04-25) + +Full Changelog: [v0.31.0...v0.32.0](https://github.com/runloopai/api-client-python/compare/v0.31.0...v0.32.0) + +### Features + +* **api:** api update ([cf2ad50](https://github.com/runloopai/api-client-python/commit/cf2ad505284d70de40503c417b3a1183294b7174)) + + +### Bug Fixes + +* **pydantic v1:** more robust ModelField.annotation check ([6ac21f3](https://github.com/runloopai/api-client-python/commit/6ac21f3a0ceb614011e1359b7c8d68c85a47b840)) + + +### Chores + +* broadly detect json family of content-type headers ([0a09a72](https://github.com/runloopai/api-client-python/commit/0a09a72c20fdf74d044bf074a68629da159d16b7)) +* **ci:** add timeout thresholds for CI jobs ([a10bca8](https://github.com/runloopai/api-client-python/commit/a10bca8067d5fdbd5095a7bdc81ff744e5bc1314)) +* **ci:** only use depot for staging repos ([84dd6ba](https://github.com/runloopai/api-client-python/commit/84dd6ba5bd4279e8dacad56c04b4aebb20023463)) +* **internal:** codegen related update ([fdaff81](https://github.com/runloopai/api-client-python/commit/fdaff81cc4ef7a4ee0455c07a7655868705599c0)) +* **internal:** fix list file params ([d74de2f](https://github.com/runloopai/api-client-python/commit/d74de2fab676075f4e46972dbba8f413b1e1160e)) +* **internal:** import reformatting ([97a122c](https://github.com/runloopai/api-client-python/commit/97a122c50265888b92b41d6f078209c054a1fb79)) +* **internal:** minor formatting changes ([6557c6c](https://github.com/runloopai/api-client-python/commit/6557c6ccf7bb6f6e93b6e29c3c8bbe49ef57d44d)) +* **internal:** refactor retries to not use recursion ([88cd023](https://github.com/runloopai/api-client-python/commit/88cd02300c4e5eb6bb763341a71aca3eccbeebae)) + ## 0.31.0 (2025-04-21) Full Changelog: [v0.30.0...v0.31.0](https://github.com/runloopai/api-client-python/compare/v0.30.0...v0.31.0) diff --git a/README.md b/README.md index 00c2c41bf..05e74c625 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,10 @@ devbox_view = client.devboxes.create( "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, ) print(devbox_view.launch_parameters) diff --git a/pyproject.toml b/pyproject.toml index 6a30dc50f..1ace85349 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "0.31.0" +version = "0.32.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_base_client.py b/src/runloop_api_client/_base_client.py index 32f743a59..1b3041060 100644 --- a/src/runloop_api_client/_base_client.py +++ b/src/runloop_api_client/_base_client.py @@ -437,8 +437,7 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - options.idempotency_key = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: headers[idempotency_header] = options.idempotency_key # Don't set these headers if they were already set or removed by the caller. We check @@ -903,7 +902,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -914,7 +912,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -924,7 +921,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -934,125 +930,109 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - retries_taken: int, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) - - if options.idempotency_key: + if input_options.idempotency_key is None and input_options.method.lower() != "get": # ensure the idempotency key is reused between requests - input_options.idempotency_key = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, @@ -1062,37 +1042,20 @@ def _request( retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1436,7 +1399,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1447,7 +1409,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1458,7 +1419,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1468,120 +1428,111 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - retries_taken: int, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) - - if options.idempotency_key: + if input_options.idempotency_key is None and input_options.method.lower() != "get": # ensure the idempotency key is reused between requests - input_options.idempotency_key = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return await self._process_response( cast_to=cast_to, options=options, @@ -1591,35 +1542,20 @@ async def _request( retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py index 2c6fa90fa..babaea83b 100644 --- a/src/runloop_api_client/_client.py +++ b/src/runloop_api_client/_client.py @@ -19,10 +19,7 @@ ProxiesTypes, RequestOptions, ) -from ._utils import ( - is_given, - get_async_library, -) +from ._utils import is_given, get_async_library from ._version import __version__ from .resources import blueprints, repositories from ._streaming import Stream as Stream, AsyncStream as AsyncStream diff --git a/src/runloop_api_client/_models.py b/src/runloop_api_client/_models.py index 58b9263e8..798956f17 100644 --- a/src/runloop_api_client/_models.py +++ b/src/runloop_api_client/_models.py @@ -626,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, # Note: if one variant defines an alias then they all should discriminator_alias = field_info.alias - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant diff --git a/src/runloop_api_client/_response.py b/src/runloop_api_client/_response.py index 1e0ab213e..31591fad8 100644 --- a/src/runloop_api_client/_response.py +++ b/src/runloop_api_client/_response.py @@ -235,7 +235,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() diff --git a/src/runloop_api_client/_utils/_utils.py b/src/runloop_api_client/_utils/_utils.py index e5811bba4..ea3cf3f2c 100644 --- a/src/runloop_api_client/_utils/_utils.py +++ b/src/runloop_api_client/_utils/_utils.py @@ -72,8 +72,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index 8d021c0e2..6db6388a9 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.31.0" # x-release-please-version +__version__ = "0.32.0" # x-release-please-version diff --git a/src/runloop_api_client/resources/benchmarks/benchmarks.py b/src/runloop_api_client/resources/benchmarks/benchmarks.py index 550ef0867..beb5f42c3 100644 --- a/src/runloop_api_client/resources/benchmarks/benchmarks.py +++ b/src/runloop_api_client/resources/benchmarks/benchmarks.py @@ -21,10 +21,7 @@ benchmark_list_public_params, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/blueprints.py b/src/runloop_api_client/resources/blueprints.py index 67c540dc0..85ce6c2b1 100644 --- a/src/runloop_api_client/resources/blueprints.py +++ b/src/runloop_api_client/resources/blueprints.py @@ -8,10 +8,7 @@ from ..types import blueprint_list_params, blueprint_create_params, blueprint_preview_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/runloop_api_client/resources/devboxes/browsers.py b/src/runloop_api_client/resources/devboxes/browsers.py index 799b58aef..9a2c1c9f5 100644 --- a/src/runloop_api_client/resources/devboxes/browsers.py +++ b/src/runloop_api_client/resources/devboxes/browsers.py @@ -7,10 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/devboxes/computers.py b/src/runloop_api_client/resources/devboxes/computers.py index fea63c395..b08c90c02 100644 --- a/src/runloop_api_client/resources/devboxes/computers.py +++ b/src/runloop_api_client/resources/devboxes/computers.py @@ -8,10 +8,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 7f20d6005..a51255713 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -39,12 +39,7 @@ devbox_write_file_contents_params, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ..._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, -) +from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from .browsers import ( BrowsersResource, AsyncBrowsersResource, diff --git a/src/runloop_api_client/resources/devboxes/disk_snapshots.py b/src/runloop_api_client/resources/devboxes/disk_snapshots.py index 9d23416f1..72439a4ec 100644 --- a/src/runloop_api_client/resources/devboxes/disk_snapshots.py +++ b/src/runloop_api_client/resources/devboxes/disk_snapshots.py @@ -7,10 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index 9cbe048de..1515be695 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -7,10 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/devboxes/logs.py b/src/runloop_api_client/resources/devboxes/logs.py index 05b80f362..c3302e2fb 100644 --- a/src/runloop_api_client/resources/devboxes/logs.py +++ b/src/runloop_api_client/resources/devboxes/logs.py @@ -5,10 +5,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/devboxes/lsp.py b/src/runloop_api_client/resources/devboxes/lsp.py index d1601f207..9de56eda2 100644 --- a/src/runloop_api_client/resources/devboxes/lsp.py +++ b/src/runloop_api_client/resources/devboxes/lsp.py @@ -5,10 +5,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/repositories.py b/src/runloop_api_client/resources/repositories.py index 5ebca0116..5bde8f618 100644 --- a/src/runloop_api_client/resources/repositories.py +++ b/src/runloop_api_client/resources/repositories.py @@ -8,10 +8,7 @@ from ..types import repository_list_params, repository_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/runloop_api_client/resources/scenarios/scenarios.py b/src/runloop_api_client/resources/scenarios/scenarios.py index a13b714cb..31aa4274b 100644 --- a/src/runloop_api_client/resources/scenarios/scenarios.py +++ b/src/runloop_api_client/resources/scenarios/scenarios.py @@ -30,10 +30,7 @@ AsyncScorersResourceWithStreamingResponse, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/resources/scenarios/scorers.py b/src/runloop_api_client/resources/scenarios/scorers.py index c71bb028b..56236b184 100644 --- a/src/runloop_api_client/resources/scenarios/scorers.py +++ b/src/runloop_api_client/resources/scenarios/scorers.py @@ -5,10 +5,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/runloop_api_client/types/blueprint_build_log.py b/src/runloop_api_client/types/blueprint_build_log.py index 3343bb16f..7666ac65c 100644 --- a/src/runloop_api_client/types/blueprint_build_log.py +++ b/src/runloop_api_client/types/blueprint_build_log.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["BlueprintBuildLog"] diff --git a/src/runloop_api_client/types/blueprint_preview_view.py b/src/runloop_api_client/types/blueprint_preview_view.py index 5703115a4..7e4b0c53c 100644 --- a/src/runloop_api_client/types/blueprint_preview_view.py +++ b/src/runloop_api_client/types/blueprint_preview_view.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["BlueprintPreviewView"] diff --git a/src/runloop_api_client/types/devbox_create_ssh_key_response.py b/src/runloop_api_client/types/devbox_create_ssh_key_response.py index 90746ab31..1b69373a3 100755 --- a/src/runloop_api_client/types/devbox_create_ssh_key_response.py +++ b/src/runloop_api_client/types/devbox_create_ssh_key_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["DevboxCreateSSHKeyResponse"] diff --git a/src/runloop_api_client/types/devbox_tunnel_view.py b/src/runloop_api_client/types/devbox_tunnel_view.py index ac47c82a0..48e3b3ac8 100644 --- a/src/runloop_api_client/types/devbox_tunnel_view.py +++ b/src/runloop_api_client/types/devbox_tunnel_view.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["DevboxTunnelView"] diff --git a/src/runloop_api_client/types/devboxes/base_location.py b/src/runloop_api_client/types/devboxes/base_location.py index 3d83663f0..716a7cc0a 100644 --- a/src/runloop_api_client/types/devboxes/base_location.py +++ b/src/runloop_api_client/types/devboxes/base_location.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel from .base_range import BaseRange diff --git a/src/runloop_api_client/types/devboxes/base_markup_content.py b/src/runloop_api_client/types/devboxes/base_markup_content.py index bd874488d..8a00ea75d 100644 --- a/src/runloop_api_client/types/devboxes/base_markup_content.py +++ b/src/runloop_api_client/types/devboxes/base_markup_content.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["BaseMarkupContent"] diff --git a/src/runloop_api_client/types/devboxes/base_range.py b/src/runloop_api_client/types/devboxes/base_range.py index 52e22338d..a15dd11d4 100644 --- a/src/runloop_api_client/types/devboxes/base_range.py +++ b/src/runloop_api_client/types/devboxes/base_range.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["BaseRange", "End", "Start"] diff --git a/src/runloop_api_client/types/devboxes/browser_view.py b/src/runloop_api_client/types/devboxes/browser_view.py index 84341e301..d6d377a28 100644 --- a/src/runloop_api_client/types/devboxes/browser_view.py +++ b/src/runloop_api_client/types/devboxes/browser_view.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel from ..devbox_view import DevboxView diff --git a/src/runloop_api_client/types/devboxes/computer_view.py b/src/runloop_api_client/types/devboxes/computer_view.py index f401845cc..907629d54 100644 --- a/src/runloop_api_client/types/devboxes/computer_view.py +++ b/src/runloop_api_client/types/devboxes/computer_view.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel from ..devbox_view import DevboxView diff --git a/src/runloop_api_client/types/devboxes/file_contents_response.py b/src/runloop_api_client/types/devboxes/file_contents_response.py index e5aa38d11..b862022e5 100644 --- a/src/runloop_api_client/types/devboxes/file_contents_response.py +++ b/src/runloop_api_client/types/devboxes/file_contents_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from pydantic import Field as FieldInfo from ..._models import BaseModel diff --git a/src/runloop_api_client/types/devboxes/watched_file_response.py b/src/runloop_api_client/types/devboxes/watched_file_response.py index 01884f8e8..9ef69585a 100644 --- a/src/runloop_api_client/types/devboxes/watched_file_response.py +++ b/src/runloop_api_client/types/devboxes/watched_file_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from pydantic import Field as FieldInfo from ..._models import BaseModel diff --git a/src/runloop_api_client/types/scenarios/scorer_create_response.py b/src/runloop_api_client/types/scenarios/scorer_create_response.py index 49b614e0d..345779a1d 100644 --- a/src/runloop_api_client/types/scenarios/scorer_create_response.py +++ b/src/runloop_api_client/types/scenarios/scorer_create_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["ScorerCreateResponse"] diff --git a/src/runloop_api_client/types/scenarios/scorer_list_response.py b/src/runloop_api_client/types/scenarios/scorer_list_response.py index d24bfd87e..8f8b12e15 100644 --- a/src/runloop_api_client/types/scenarios/scorer_list_response.py +++ b/src/runloop_api_client/types/scenarios/scorer_list_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["ScorerListResponse"] diff --git a/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py b/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py index d72dd5778..f2dd7f0b1 100644 --- a/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py +++ b/src/runloop_api_client/types/scenarios/scorer_retrieve_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["ScorerRetrieveResponse"] diff --git a/src/runloop_api_client/types/scenarios/scorer_update_response.py b/src/runloop_api_client/types/scenarios/scorer_update_response.py index ef77f37e6..540107613 100644 --- a/src/runloop_api_client/types/scenarios/scorer_update_response.py +++ b/src/runloop_api_client/types/scenarios/scorer_update_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["ScorerUpdateResponse"] diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py index 5d5fa1178..f2f78f3da 100644 --- a/src/runloop_api_client/types/shared/launch_parameters.py +++ b/src/runloop_api_client/types/shared/launch_parameters.py @@ -6,7 +6,15 @@ from ..._models import BaseModel from .after_idle import AfterIdle -__all__ = ["LaunchParameters"] +__all__ = ["LaunchParameters", "UserParameters"] + + +class UserParameters(BaseModel): + uid: int + """User ID (UID) for the Linux user. Must be a positive integer.""" + + username: str + """Username for the Linux user.""" class LaunchParameters(BaseModel): @@ -45,3 +53,9 @@ class LaunchParameters(BaseModel): Literal["X_SMALL", "SMALL", "MEDIUM", "LARGE", "X_LARGE", "XX_LARGE", "CUSTOM_SIZE"] ] = None """Manual resource configuration for Devbox. If not set, defaults will be used.""" + + user_parameters: Optional[UserParameters] = None + """Specify the user for execution on Devbox. + + If not set, default `user` will be used. + """ diff --git a/src/runloop_api_client/types/shared_params/launch_parameters.py b/src/runloop_api_client/types/shared_params/launch_parameters.py index decfb9feb..d51d06d99 100644 --- a/src/runloop_api_client/types/shared_params/launch_parameters.py +++ b/src/runloop_api_client/types/shared_params/launch_parameters.py @@ -3,11 +3,19 @@ from __future__ import annotations from typing import List, Iterable, Optional -from typing_extensions import Literal, TypedDict +from typing_extensions import Literal, Required, TypedDict from .after_idle import AfterIdle -__all__ = ["LaunchParameters"] +__all__ = ["LaunchParameters", "UserParameters"] + + +class UserParameters(TypedDict, total=False): + uid: Required[int] + """User ID (UID) for the Linux user. Must be a positive integer.""" + + username: Required[str] + """Username for the Linux user.""" class LaunchParameters(TypedDict, total=False): @@ -46,3 +54,9 @@ class LaunchParameters(TypedDict, total=False): Literal["X_SMALL", "SMALL", "MEDIUM", "LARGE", "X_LARGE", "XX_LARGE", "CUSTOM_SIZE"] ] """Manual resource configuration for Devbox. If not set, defaults will be used.""" + + user_parameters: Optional[UserParameters] + """Specify the user for execution on Devbox. + + If not set, default `user` will be used. + """ diff --git a/tests/api_resources/scenarios/test_scorers.py b/tests/api_resources/scenarios/test_scorers.py index 05ec020d4..db1ae2c56 100644 --- a/tests/api_resources/scenarios/test_scorers.py +++ b/tests/api_resources/scenarios/test_scorers.py @@ -202,6 +202,10 @@ def test_method_validate_with_all_params(self, client: Runloop) -> None: "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, "prebuilt_id": "prebuilt_id", "snapshot_id": "snapshot_id", @@ -426,6 +430,10 @@ async def test_method_validate_with_all_params(self, async_client: AsyncRunloop) "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, "prebuilt_id": "prebuilt_id", "snapshot_id": "snapshot_id", diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py index be38be581..64cd30459 100644 --- a/tests/api_resources/test_blueprints.py +++ b/tests/api_resources/test_blueprints.py @@ -55,6 +55,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, system_setup_commands=["string"], ) @@ -265,6 +269,10 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None: "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, system_setup_commands=["string"], ) @@ -331,6 +339,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, system_setup_commands=["string"], ) @@ -541,6 +553,10 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop) "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, system_setup_commands=["string"], ) diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index b0b96d7a4..0f7045def 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -71,6 +71,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, metadata={"foo": "string"}, name="name", @@ -947,6 +951,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, metadata={"foo": "string"}, name="name", diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index 73af952a4..6f7183bf8 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -84,6 +84,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, "prebuilt_id": "prebuilt_id", "snapshot_id": "snapshot_id", @@ -247,6 +251,10 @@ def test_method_update_with_all_params(self, client: Runloop) -> None: "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, "prebuilt_id": "prebuilt_id", "snapshot_id": "snapshot_id", @@ -508,6 +516,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, "prebuilt_id": "prebuilt_id", "snapshot_id": "snapshot_id", @@ -671,6 +683,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) - "keep_alive_time_seconds": 0, "launch_commands": ["string"], "resource_size_request": "X_SMALL", + "user_parameters": { + "uid": 0, + "username": "username", + }, }, "prebuilt_id": "prebuilt_id", "snapshot_id": "snapshot_id",