diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98974d6cf..a6a934d47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -35,7 +36,7 @@ jobs: run: ./scripts/lint upload: - if: github.repository == 'stainless-sdks/runloop-python' + if: github.repository == 'stainless-sdks/runloop-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 name: upload permissions: @@ -62,6 +63,7 @@ jobs: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fc0d7ff8b..563004f25 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.45.0" + ".": "0.46.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index a615cde74..212d59968 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 92 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-1d4b018cbfb409baf83725737f00789a9ddbbdbdbe12db1ac7a1fd2cf9c453f0.yml -openapi_spec_hash: 1c533f386f6d3d3802d353cc6f1df547 -config_hash: 33b544375e4a932cbb2890f79a9aa040 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-75c0dc94dd629772c98b3e6f763d8b3a1bdb732aa3eb92d49a59fb446fb2410d.yml +openapi_spec_hash: 12286e648ea1156bfa23020ed0c7598b +config_hash: 5a04741d17a98f68dc474a29c516d3f8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb9442bc..3db9c93f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 0.46.0 (2025-07-01) + +Full Changelog: [v0.45.0...v0.46.0](https://github.com/runloopai/api-client-python/compare/v0.45.0...v0.46.0) + +### Features + +* **api:** api update ([f8b3493](https://github.com/runloopai/api-client-python/commit/f8b3493a584b10a91fe1e7d543fe4c0de406cf7d)) + + +### Bug Fixes + +* **ci:** correct conditional ([2005e4f](https://github.com/runloopai/api-client-python/commit/2005e4ff9ca9bcb569ef4798bd490e036ad1afd5)) +* **ci:** release-doctor — report correct token name ([41a600a](https://github.com/runloopai/api-client-python/commit/41a600aeceb84b5c32a7d771acc6acd1cca97b8e)) + + +### Chores + +* **ci:** only run for pushes and fork pull requests ([c914e94](https://github.com/runloopai/api-client-python/commit/c914e94ea65e9d4180810cc3b816f4ab4a2a8e22)) + ## 0.45.0 (2025-06-24) Full Changelog: [v0.44.0...v0.45.0](https://github.com/runloopai/api-client-python/compare/v0.44.0...v0.45.0) diff --git a/README.md b/README.md index eb5672f4d..afc66e029 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ Error codes are as follows: ### Retries -Certain errors are automatically retried 0 times by default, with a short exponential backoff. +Certain errors are automatically retried 3 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors are all retried by default. @@ -266,7 +266,7 @@ client.with_options(max_retries=5).devboxes.create() ### Timeouts -By default requests time out after 1 minute. You can configure this with a `timeout` option, +By default requests time out after 30 seconds. You can configure this with a `timeout` option, which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python @@ -274,7 +274,7 @@ from runloop_api_client import Runloop # Configure the default for all requests: client = Runloop( - # 20 seconds (default is 1 minute) + # 20 seconds (default is 30 seconds) timeout=20.0, ) diff --git a/bin/check-release-environment b/bin/check-release-environment index 2ba897b5f..b845b0f4c 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The RUNLOOP_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi lenErrors=${#errors[@]} diff --git a/pyproject.toml b/pyproject.toml index 33cf23a70..fcc69280b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "0.45.0" +version = "0.46.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_constants.py b/src/runloop_api_client/_constants.py index 6be0f5e04..d1ccc0109 100644 --- a/src/runloop_api_client/_constants.py +++ b/src/runloop_api_client/_constants.py @@ -5,10 +5,10 @@ RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response" OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" -# default timeout is 1 minute -DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) -DEFAULT_MAX_RETRIES = 0 +# default timeout is 30 seconds +DEFAULT_TIMEOUT = httpx.Timeout(timeout=30, connect=5.0) +DEFAULT_MAX_RETRIES = 3 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) INITIAL_RETRY_DELAY = 1.0 -MAX_RETRY_DELAY = 10.0 +MAX_RETRY_DELAY = 15.0 diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index 0bac585fa..f66dbcb44 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.45.0" # x-release-please-version +__version__ = "0.46.0" # x-release-please-version diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 4d4c41709..fbff382d6 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -691,6 +691,8 @@ def download_file( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} return self._post( f"/v1/devboxes/{id}/download_file", @@ -962,6 +964,8 @@ def read_file_contents( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 extra_headers = {"Accept": "text/plain", **(extra_headers or {})} return self._post( f"/v1/devboxes/{id}/read_file_contents", @@ -1297,6 +1301,8 @@ def upload_file( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 body = deepcopy_minimal( { "path": path, @@ -1359,6 +1365,8 @@ def write_file_contents( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 return self._post( f"/v1/devboxes/{id}/write_file_contents", body=maybe_transform( @@ -1954,6 +1962,8 @@ async def download_file( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} return await self._post( f"/v1/devboxes/{id}/download_file", @@ -2225,6 +2235,8 @@ async def read_file_contents( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 extra_headers = {"Accept": "text/plain", **(extra_headers or {})} return await self._post( f"/v1/devboxes/{id}/read_file_contents", @@ -2560,6 +2572,8 @@ async def upload_file( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 body = deepcopy_minimal( { "path": path, @@ -2622,6 +2636,8 @@ async def write_file_contents( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 return await self._post( f"/v1/devboxes/{id}/write_file_contents", body=await async_maybe_transform( diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index 1515be695..ad7df57e5 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -7,7 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import is_given, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -17,6 +17,7 @@ async_to_streamed_response_wrapper, ) from ...lib.polling import PollingConfig, poll_until +from ..._constants import DEFAULT_TIMEOUT from ..._base_client import make_request_options from ...types.devboxes import execution_retrieve_params, execution_execute_sync_params, execution_execute_async_params from ...lib.polling_async import async_poll_until @@ -235,6 +236,8 @@ def execute_sync( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 return self._post( f"/v1/devboxes/{id}/execute_sync", body=maybe_transform( @@ -510,6 +513,8 @@ async def execute_sync( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 600 return await self._post( f"/v1/devboxes/{id}/execute_sync", body=await async_maybe_transform( diff --git a/src/runloop_api_client/types/scoring_function.py b/src/runloop_api_client/types/scoring_function.py index 9d5cf655a..c49ff61e4 100644 --- a/src/runloop_api_client/types/scoring_function.py +++ b/src/runloop_api_client/types/scoring_function.py @@ -74,6 +74,9 @@ class ScorerPythonScriptScoringFunction(BaseModel): type: Literal["python_script_scorer"] + python_version_constraint: Optional[str] = None + """Python version to run scoring. Default is "==3.12.10" """ + requirements_contents: Optional[str] = None """Package dependencies to be installed. diff --git a/src/runloop_api_client/types/scoring_function_param.py b/src/runloop_api_client/types/scoring_function_param.py index bfbddb21b..1dedcc557 100644 --- a/src/runloop_api_client/types/scoring_function_param.py +++ b/src/runloop_api_client/types/scoring_function_param.py @@ -73,6 +73,9 @@ class ScorerPythonScriptScoringFunction(TypedDict, total=False): type: Required[Literal["python_script_scorer"]] + python_version_constraint: Optional[str] + """Python version to run scoring. Default is "==3.12.10" """ + requirements_contents: str """Package dependencies to be installed. diff --git a/tests/test_client.py b/tests/test_client.py index 8f3f70514..9542e6d4b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -93,7 +93,7 @@ def test_copy_default_options(self) -> None: # options that have a default are overridden correctly copied = self.client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 0 + assert self.client.max_retries == 3 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 @@ -754,7 +754,7 @@ class Model(BaseModel): [3, "", 1], [2, "", 1 * 2.0], [1, "", 1 * 4.0], - [-1100, "", 10], # test large number potentially overflowing + [-1100, "", 15], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -949,7 +949,7 @@ def test_copy_default_options(self) -> None: # options that have a default are overridden correctly copied = self.client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 0 + assert self.client.max_retries == 3 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 @@ -1613,7 +1613,7 @@ class Model(BaseModel): [3, "", 1], [2, "", 1 * 2.0], [1, "", 1 * 4.0], - [-1100, "", 10], # test large number potentially overflowing + [-1100, "", 15], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797))