From a11e003cde6ce1119b245f3bab53539402413fd1 Mon Sep 17 00:00:00 2001 From: cploujoux Date: Tue, 19 May 2026 10:06:17 -0700 Subject: [PATCH 1/2] feat: add paginated controlplane list responses --- README.md | 55 +++- .../core/client/api/agents/list_agents.py | 145 +++++++--- .../core/client/api/compute/list_sandboxes.py | 162 +++++++++-- .../core/client/api/drives/list_drives.py | 139 ++++++++-- .../client/api/functions/list_functions.py | 143 ++++++++-- .../api/jobs/list_job_execution_tasks.py | 259 ++++++++++++++++++ .../client/api/jobs/list_job_executions.py | 99 +++++-- src/blaxel/core/client/api/jobs/list_jobs.py | 145 ++++++++-- .../core/client/api/models/list_models.py | 145 +++++++--- .../client/api/policies/get_policy_usages.py | 158 +++++++++++ .../core/client/api/policies/list_policies.py | 145 ++++++++-- .../core/client/api/volumes/list_volumes.py | 145 +++++++--- .../api/vpcs/get_egress_gateway_usage.py | 85 ++++++ .../client/api/workspaces/get_workspace.py | 23 +- src/blaxel/core/client/models/__init__.py | 64 +++++ src/blaxel/core/client/models/agent_list.py | 103 +++++++ .../core/client/models/configuration.py | 10 + src/blaxel/core/client/models/drive_list.py | 103 +++++++ .../core/client/models/function_list.py | 103 +++++++ .../core/client/models/job_execution_list.py | 103 +++++++ .../client/models/job_execution_task_list.py | 103 +++++++ src/blaxel/core/client/models/job_list.py | 103 +++++++ .../core/client/models/list_agents_sort.py | 20 ++ .../core/client/models/list_drives_sort.py | 20 ++ .../core/client/models/list_functions_sort.py | 20 ++ .../models/list_job_execution_tasks_sort.py | 20 ++ .../client/models/list_job_executions_sort.py | 20 ++ .../core/client/models/list_jobs_sort.py | 20 ++ .../core/client/models/list_models_sort.py | 20 ++ .../core/client/models/list_policies_sort.py | 20 ++ .../core/client/models/list_sandboxes_sort.py | 20 ++ .../core/client/models/list_volumes_sort.py | 20 ++ src/blaxel/core/client/models/lite_volume.py | 139 ++++++++++ .../client/models/lite_volume_metadata.py | 88 ++++++ .../core/client/models/lite_volume_spec.py | 70 +++++ src/blaxel/core/client/models/model_list.py | 103 +++++++ .../core/client/models/pagination_meta.py | 83 ++++++ src/blaxel/core/client/models/policy.py | 26 +- src/blaxel/core/client/models/policy_list.py | 103 +++++++ .../core/client/models/policy_usage_counts.py | 98 +++++++ .../core/client/models/policy_usages.py | 180 ++++++++++++ .../models/policy_usages_agents_item.py | 45 +++ .../models/policy_usages_functions_item.py | 45 +++ .../client/models/policy_usages_jobs_item.py | 45 +++ .../models/policy_usages_models_item.py | 45 +++ .../models/policy_usages_sandboxes_item.py | 45 +++ src/blaxel/core/client/models/sandbox_list.py | 104 +++++++ src/blaxel/core/client/models/trigger.py | 3 +- src/blaxel/core/client/models/volume_list.py | 103 +++++++ src/blaxel/core/client/models/workspace.py | 26 ++ .../models/workspace_resource_counts.py | 49 ++++ src/blaxel/core/client/pagination.py | 164 +++++++++++ src/blaxel/core/common/settings.py | 2 +- src/blaxel/core/drive/drive.py | 88 +++++- src/blaxel/core/jobs/__init__.py | 115 +++++--- src/blaxel/core/sandbox/default/sandbox.py | 47 +++- src/blaxel/core/sandbox/sync/sandbox.py | 47 +++- src/blaxel/core/volume/volume.py | 90 +++++- tests/core/test_controlplane_pagination.py | 160 +++++++++++ tests/core/test_settings_api_version.py | 3 +- 60 files changed, 4526 insertions(+), 330 deletions(-) create mode 100644 src/blaxel/core/client/api/jobs/list_job_execution_tasks.py create mode 100644 src/blaxel/core/client/api/policies/get_policy_usages.py create mode 100644 src/blaxel/core/client/api/vpcs/get_egress_gateway_usage.py create mode 100644 src/blaxel/core/client/models/agent_list.py create mode 100644 src/blaxel/core/client/models/drive_list.py create mode 100644 src/blaxel/core/client/models/function_list.py create mode 100644 src/blaxel/core/client/models/job_execution_list.py create mode 100644 src/blaxel/core/client/models/job_execution_task_list.py create mode 100644 src/blaxel/core/client/models/job_list.py create mode 100644 src/blaxel/core/client/models/list_agents_sort.py create mode 100644 src/blaxel/core/client/models/list_drives_sort.py create mode 100644 src/blaxel/core/client/models/list_functions_sort.py create mode 100644 src/blaxel/core/client/models/list_job_execution_tasks_sort.py create mode 100644 src/blaxel/core/client/models/list_job_executions_sort.py create mode 100644 src/blaxel/core/client/models/list_jobs_sort.py create mode 100644 src/blaxel/core/client/models/list_models_sort.py create mode 100644 src/blaxel/core/client/models/list_policies_sort.py create mode 100644 src/blaxel/core/client/models/list_sandboxes_sort.py create mode 100644 src/blaxel/core/client/models/list_volumes_sort.py create mode 100644 src/blaxel/core/client/models/lite_volume.py create mode 100644 src/blaxel/core/client/models/lite_volume_metadata.py create mode 100644 src/blaxel/core/client/models/lite_volume_spec.py create mode 100644 src/blaxel/core/client/models/model_list.py create mode 100644 src/blaxel/core/client/models/pagination_meta.py create mode 100644 src/blaxel/core/client/models/policy_list.py create mode 100644 src/blaxel/core/client/models/policy_usage_counts.py create mode 100644 src/blaxel/core/client/models/policy_usages.py create mode 100644 src/blaxel/core/client/models/policy_usages_agents_item.py create mode 100644 src/blaxel/core/client/models/policy_usages_functions_item.py create mode 100644 src/blaxel/core/client/models/policy_usages_jobs_item.py create mode 100644 src/blaxel/core/client/models/policy_usages_models_item.py create mode 100644 src/blaxel/core/client/models/policy_usages_sandboxes_item.py create mode 100644 src/blaxel/core/client/models/sandbox_list.py create mode 100644 src/blaxel/core/client/models/volume_list.py create mode 100644 src/blaxel/core/client/models/workspace_resource_counts.py create mode 100644 src/blaxel/core/client/pagination.py create mode 100644 tests/core/test_controlplane_pagination.py diff --git a/README.md b/README.md index 5f36dc89..6976d7f5 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,49 @@ When running Blaxel SDK from a remote server that is not Blaxel-hosted, we recom ## Usage +### Paginated list responses + +Control plane list methods return one page at a time. The return value behaves like a list for the current page and also exposes pagination helpers: + +```python +from blaxel.core import SandboxInstance + +page = await SandboxInstance.list(limit=50) + +for sandbox in page.data: + print(sandbox.metadata.name) + +if page.has_more: + next_page = await page.next_page() + print(next_page.next_cursor) +``` + +Use `auto_paging_iter()` only when you explicitly want the SDK to walk every page for you: + +```python +page = await SandboxInstance.list(limit=50) + +async for sandbox in page.auto_paging_iter(): + print(sandbox.metadata.name) +``` + +The same shape is used by `DriveInstance.list()`, `VolumeInstance.list()`, and job execution listing. Sync APIs expose the same fields, with a synchronous `next_page()`: + +```python +from blaxel.core import SyncDriveInstance + +page = SyncDriveInstance.list(limit=50) + +while True: + for drive in page.data: + print(drive.name) + + if not page.has_more: + break + + page = page.next_page() +``` + ### Sandboxes Sandboxes are secure, instant-launching compute environments that scale to zero after inactivity and resume in under 25ms. @@ -250,8 +293,10 @@ async def main(): ] }) - # List volumes - volumes = await VolumeInstance.list() + # List the first page of volumes + volumes = await VolumeInstance.list(limit=50) + for listed_volume in volumes.data: + print(listed_volume.name) # Delete volume (using class) await VolumeInstance.delete("my-volume") @@ -303,8 +348,10 @@ async def main(): except Exception as error: print(f"Timeout: {error}") - # List all executions - executions = await job.alist_executions() + # List one page of executions + executions = await job.alist_executions(limit=20) + for execution in executions.data: + print(execution.metadata.id) # Delete an execution await job.acancel_execution(execution_id) diff --git a/src/blaxel/core/client/api/agents/list_agents.py b/src/blaxel/core/client/api/agents/list_agents.py index 487c47a2..72fadd9f 100644 --- a/src/blaxel/core/client/api/agents/list_agents.py +++ b/src/blaxel/core/client/api/agents/list_agents.py @@ -5,30 +5,47 @@ from ... import errors from ...client import Client -from ...models.agent import Agent +from ...models.agent_list import AgentList from ...models.error import Error -from ...types import Response +from ...models.list_agents_sort import ListAgentsSort +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListAgentsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/agents", + "params": params, } return _kwargs -def _parse_response( - *, client: Client, response: httpx.Response -) -> Union[Error, list["Agent"]] | None: +def _parse_response(*, client: Client, response: httpx.Response) -> Union[AgentList, Error] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Agent.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = AgentList.from_dict(response.json()) return response_200 if response.status_code == 401: @@ -51,7 +68,7 @@ def _parse_response( def _build_response( *, client: Client, response: httpx.Response -) -> Response[Union[Error, list["Agent"]]]: +) -> Response[Union[AgentList, Error]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -63,21 +80,38 @@ def _build_response( def sync_detailed( *, client: Client, -) -> Response[Union[Error, list["Agent"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListAgentsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[AgentList, Error]]: """List all agents - Returns all AI agents deployed in the workspace. Each agent includes its deployment status, runtime - configuration, and global inference endpoint URL. + Returns AI agents deployed in the workspace. Each agent includes its deployment status, runtime + configuration, and global inference endpoint URL. Starting with API version 2026-04-28 the response + is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query + parameters; older versions keep returning a bare array with all agents. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListAgentsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Agent']]] + Response[Union[AgentList, Error]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -89,43 +123,76 @@ def sync_detailed( def sync( *, client: Client, -) -> Union[Error, list["Agent"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListAgentsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[AgentList, Error] | None: """List all agents - Returns all AI agents deployed in the workspace. Each agent includes its deployment status, runtime - configuration, and global inference endpoint URL. + Returns AI agents deployed in the workspace. Each agent includes its deployment status, runtime + configuration, and global inference endpoint URL. Starting with API version 2026-04-28 the response + is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query + parameters; older versions keep returning a bare array with all agents. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListAgentsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Agent']] + Union[AgentList, Error] """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[Union[Error, list["Agent"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListAgentsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[AgentList, Error]]: """List all agents - Returns all AI agents deployed in the workspace. Each agent includes its deployment status, runtime - configuration, and global inference endpoint URL. + Returns AI agents deployed in the workspace. Each agent includes its deployment status, runtime + configuration, and global inference endpoint URL. Starting with API version 2026-04-28 the response + is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query + parameters; older versions keep returning a bare array with all agents. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListAgentsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Agent']]] + Response[Union[AgentList, Error]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -135,22 +202,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> Union[Error, list["Agent"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListAgentsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[AgentList, Error] | None: """List all agents - Returns all AI agents deployed in the workspace. Each agent includes its deployment status, runtime - configuration, and global inference endpoint URL. + Returns AI agents deployed in the workspace. Each agent includes its deployment status, runtime + configuration, and global inference endpoint URL. Starting with API version 2026-04-28 the response + is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query + parameters; older versions keep returning a bare array with all agents. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListAgentsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Agent']] + Union[AgentList, Error] """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/compute/list_sandboxes.py b/src/blaxel/core/client/api/compute/list_sandboxes.py index 655e6877..8fbfab9b 100644 --- a/src/blaxel/core/client/api/compute/list_sandboxes.py +++ b/src/blaxel/core/client/api/compute/list_sandboxes.py @@ -6,14 +6,41 @@ from ... import errors from ...client import Client from ...models.error import Error -from ...models.sandbox import Sandbox -from ...types import Response +from ...models.list_sandboxes_sort import ListSandboxesSort +from ...models.sandbox_list import SandboxList +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + show_terminated: Union[Unset, bool] = False, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListSandboxesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["showTerminated"] = show_terminated + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/sandboxes", + "params": params, } return _kwargs @@ -21,14 +48,9 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response( *, client: Client, response: httpx.Response -) -> Union[Error, list["Sandbox"]] | None: +) -> Union[Error, SandboxList] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Sandbox.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = SandboxList.from_dict(response.json()) return response_200 if response.status_code == 401: @@ -51,7 +73,7 @@ def _parse_response( def _build_response( *, client: Client, response: httpx.Response -) -> Response[Union[Error, list["Sandbox"]]]: +) -> Response[Union[Error, SandboxList]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -63,21 +85,42 @@ def _build_response( def sync_detailed( *, client: Client, -) -> Response[Union[Error, list["Sandbox"]]]: + show_terminated: Union[Unset, bool] = False, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListSandboxesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, SandboxList]]: """List sandboxes - Returns all sandboxes in the workspace. Each sandbox includes its configuration, status, and - endpoint URL. + Returns sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint + URL. Terminated sandboxes are hidden by default; pass `showTerminated=true` to include them. + Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor + pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array + of all sandboxes. + + Args: + show_terminated (Union[Unset, bool]): Default: False. + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListSandboxesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Sandbox']]] + Response[Union[Error, SandboxList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + show_terminated=show_terminated, + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -89,43 +132,84 @@ def sync_detailed( def sync( *, client: Client, -) -> Union[Error, list["Sandbox"]] | None: + show_terminated: Union[Unset, bool] = False, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListSandboxesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, SandboxList] | None: """List sandboxes - Returns all sandboxes in the workspace. Each sandbox includes its configuration, status, and - endpoint URL. + Returns sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint + URL. Terminated sandboxes are hidden by default; pass `showTerminated=true` to include them. + Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor + pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array + of all sandboxes. + + Args: + show_terminated (Union[Unset, bool]): Default: False. + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListSandboxesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Sandbox']] + Union[Error, SandboxList] """ return sync_detailed( client=client, + show_terminated=show_terminated, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[Union[Error, list["Sandbox"]]]: + show_terminated: Union[Unset, bool] = False, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListSandboxesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, SandboxList]]: """List sandboxes - Returns all sandboxes in the workspace. Each sandbox includes its configuration, status, and - endpoint URL. + Returns sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint + URL. Terminated sandboxes are hidden by default; pass `showTerminated=true` to include them. + Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor + pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array + of all sandboxes. + + Args: + show_terminated (Union[Unset, bool]): Default: False. + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListSandboxesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Sandbox']]] + Response[Union[Error, SandboxList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + show_terminated=show_terminated, + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -135,22 +219,42 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> Union[Error, list["Sandbox"]] | None: + show_terminated: Union[Unset, bool] = False, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListSandboxesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, SandboxList] | None: """List sandboxes - Returns all sandboxes in the workspace. Each sandbox includes its configuration, status, and - endpoint URL. + Returns sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint + URL. Terminated sandboxes are hidden by default; pass `showTerminated=true` to include them. + Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor + pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array + of all sandboxes. + + Args: + show_terminated (Union[Unset, bool]): Default: False. + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListSandboxesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Sandbox']] + Union[Error, SandboxList] """ return ( await asyncio_detailed( client=client, + show_terminated=show_terminated, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/drives/list_drives.py b/src/blaxel/core/client/api/drives/list_drives.py index 0848e1b8..4cdaee68 100644 --- a/src/blaxel/core/client/api/drives/list_drives.py +++ b/src/blaxel/core/client/api/drives/list_drives.py @@ -5,29 +5,46 @@ from ... import errors from ...client import Client -from ...models.drive import Drive -from ...types import Response +from ...models.drive_list import DriveList +from ...models.list_drives_sort import ListDrivesSort +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListDrivesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/drives", + "params": params, } return _kwargs -def _parse_response( - *, client: Client, response: httpx.Response -) -> Union[Any, list["Drive"]] | None: +def _parse_response(*, client: Client, response: httpx.Response) -> Union[Any, DriveList] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Drive.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = DriveList.from_dict(response.json()) return response_200 if response.status_code == 401: @@ -39,9 +56,7 @@ def _parse_response( return None -def _build_response( - *, client: Client, response: httpx.Response -) -> Response[Union[Any, list["Drive"]]]: +def _build_response(*, client: Client, response: httpx.Response) -> Response[Union[Any, DriveList]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -53,21 +68,38 @@ def _build_response( def sync_detailed( *, client: Client, -) -> Response[Union[Any, list["Drive"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListDrivesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Any, DriveList]]: """List drives Returns all drives in the workspace. Drives provide persistent storage that can be attached to - agents, functions, and sandboxes. + agents, functions, and sandboxes. Starting with API version 2026-04-28, the response wraps items in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep returning a bare array with all drives. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListDrivesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Any, list['Drive']]] + Response[Union[Any, DriveList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -79,43 +111,76 @@ def sync_detailed( def sync( *, client: Client, -) -> Union[Any, list["Drive"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListDrivesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Any, DriveList] | None: """List drives Returns all drives in the workspace. Drives provide persistent storage that can be attached to - agents, functions, and sandboxes. + agents, functions, and sandboxes. Starting with API version 2026-04-28, the response wraps items in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep returning a bare array with all drives. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListDrivesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Any, list['Drive']] + Union[Any, DriveList] """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[Union[Any, list["Drive"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListDrivesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Any, DriveList]]: """List drives Returns all drives in the workspace. Drives provide persistent storage that can be attached to - agents, functions, and sandboxes. + agents, functions, and sandboxes. Starting with API version 2026-04-28, the response wraps items in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep returning a bare array with all drives. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListDrivesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Any, list['Drive']]] + Response[Union[Any, DriveList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -125,22 +190,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> Union[Any, list["Drive"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListDrivesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Any, DriveList] | None: """List drives Returns all drives in the workspace. Drives provide persistent storage that can be attached to - agents, functions, and sandboxes. + agents, functions, and sandboxes. Starting with API version 2026-04-28, the response wraps items in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep returning a bare array with all drives. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListDrivesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Any, list['Drive']] + Union[Any, DriveList] """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/functions/list_functions.py b/src/blaxel/core/client/api/functions/list_functions.py index 264e9f39..7cfd883f 100644 --- a/src/blaxel/core/client/api/functions/list_functions.py +++ b/src/blaxel/core/client/api/functions/list_functions.py @@ -6,14 +6,38 @@ from ... import errors from ...client import Client from ...models.error import Error -from ...models.function import Function -from ...types import Response +from ...models.function_list import FunctionList +from ...models.list_functions_sort import ListFunctionsSort +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListFunctionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/functions", + "params": params, } return _kwargs @@ -21,14 +45,9 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response( *, client: Client, response: httpx.Response -) -> Union[Error, list["Function"]] | None: +) -> Union[Error, FunctionList] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Function.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = FunctionList.from_dict(response.json()) return response_200 if response.status_code == 401: @@ -51,7 +70,7 @@ def _parse_response( def _build_response( *, client: Client, response: httpx.Response -) -> Response[Union[Error, list["Function"]]]: +) -> Response[Union[Error, FunctionList]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -63,21 +82,38 @@ def _build_response( def sync_detailed( *, client: Client, -) -> Response[Union[Error, list["Function"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListFunctionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, FunctionList]]: """List all MCP servers - Returns all MCP server functions deployed in the workspace. Each function includes its deployment - status, transport protocol (websocket or http-stream), and endpoint URL. + Returns MCP server functions deployed in the workspace. Each function includes its deployment + status, transport protocol (websocket or http-stream), and endpoint URL. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array with all functions. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListFunctionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Function']]] + Response[Union[Error, FunctionList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -89,43 +125,76 @@ def sync_detailed( def sync( *, client: Client, -) -> Union[Error, list["Function"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListFunctionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, FunctionList] | None: """List all MCP servers - Returns all MCP server functions deployed in the workspace. Each function includes its deployment - status, transport protocol (websocket or http-stream), and endpoint URL. + Returns MCP server functions deployed in the workspace. Each function includes its deployment + status, transport protocol (websocket or http-stream), and endpoint URL. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array with all functions. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListFunctionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Function']] + Union[Error, FunctionList] """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[Union[Error, list["Function"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListFunctionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, FunctionList]]: """List all MCP servers - Returns all MCP server functions deployed in the workspace. Each function includes its deployment - status, transport protocol (websocket or http-stream), and endpoint URL. + Returns MCP server functions deployed in the workspace. Each function includes its deployment + status, transport protocol (websocket or http-stream), and endpoint URL. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array with all functions. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListFunctionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Function']]] + Response[Union[Error, FunctionList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -135,22 +204,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> Union[Error, list["Function"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListFunctionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, FunctionList] | None: """List all MCP servers - Returns all MCP server functions deployed in the workspace. Each function includes its deployment - status, transport protocol (websocket or http-stream), and endpoint URL. + Returns MCP server functions deployed in the workspace. Each function includes its deployment + status, transport protocol (websocket or http-stream), and endpoint URL. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array with all functions. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListFunctionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Function']] + Union[Error, FunctionList] """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/jobs/list_job_execution_tasks.py b/src/blaxel/core/client/api/jobs/list_job_execution_tasks.py new file mode 100644 index 00000000..22c1ad0f --- /dev/null +++ b/src/blaxel/core/client/api/jobs/list_job_execution_tasks.py @@ -0,0 +1,259 @@ +from http import HTTPStatus +from typing import Any, Union, cast + +import httpx + +from ... import errors +from ...client import Client +from ...models.job_execution_task_list import JobExecutionTaskList +from ...models.list_job_execution_tasks_sort import ListJobExecutionTasksSort +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + job_id: str, + execution_id: str, + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobExecutionTasksSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": f"/jobs/{job_id}/executions/{execution_id}/tasks", + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: Client, response: httpx.Response +) -> Union[Any, JobExecutionTaskList] | None: + if response.status_code == 200: + response_200 = JobExecutionTaskList.from_dict(response.json()) + + return response_200 + if response.status_code == 400: + response_400 = cast(Any, None) + return response_400 + if response.status_code == 404: + response_404 = cast(Any, None) + return response_404 + if response.status_code == 500: + response_500 = cast(Any, None) + return response_500 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Client, response: httpx.Response +) -> Response[Union[Any, JobExecutionTaskList]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + job_id: str, + execution_id: str, + *, + client: Client, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobExecutionTasksSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Any, JobExecutionTaskList]]: + """List execution tasks + + Returns one cursor-paginated page of an execution's tasks. Tasks are derived from event history each + request; only the in-memory slicing is paginated, the events scan still fetches the whole event log + behind the scenes. Available starting with API version 2026-04-28. + + Args: + job_id (str): + execution_id (str): + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobExecutionTasksSort]): + q (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, JobExecutionTaskList]] + """ + + kwargs = _get_kwargs( + job_id=job_id, + execution_id=execution_id, + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + job_id: str, + execution_id: str, + *, + client: Client, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobExecutionTasksSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Any, JobExecutionTaskList] | None: + """List execution tasks + + Returns one cursor-paginated page of an execution's tasks. Tasks are derived from event history each + request; only the in-memory slicing is paginated, the events scan still fetches the whole event log + behind the scenes. Available starting with API version 2026-04-28. + + Args: + job_id (str): + execution_id (str): + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobExecutionTasksSort]): + q (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, JobExecutionTaskList] + """ + + return sync_detailed( + job_id=job_id, + execution_id=execution_id, + client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ).parsed + + +async def asyncio_detailed( + job_id: str, + execution_id: str, + *, + client: Client, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobExecutionTasksSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Any, JobExecutionTaskList]]: + """List execution tasks + + Returns one cursor-paginated page of an execution's tasks. Tasks are derived from event history each + request; only the in-memory slicing is paginated, the events scan still fetches the whole event log + behind the scenes. Available starting with API version 2026-04-28. + + Args: + job_id (str): + execution_id (str): + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobExecutionTasksSort]): + q (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, JobExecutionTaskList]] + """ + + kwargs = _get_kwargs( + job_id=job_id, + execution_id=execution_id, + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + job_id: str, + execution_id: str, + *, + client: Client, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobExecutionTasksSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Any, JobExecutionTaskList] | None: + """List execution tasks + + Returns one cursor-paginated page of an execution's tasks. Tasks are derived from event history each + request; only the in-memory slicing is paginated, the events scan still fetches the whole event log + behind the scenes. Available starting with API version 2026-04-28. + + Args: + job_id (str): + execution_id (str): + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobExecutionTasksSort]): + q (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, JobExecutionTaskList] + """ + + return ( + await asyncio_detailed( + job_id=job_id, + execution_id=execution_id, + client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) + ).parsed diff --git a/src/blaxel/core/client/api/jobs/list_job_executions.py b/src/blaxel/core/client/api/jobs/list_job_executions.py index 640b2162..ac653aa5 100644 --- a/src/blaxel/core/client/api/jobs/list_job_executions.py +++ b/src/blaxel/core/client/api/jobs/list_job_executions.py @@ -5,7 +5,8 @@ from ... import errors from ...client import Client -from ...models.job_execution import JobExecution +from ...models.job_execution_list import JobExecutionList +from ...models.list_job_executions_sort import ListJobExecutionsSort from ...types import UNSET, Response, Unset @@ -14,6 +15,9 @@ def _get_kwargs( *, limit: Union[Unset, int] = 20, offset: Union[Unset, int] = 0, + cursor: Union[Unset, str] = UNSET, + sort: Union[Unset, ListJobExecutionsSort] = UNSET, + q: Union[Unset, str] = UNSET, ) -> dict[str, Any]: params: dict[str, Any] = {} @@ -21,6 +25,16 @@ def _get_kwargs( params["offset"] = offset + params["cursor"] = cursor + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} _kwargs: dict[str, Any] = { @@ -34,14 +48,9 @@ def _get_kwargs( def _parse_response( *, client: Client, response: httpx.Response -) -> Union[Any, list["JobExecution"]] | None: +) -> Union[Any, JobExecutionList] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = JobExecution.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = JobExecutionList.from_dict(response.json()) return response_200 if response.status_code == 400: @@ -58,7 +67,7 @@ def _parse_response( def _build_response( *, client: Client, response: httpx.Response -) -> Response[Union[Any, list["JobExecution"]]]: +) -> Response[Union[Any, JobExecutionList]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -73,29 +82,39 @@ def sync_detailed( client: Client, limit: Union[Unset, int] = 20, offset: Union[Unset, int] = 0, -) -> Response[Union[Any, list["JobExecution"]]]: + cursor: Union[Unset, str] = UNSET, + sort: Union[Unset, ListJobExecutionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Any, JobExecutionList]]: """List job executions - Returns paginated list of executions for a batch job, sorted by creation time. Each execution - contains status, task counts, and timing information. + Returns executions for a batch job. Starting with API version 2026-04-28 the response is wrapped in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep the legacy offset/limit contract and return a bare array. Args: job_id (str): limit (Union[Unset, int]): Default: 20. offset (Union[Unset, int]): Default: 0. + cursor (Union[Unset, str]): + sort (Union[Unset, ListJobExecutionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Any, list['JobExecution']]] + Response[Union[Any, JobExecutionList]] """ kwargs = _get_kwargs( job_id=job_id, limit=limit, offset=offset, + cursor=cursor, + sort=sort, + q=q, ) response = client.get_httpx_client().request( @@ -111,23 +130,30 @@ def sync( client: Client, limit: Union[Unset, int] = 20, offset: Union[Unset, int] = 0, -) -> Union[Any, list["JobExecution"]] | None: + cursor: Union[Unset, str] = UNSET, + sort: Union[Unset, ListJobExecutionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Any, JobExecutionList] | None: """List job executions - Returns paginated list of executions for a batch job, sorted by creation time. Each execution - contains status, task counts, and timing information. + Returns executions for a batch job. Starting with API version 2026-04-28 the response is wrapped in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep the legacy offset/limit contract and return a bare array. Args: job_id (str): limit (Union[Unset, int]): Default: 20. offset (Union[Unset, int]): Default: 0. + cursor (Union[Unset, str]): + sort (Union[Unset, ListJobExecutionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Any, list['JobExecution']] + Union[Any, JobExecutionList] """ return sync_detailed( @@ -135,6 +161,9 @@ def sync( client=client, limit=limit, offset=offset, + cursor=cursor, + sort=sort, + q=q, ).parsed @@ -144,29 +173,39 @@ async def asyncio_detailed( client: Client, limit: Union[Unset, int] = 20, offset: Union[Unset, int] = 0, -) -> Response[Union[Any, list["JobExecution"]]]: + cursor: Union[Unset, str] = UNSET, + sort: Union[Unset, ListJobExecutionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Any, JobExecutionList]]: """List job executions - Returns paginated list of executions for a batch job, sorted by creation time. Each execution - contains status, task counts, and timing information. + Returns executions for a batch job. Starting with API version 2026-04-28 the response is wrapped in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep the legacy offset/limit contract and return a bare array. Args: job_id (str): limit (Union[Unset, int]): Default: 20. offset (Union[Unset, int]): Default: 0. + cursor (Union[Unset, str]): + sort (Union[Unset, ListJobExecutionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Any, list['JobExecution']]] + Response[Union[Any, JobExecutionList]] """ kwargs = _get_kwargs( job_id=job_id, limit=limit, offset=offset, + cursor=cursor, + sort=sort, + q=q, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -180,23 +219,30 @@ async def asyncio( client: Client, limit: Union[Unset, int] = 20, offset: Union[Unset, int] = 0, -) -> Union[Any, list["JobExecution"]] | None: + cursor: Union[Unset, str] = UNSET, + sort: Union[Unset, ListJobExecutionsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Any, JobExecutionList] | None: """List job executions - Returns paginated list of executions for a batch job, sorted by creation time. Each execution - contains status, task counts, and timing information. + Returns executions for a batch job. Starting with API version 2026-04-28 the response is wrapped in + `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older + versions keep the legacy offset/limit contract and return a bare array. Args: job_id (str): limit (Union[Unset, int]): Default: 20. offset (Union[Unset, int]): Default: 0. + cursor (Union[Unset, str]): + sort (Union[Unset, ListJobExecutionsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Any, list['JobExecution']] + Union[Any, JobExecutionList] """ return ( @@ -205,5 +251,8 @@ async def asyncio( client=client, limit=limit, offset=offset, + cursor=cursor, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/jobs/list_jobs.py b/src/blaxel/core/client/api/jobs/list_jobs.py index 039612cd..ca904865 100644 --- a/src/blaxel/core/client/api/jobs/list_jobs.py +++ b/src/blaxel/core/client/api/jobs/list_jobs.py @@ -1,31 +1,50 @@ from http import HTTPStatus -from typing import Any +from typing import Any, Union import httpx from ... import errors from ...client import Client -from ...models.job import Job -from ...types import Response +from ...models.job_list import JobList +from ...models.list_jobs_sort import ListJobsSort +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/jobs", + "params": params, } return _kwargs -def _parse_response(*, client: Client, response: httpx.Response) -> list["Job"] | None: +def _parse_response(*, client: Client, response: httpx.Response) -> JobList | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Job.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = JobList.from_dict(response.json()) return response_200 if client.raise_on_unexpected_status: @@ -34,7 +53,7 @@ def _parse_response(*, client: Client, response: httpx.Response) -> list["Job"] return None -def _build_response(*, client: Client, response: httpx.Response) -> Response[list["Job"]]: +def _build_response(*, client: Client, response: httpx.Response) -> Response[JobList]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -46,21 +65,38 @@ def _build_response(*, client: Client, response: httpx.Response) -> Response[lis def sync_detailed( *, client: Client, -) -> Response[list["Job"]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[JobList]: """List batch jobs - Returns all batch job definitions in the workspace. Each job can be triggered to run multiple - parallel tasks with configurable concurrency and retry settings. + Returns batch job definitions in the workspace. Each job can be triggered to run multiple parallel + tasks with configurable concurrency and retry settings. Starting with API version 2026-04-28 the + response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` + query parameters; older versions keep returning a bare array with all jobs. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[list['Job']] + Response[JobList] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -72,43 +108,76 @@ def sync_detailed( def sync( *, client: Client, -) -> list["Job"] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> JobList | None: """List batch jobs - Returns all batch job definitions in the workspace. Each job can be triggered to run multiple - parallel tasks with configurable concurrency and retry settings. + Returns batch job definitions in the workspace. Each job can be triggered to run multiple parallel + tasks with configurable concurrency and retry settings. Starting with API version 2026-04-28 the + response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` + query parameters; older versions keep returning a bare array with all jobs. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - list['Job'] + JobList """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[list["Job"]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[JobList]: """List batch jobs - Returns all batch job definitions in the workspace. Each job can be triggered to run multiple - parallel tasks with configurable concurrency and retry settings. + Returns batch job definitions in the workspace. Each job can be triggered to run multiple parallel + tasks with configurable concurrency and retry settings. Starting with API version 2026-04-28 the + response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` + query parameters; older versions keep returning a bare array with all jobs. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[list['Job']] + Response[JobList] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -118,22 +187,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> list["Job"] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListJobsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> JobList | None: """List batch jobs - Returns all batch job definitions in the workspace. Each job can be triggered to run multiple - parallel tasks with configurable concurrency and retry settings. + Returns batch job definitions in the workspace. Each job can be triggered to run multiple parallel + tasks with configurable concurrency and retry settings. Starting with API version 2026-04-28 the + response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` + query parameters; older versions keep returning a bare array with all jobs. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListJobsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - list['Job'] + JobList """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/models/list_models.py b/src/blaxel/core/client/api/models/list_models.py index 790712f0..d24681ab 100644 --- a/src/blaxel/core/client/api/models/list_models.py +++ b/src/blaxel/core/client/api/models/list_models.py @@ -6,29 +6,46 @@ from ... import errors from ...client import Client from ...models.error import Error -from ...models.model import Model -from ...types import Response +from ...models.list_models_sort import ListModelsSort +from ...models.model_list import ModelList +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListModelsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/models", + "params": params, } return _kwargs -def _parse_response( - *, client: Client, response: httpx.Response -) -> Union[Error, list["Model"]] | None: +def _parse_response(*, client: Client, response: httpx.Response) -> Union[Error, ModelList] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Model.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = ModelList.from_dict(response.json()) return response_200 if response.status_code == 401: @@ -51,7 +68,7 @@ def _parse_response( def _build_response( *, client: Client, response: httpx.Response -) -> Response[Union[Error, list["Model"]]]: +) -> Response[Union[Error, ModelList]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -63,21 +80,38 @@ def _build_response( def sync_detailed( *, client: Client, -) -> Response[Union[Error, list["Model"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListModelsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, ModelList]]: """List model endpoints - Returns all model gateway endpoints configured in the workspace. Each model represents a proxy to an - external LLM provider (OpenAI, Anthropic, etc.) with unified access control. + Returns model gateway endpoints configured in the workspace. Each model represents a proxy to an + external LLM provider (OpenAI, Anthropic, etc.) with unified access control. Starting with API + version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the + `cursor` and `limit` query parameters; older versions keep returning a bare array with all models. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListModelsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Model']]] + Response[Union[Error, ModelList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -89,43 +123,76 @@ def sync_detailed( def sync( *, client: Client, -) -> Union[Error, list["Model"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListModelsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, ModelList] | None: """List model endpoints - Returns all model gateway endpoints configured in the workspace. Each model represents a proxy to an - external LLM provider (OpenAI, Anthropic, etc.) with unified access control. + Returns model gateway endpoints configured in the workspace. Each model represents a proxy to an + external LLM provider (OpenAI, Anthropic, etc.) with unified access control. Starting with API + version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the + `cursor` and `limit` query parameters; older versions keep returning a bare array with all models. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListModelsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Model']] + Union[Error, ModelList] """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[Union[Error, list["Model"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListModelsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, ModelList]]: """List model endpoints - Returns all model gateway endpoints configured in the workspace. Each model represents a proxy to an - external LLM provider (OpenAI, Anthropic, etc.) with unified access control. + Returns model gateway endpoints configured in the workspace. Each model represents a proxy to an + external LLM provider (OpenAI, Anthropic, etc.) with unified access control. Starting with API + version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the + `cursor` and `limit` query parameters; older versions keep returning a bare array with all models. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListModelsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Model']]] + Response[Union[Error, ModelList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -135,22 +202,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> Union[Error, list["Model"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListModelsSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, ModelList] | None: """List model endpoints - Returns all model gateway endpoints configured in the workspace. Each model represents a proxy to an - external LLM provider (OpenAI, Anthropic, etc.) with unified access control. + Returns model gateway endpoints configured in the workspace. Each model represents a proxy to an + external LLM provider (OpenAI, Anthropic, etc.) with unified access control. Starting with API + version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the + `cursor` and `limit` query parameters; older versions keep returning a bare array with all models. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListModelsSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Model']] + Union[Error, ModelList] """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/policies/get_policy_usages.py b/src/blaxel/core/client/api/policies/get_policy_usages.py new file mode 100644 index 00000000..8efc2656 --- /dev/null +++ b/src/blaxel/core/client/api/policies/get_policy_usages.py @@ -0,0 +1,158 @@ +from http import HTTPStatus +from typing import Any + +import httpx + +from ... import errors +from ...client import Client +from ...models.policy_usages import PolicyUsages +from ...types import Response + + +def _get_kwargs( + policy_name: str, +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "get", + "url": f"/policies/{policy_name}/usages", + } + + return _kwargs + + +def _parse_response(*, client: Client, response: httpx.Response) -> PolicyUsages | None: + if response.status_code == 200: + response_200 = PolicyUsages.from_dict(response.json()) + + return response_200 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: Client, response: httpx.Response) -> Response[PolicyUsages]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + policy_name: str, + *, + client: Client, +) -> Response[PolicyUsages]: + """List resources using a policy + + Returns the names of every resource (agent, function, model, sandbox, job) currently referencing the + given policy. Replaces the client-side fan-out the policies UI used to do over the listings. + + Args: + policy_name (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[PolicyUsages] + """ + + kwargs = _get_kwargs( + policy_name=policy_name, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + policy_name: str, + *, + client: Client, +) -> PolicyUsages | None: + """List resources using a policy + + Returns the names of every resource (agent, function, model, sandbox, job) currently referencing the + given policy. Replaces the client-side fan-out the policies UI used to do over the listings. + + Args: + policy_name (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + PolicyUsages + """ + + return sync_detailed( + policy_name=policy_name, + client=client, + ).parsed + + +async def asyncio_detailed( + policy_name: str, + *, + client: Client, +) -> Response[PolicyUsages]: + """List resources using a policy + + Returns the names of every resource (agent, function, model, sandbox, job) currently referencing the + given policy. Replaces the client-side fan-out the policies UI used to do over the listings. + + Args: + policy_name (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[PolicyUsages] + """ + + kwargs = _get_kwargs( + policy_name=policy_name, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + policy_name: str, + *, + client: Client, +) -> PolicyUsages | None: + """List resources using a policy + + Returns the names of every resource (agent, function, model, sandbox, job) currently referencing the + given policy. Replaces the client-side fan-out the policies UI used to do over the listings. + + Args: + policy_name (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + PolicyUsages + """ + + return ( + await asyncio_detailed( + policy_name=policy_name, + client=client, + ) + ).parsed diff --git a/src/blaxel/core/client/api/policies/list_policies.py b/src/blaxel/core/client/api/policies/list_policies.py index 6f188ed5..6d2d7be8 100644 --- a/src/blaxel/core/client/api/policies/list_policies.py +++ b/src/blaxel/core/client/api/policies/list_policies.py @@ -1,31 +1,50 @@ from http import HTTPStatus -from typing import Any +from typing import Any, Union import httpx from ... import errors from ...client import Client -from ...models.policy import Policy -from ...types import Response +from ...models.list_policies_sort import ListPoliciesSort +from ...models.policy_list import PolicyList +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListPoliciesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/policies", + "params": params, } return _kwargs -def _parse_response(*, client: Client, response: httpx.Response) -> list["Policy"] | None: +def _parse_response(*, client: Client, response: httpx.Response) -> PolicyList | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Policy.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = PolicyList.from_dict(response.json()) return response_200 if client.raise_on_unexpected_status: @@ -34,7 +53,7 @@ def _parse_response(*, client: Client, response: httpx.Response) -> list["Policy return None -def _build_response(*, client: Client, response: httpx.Response) -> Response[list["Policy"]]: +def _build_response(*, client: Client, response: httpx.Response) -> Response[PolicyList]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -46,21 +65,38 @@ def _build_response(*, client: Client, response: httpx.Response) -> Response[lis def sync_detailed( *, client: Client, -) -> Response[list["Policy"]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListPoliciesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[PolicyList]: """List governance policies - Returns all governance policies in the workspace. Policies control deployment locations, hardware - flavors, and token limits for agents, functions, and models. + Returns governance policies in the workspace. Policies control deployment locations, hardware + flavors, and token limits for agents, functions, and models. Starting with API version 2026-04-28 + the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and + `limit` query parameters; older versions keep returning a bare array with all policies. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListPoliciesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[list['Policy']] + Response[PolicyList] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -72,43 +108,76 @@ def sync_detailed( def sync( *, client: Client, -) -> list["Policy"] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListPoliciesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> PolicyList | None: """List governance policies - Returns all governance policies in the workspace. Policies control deployment locations, hardware - flavors, and token limits for agents, functions, and models. + Returns governance policies in the workspace. Policies control deployment locations, hardware + flavors, and token limits for agents, functions, and models. Starting with API version 2026-04-28 + the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and + `limit` query parameters; older versions keep returning a bare array with all policies. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListPoliciesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - list['Policy'] + PolicyList """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[list["Policy"]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListPoliciesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[PolicyList]: """List governance policies - Returns all governance policies in the workspace. Policies control deployment locations, hardware - flavors, and token limits for agents, functions, and models. + Returns governance policies in the workspace. Policies control deployment locations, hardware + flavors, and token limits for agents, functions, and models. Starting with API version 2026-04-28 + the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and + `limit` query parameters; older versions keep returning a bare array with all policies. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListPoliciesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[list['Policy']] + Response[PolicyList] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -118,22 +187,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> list["Policy"] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListPoliciesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> PolicyList | None: """List governance policies - Returns all governance policies in the workspace. Policies control deployment locations, hardware - flavors, and token limits for agents, functions, and models. + Returns governance policies in the workspace. Policies control deployment locations, hardware + flavors, and token limits for agents, functions, and models. Starting with API version 2026-04-28 + the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and + `limit` query parameters; older versions keep returning a bare array with all policies. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListPoliciesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - list['Policy'] + PolicyList """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/volumes/list_volumes.py b/src/blaxel/core/client/api/volumes/list_volumes.py index 23cfe6d6..539a1829 100644 --- a/src/blaxel/core/client/api/volumes/list_volumes.py +++ b/src/blaxel/core/client/api/volumes/list_volumes.py @@ -6,29 +6,46 @@ from ... import errors from ...client import Client from ...models.error import Error -from ...models.volume import Volume -from ...types import Response +from ...models.list_volumes_sort import ListVolumesSort +from ...models.volume_list import VolumeList +from ...types import UNSET, Response, Unset -def _get_kwargs() -> dict[str, Any]: +def _get_kwargs( + *, + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListVolumesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["cursor"] = cursor + + params["limit"] = limit + + json_sort: Union[Unset, str] = UNSET + if not isinstance(sort, Unset): + json_sort = sort.value + + params["sort"] = json_sort + + params["q"] = q + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": "/volumes", + "params": params, } return _kwargs -def _parse_response( - *, client: Client, response: httpx.Response -) -> Union[Error, list["Volume"]] | None: +def _parse_response(*, client: Client, response: httpx.Response) -> Union[Error, VolumeList] | None: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = Volume.from_dict(response_200_item_data) - - response_200.append(response_200_item) + response_200 = VolumeList.from_dict(response.json()) return response_200 if response.status_code == 401: @@ -51,7 +68,7 @@ def _parse_response( def _build_response( *, client: Client, response: httpx.Response -) -> Response[Union[Error, list["Volume"]]]: +) -> Response[Union[Error, VolumeList]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -63,21 +80,38 @@ def _build_response( def sync_detailed( *, client: Client, -) -> Response[Union[Error, list["Volume"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListVolumesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, VolumeList]]: """List persistent volumes - Returns all persistent storage volumes in the workspace. Volumes can be attached to sandboxes for - durable file storage that persists across sessions and sandbox deletions. + Returns persistent storage volumes in the workspace. Volumes can be attached to sandboxes for + durable file storage that persists across sessions and sandbox deletions. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array of volumes. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListVolumesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Volume']]] + Response[Union[Error, VolumeList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = client.get_httpx_client().request( **kwargs, @@ -89,43 +123,76 @@ def sync_detailed( def sync( *, client: Client, -) -> Union[Error, list["Volume"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListVolumesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, VolumeList] | None: """List persistent volumes - Returns all persistent storage volumes in the workspace. Volumes can be attached to sandboxes for - durable file storage that persists across sessions and sandbox deletions. + Returns persistent storage volumes in the workspace. Volumes can be attached to sandboxes for + durable file storage that persists across sessions and sandbox deletions. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array of volumes. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListVolumesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Volume']] + Union[Error, VolumeList] """ return sync_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ).parsed async def asyncio_detailed( *, client: Client, -) -> Response[Union[Error, list["Volume"]]]: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListVolumesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Response[Union[Error, VolumeList]]: """List persistent volumes - Returns all persistent storage volumes in the workspace. Volumes can be attached to sandboxes for - durable file storage that persists across sessions and sandbox deletions. + Returns persistent storage volumes in the workspace. Volumes can be attached to sandboxes for + durable file storage that persists across sessions and sandbox deletions. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array of volumes. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListVolumesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Error, list['Volume']]] + Response[Union[Error, VolumeList]] """ - kwargs = _get_kwargs() + kwargs = _get_kwargs( + cursor=cursor, + limit=limit, + sort=sort, + q=q, + ) response = await client.get_async_httpx_client().request(**kwargs) @@ -135,22 +202,38 @@ async def asyncio_detailed( async def asyncio( *, client: Client, -) -> Union[Error, list["Volume"]] | None: + cursor: Union[Unset, str] = UNSET, + limit: Union[Unset, int] = 50, + sort: Union[Unset, ListVolumesSort] = UNSET, + q: Union[Unset, str] = UNSET, +) -> Union[Error, VolumeList] | None: """List persistent volumes - Returns all persistent storage volumes in the workspace. Volumes can be attached to sandboxes for - durable file storage that persists across sessions and sandbox deletions. + Returns persistent storage volumes in the workspace. Volumes can be attached to sandboxes for + durable file storage that persists across sessions and sandbox deletions. Starting with API version + 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` + and `limit` query parameters; older versions keep returning a bare array of volumes. + + Args: + cursor (Union[Unset, str]): + limit (Union[Unset, int]): Default: 50. + sort (Union[Unset, ListVolumesSort]): + q (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Error, list['Volume']] + Union[Error, VolumeList] """ return ( await asyncio_detailed( client=client, + cursor=cursor, + limit=limit, + sort=sort, + q=q, ) ).parsed diff --git a/src/blaxel/core/client/api/vpcs/get_egress_gateway_usage.py b/src/blaxel/core/client/api/vpcs/get_egress_gateway_usage.py new file mode 100644 index 00000000..b2aecda1 --- /dev/null +++ b/src/blaxel/core/client/api/vpcs/get_egress_gateway_usage.py @@ -0,0 +1,85 @@ +from http import HTTPStatus +from typing import Any + +import httpx + +from ... import errors +from ...client import Client +from ...types import Response + + +def _get_kwargs() -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/egressgateways/usage", + } + + return _kwargs + + +def _parse_response(*, client: Client, response: httpx.Response) -> Any | None: + if response.status_code == 200: + return None + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: Client, response: httpx.Response) -> Response[Any]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: Client, +) -> Response[Any]: + """Egress gateway sandbox attachments + + Returns the inverse map (gateway → sandbox names) for the workspace. Used by the egress-IPs UI to + render attachment counts without fetching the sandboxes listing full client-side. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs() + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +async def asyncio_detailed( + *, + client: Client, +) -> Response[Any]: + """Egress gateway sandbox attachments + + Returns the inverse map (gateway → sandbox names) for the workspace. Used by the egress-IPs UI to + render attachment counts without fetching the sandboxes listing full client-side. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs() + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) diff --git a/src/blaxel/core/client/api/workspaces/get_workspace.py b/src/blaxel/core/client/api/workspaces/get_workspace.py index d8d1c6c7..1e1bbc95 100644 --- a/src/blaxel/core/client/api/workspaces/get_workspace.py +++ b/src/blaxel/core/client/api/workspaces/get_workspace.py @@ -7,15 +7,24 @@ from ...client import Client from ...models.error import Error from ...models.workspace import Workspace -from ...types import Response +from ...types import UNSET, Response, Unset def _get_kwargs( workspace_name: str, + *, + count_resources: Union[Unset, bool] = False, ) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["countResources"] = count_resources + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + _kwargs: dict[str, Any] = { "method": "get", "url": f"/workspaces/{workspace_name}", + "params": params, } return _kwargs @@ -63,6 +72,7 @@ def sync_detailed( workspace_name: str, *, client: Client, + count_resources: Union[Unset, bool] = False, ) -> Response[Union[Error, Workspace]]: """Get workspace details @@ -71,6 +81,7 @@ def sync_detailed( Args: workspace_name (str): + count_resources (Union[Unset, bool]): Default: False. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -82,6 +93,7 @@ def sync_detailed( kwargs = _get_kwargs( workspace_name=workspace_name, + count_resources=count_resources, ) response = client.get_httpx_client().request( @@ -95,6 +107,7 @@ def sync( workspace_name: str, *, client: Client, + count_resources: Union[Unset, bool] = False, ) -> Union[Error, Workspace] | None: """Get workspace details @@ -103,6 +116,7 @@ def sync( Args: workspace_name (str): + count_resources (Union[Unset, bool]): Default: False. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -115,6 +129,7 @@ def sync( return sync_detailed( workspace_name=workspace_name, client=client, + count_resources=count_resources, ).parsed @@ -122,6 +137,7 @@ async def asyncio_detailed( workspace_name: str, *, client: Client, + count_resources: Union[Unset, bool] = False, ) -> Response[Union[Error, Workspace]]: """Get workspace details @@ -130,6 +146,7 @@ async def asyncio_detailed( Args: workspace_name (str): + count_resources (Union[Unset, bool]): Default: False. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -141,6 +158,7 @@ async def asyncio_detailed( kwargs = _get_kwargs( workspace_name=workspace_name, + count_resources=count_resources, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -152,6 +170,7 @@ async def asyncio( workspace_name: str, *, client: Client, + count_resources: Union[Unset, bool] = False, ) -> Union[Error, Workspace] | None: """Get workspace details @@ -160,6 +179,7 @@ async def asyncio( Args: workspace_name (str): + count_resources (Union[Unset, bool]): Default: False. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -173,5 +193,6 @@ async def asyncio( await asyncio_detailed( workspace_name=workspace_name, client=client, + count_resources=count_resources, ) ).parsed diff --git a/src/blaxel/core/client/models/__init__.py b/src/blaxel/core/client/models/__init__.py index c86b8df3..332bd09f 100644 --- a/src/blaxel/core/client/models/__init__.py +++ b/src/blaxel/core/client/models/__init__.py @@ -1,6 +1,7 @@ """Contains all the data models used in inputs/outputs""" from .agent import Agent +from .agent_list import AgentList from .agent_runtime import AgentRuntime from .agent_runtime_generation import AgentRuntimeGeneration from .agent_spec import AgentSpec @@ -32,6 +33,7 @@ from .delete_volume_template_version_response_200 import DeleteVolumeTemplateVersionResponse200 from .delete_workspace_service_account_response_200 import DeleteWorkspaceServiceAccountResponse200 from .drive import Drive +from .drive_list import DriveList from .drive_spec import DriveSpec from .drive_state import DriveState from .egress_config import EgressConfig @@ -58,6 +60,7 @@ from .form_config import FormConfig from .form_secrets import FormSecrets from .function import Function +from .function_list import FunctionList from .function_runtime import FunctionRuntime from .function_runtime_generation import FunctionRuntimeGeneration from .function_runtime_transport import FunctionRuntimeTransport @@ -95,6 +98,7 @@ from .invite_workspace_user_body import InviteWorkspaceUserBody from .job import Job from .job_execution import JobExecution +from .job_execution_list import JobExecutionList from .job_execution_metadata import JobExecutionMetadata from .job_execution_spec import JobExecutionSpec from .job_execution_spec_env_override import JobExecutionSpecEnvOverride @@ -102,21 +106,37 @@ from .job_execution_status import JobExecutionStatus from .job_execution_task import JobExecutionTask from .job_execution_task_condition import JobExecutionTaskCondition +from .job_execution_task_list import JobExecutionTaskList from .job_execution_task_metadata import JobExecutionTaskMetadata from .job_execution_task_spec import JobExecutionTaskSpec from .job_execution_task_status import JobExecutionTaskStatus +from .job_list import JobList from .job_runtime import JobRuntime from .job_runtime_generation import JobRuntimeGeneration from .job_spec import JobSpec from .job_volume import JobVolume from .job_volume_type import JobVolumeType +from .list_agents_sort import ListAgentsSort +from .list_drives_sort import ListDrivesSort +from .list_functions_sort import ListFunctionsSort +from .list_job_execution_tasks_sort import ListJobExecutionTasksSort +from .list_job_executions_sort import ListJobExecutionsSort +from .list_jobs_sort import ListJobsSort +from .list_models_sort import ListModelsSort from .list_pending_image_shares_direction import ListPendingImageSharesDirection +from .list_policies_sort import ListPoliciesSort +from .list_sandboxes_sort import ListSandboxesSort +from .list_volumes_sort import ListVolumesSort +from .lite_volume import LiteVolume +from .lite_volume_metadata import LiteVolumeMetadata +from .lite_volume_spec import LiteVolumeSpec from .location_response import LocationResponse from .mcp_definition import MCPDefinition from .mcp_definition_categories_item import MCPDefinitionCategoriesItem from .metadata import Metadata from .metadata_labels import MetadataLabels from .model import Model +from .model_list import ModelList from .model_runtime import ModelRuntime from .model_runtime_generation import ModelRuntimeGeneration from .model_runtime_type import ModelRuntimeType @@ -125,6 +145,7 @@ from .o_auth import OAuth from .o_auth_scope_item import OAuthScopeItem from .owner_fields import OwnerFields +from .pagination_meta import PaginationMeta from .pending_image_share import PendingImageShare from .pending_image_share_render import PendingImageShareRender from .pending_invitation import PendingInvitation @@ -138,12 +159,20 @@ PendingInvitationWorkspaceDetailsEmailsItem, ) from .policy import Policy +from .policy_list import PolicyList from .policy_location import PolicyLocation from .policy_location_type import PolicyLocationType from .policy_max_tokens import PolicyMaxTokens from .policy_resource_type import PolicyResourceType from .policy_spec import PolicySpec from .policy_spec_type import PolicySpecType +from .policy_usage_counts import PolicyUsageCounts +from .policy_usages import PolicyUsages +from .policy_usages_agents_item import PolicyUsagesAgentsItem +from .policy_usages_functions_item import PolicyUsagesFunctionsItem +from .policy_usages_jobs_item import PolicyUsagesJobsItem +from .policy_usages_models_item import PolicyUsagesModelsItem +from .policy_usages_sandboxes_item import PolicyUsagesSandboxesItem from .port import Port from .port_protocol import PortProtocol from .preview import Preview @@ -173,6 +202,7 @@ from .sandbox_error import SandboxError from .sandbox_error_details import SandboxErrorDetails from .sandbox_lifecycle import SandboxLifecycle +from .sandbox_list import SandboxList from .sandbox_network import SandboxNetwork from .sandbox_runtime import SandboxRuntime from .sandbox_runtime_extra_args import SandboxRuntimeExtraArgs @@ -198,6 +228,7 @@ from .update_workspace_user_role_body import UpdateWorkspaceUserRoleBody from .volume import Volume from .volume_attachment import VolumeAttachment +from .volume_list import VolumeList from .volume_spec import VolumeSpec from .volume_state import VolumeState from .volume_template import VolumeTemplate @@ -211,6 +242,7 @@ from .workspace import Workspace from .workspace_availability import WorkspaceAvailability from .workspace_availability_reason import WorkspaceAvailabilityReason +from .workspace_resource_counts import WorkspaceResourceCounts from .workspace_runtime import WorkspaceRuntime from .workspace_status import WorkspaceStatus from .workspace_user import WorkspaceUser @@ -218,6 +250,7 @@ __all__ = ( "Agent", + "AgentList", "AgentRuntime", "AgentRuntimeGeneration", "AgentSpec", @@ -249,6 +282,7 @@ "DeleteVolumeTemplateVersionResponse200", "DeleteWorkspaceServiceAccountResponse200", "Drive", + "DriveList", "DriveSpec", "DriveState", "EgressConfig", @@ -275,6 +309,7 @@ "FormConfig", "FormSecrets", "Function", + "FunctionList", "FunctionRuntime", "FunctionRuntimeGeneration", "FunctionRuntimeTransport", @@ -310,6 +345,7 @@ "InviteWorkspaceUserBody", "Job", "JobExecution", + "JobExecutionList", "JobExecutionMetadata", "JobExecutionSpec", "JobExecutionSpecEnvOverride", @@ -317,21 +353,37 @@ "JobExecutionStatus", "JobExecutionTask", "JobExecutionTaskCondition", + "JobExecutionTaskList", "JobExecutionTaskMetadata", "JobExecutionTaskSpec", "JobExecutionTaskStatus", + "JobList", "JobRuntime", "JobRuntimeGeneration", "JobSpec", "JobVolume", "JobVolumeType", + "ListAgentsSort", + "ListDrivesSort", + "ListFunctionsSort", + "ListJobExecutionsSort", + "ListJobExecutionTasksSort", + "ListJobsSort", + "ListModelsSort", "ListPendingImageSharesDirection", + "ListPoliciesSort", + "ListSandboxesSort", + "ListVolumesSort", + "LiteVolume", + "LiteVolumeMetadata", + "LiteVolumeSpec", "LocationResponse", "MCPDefinition", "MCPDefinitionCategoriesItem", "Metadata", "MetadataLabels", "Model", + "ModelList", "ModelRuntime", "ModelRuntimeGeneration", "ModelRuntimeType", @@ -340,6 +392,7 @@ "OAuth", "OAuthScopeItem", "OwnerFields", + "PaginationMeta", "PendingImageShare", "PendingImageShareRender", "PendingInvitation", @@ -351,12 +404,20 @@ "PendingInvitationWorkspaceDetails", "PendingInvitationWorkspaceDetailsEmailsItem", "Policy", + "PolicyList", "PolicyLocation", "PolicyLocationType", "PolicyMaxTokens", "PolicyResourceType", "PolicySpec", "PolicySpecType", + "PolicyUsageCounts", + "PolicyUsages", + "PolicyUsagesAgentsItem", + "PolicyUsagesFunctionsItem", + "PolicyUsagesJobsItem", + "PolicyUsagesModelsItem", + "PolicyUsagesSandboxesItem", "Port", "PortProtocol", "Preview", @@ -386,6 +447,7 @@ "SandboxError", "SandboxErrorDetails", "SandboxLifecycle", + "SandboxList", "SandboxNetwork", "SandboxRuntime", "SandboxRuntimeExtraArgs", @@ -411,6 +473,7 @@ "UpdateWorkspaceUserRoleBody", "Volume", "VolumeAttachment", + "VolumeList", "VolumeSpec", "VolumeState", "VolumeTemplate", @@ -424,6 +487,7 @@ "Workspace", "WorkspaceAvailability", "WorkspaceAvailabilityReason", + "WorkspaceResourceCounts", "WorkspaceRuntime", "WorkspaceStatus", "WorkspaceUser", diff --git a/src/blaxel/core/client/models/agent_list.py b/src/blaxel/core/client/models/agent_list.py new file mode 100644 index 00000000..35bd5a8b --- /dev/null +++ b/src/blaxel/core/client/models/agent_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.agent import Agent + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="AgentList") + + +@_attrs_define +class AgentList: + """Cursor-paginated list of agents. Returned starting with API version 2026-04-28; older API versions return a bare + array of agents instead. + + Attributes: + data (Union[Unset, list['Agent']]): Page of agents. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Agent"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.agent import Agent + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Agent.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + agent_list = cls( + data=data, + meta=meta, + ) + + agent_list.additional_properties = d + return agent_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/configuration.py b/src/blaxel/core/client/models/configuration.py index d0eec18b..6c474cb4 100644 --- a/src/blaxel/core/client/models/configuration.py +++ b/src/blaxel/core/client/models/configuration.py @@ -22,12 +22,15 @@ class Configuration: Attributes: continents (Union[Unset, list['Continent']]): Continents countries (Union[Unset, list['Country']]): Countries + detected_region (Union[Unset, str]): Auto-detected closest region based on viewer geolocation (from CloudFront + headers). Empty when geo headers are not available. private_locations (Union[Unset, list['PrivateLocation']]): Private locations managed with blaxel operator regions (Union[Unset, list['Region']]): Regions """ continents: Union[Unset, list["Continent"]] = UNSET countries: Union[Unset, list["Country"]] = UNSET + detected_region: Union[Unset, str] = UNSET private_locations: Union[Unset, list["PrivateLocation"]] = UNSET regions: Union[Unset, list["Region"]] = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -54,6 +57,8 @@ def to_dict(self) -> dict[str, Any]: countries_item = countries_item_data.to_dict() countries.append(countries_item) + detected_region = self.detected_region + private_locations: Union[Unset, list[dict[str, Any]]] = UNSET if not isinstance(self.private_locations, Unset): private_locations = [] @@ -81,6 +86,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["continents"] = continents if countries is not UNSET: field_dict["countries"] = countries + if detected_region is not UNSET: + field_dict["detectedRegion"] = detected_region if private_locations is not UNSET: field_dict["privateLocations"] = private_locations if regions is not UNSET: @@ -112,6 +119,8 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: countries.append(countries_item) + detected_region = d.pop("detectedRegion", d.pop("detected_region", UNSET)) + private_locations = [] _private_locations = d.pop("privateLocations", d.pop("private_locations", UNSET)) for private_locations_item_data in _private_locations or []: @@ -129,6 +138,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: configuration = cls( continents=continents, countries=countries, + detected_region=detected_region, private_locations=private_locations, regions=regions, ) diff --git a/src/blaxel/core/client/models/drive_list.py b/src/blaxel/core/client/models/drive_list.py new file mode 100644 index 00000000..cea6fa5c --- /dev/null +++ b/src/blaxel/core/client/models/drive_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.drive import Drive + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="DriveList") + + +@_attrs_define +class DriveList: + """Cursor-paginated list of drives. Returned starting with API version 2026-04-28; older API versions return a bare + array. + + Attributes: + data (Union[Unset, list['Drive']]): Page of drives. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Drive"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.drive import Drive + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Drive.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + drive_list = cls( + data=data, + meta=meta, + ) + + drive_list.additional_properties = d + return drive_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/function_list.py b/src/blaxel/core/client/models/function_list.py new file mode 100644 index 00000000..aa2928d0 --- /dev/null +++ b/src/blaxel/core/client/models/function_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.function import Function + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="FunctionList") + + +@_attrs_define +class FunctionList: + """Cursor-paginated list of MCP server functions. Returned starting with API version 2026-04-28; older API versions + return a bare array. + + Attributes: + data (Union[Unset, list['Function']]): Page of functions. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Function"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.function import Function + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Function.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + function_list = cls( + data=data, + meta=meta, + ) + + function_list.additional_properties = d + return function_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/job_execution_list.py b/src/blaxel/core/client/models/job_execution_list.py new file mode 100644 index 00000000..d8ceb625 --- /dev/null +++ b/src/blaxel/core/client/models/job_execution_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.job_execution import JobExecution + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="JobExecutionList") + + +@_attrs_define +class JobExecutionList: + """Cursor-paginated list of job executions. Returned starting with API version 2026-04-28; older API versions keep the + legacy offset-based contract and return a bare array. + + Attributes: + data (Union[Unset, list['JobExecution']]): Page of job executions. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["JobExecution"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.job_execution import JobExecution + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = JobExecution.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + job_execution_list = cls( + data=data, + meta=meta, + ) + + job_execution_list.additional_properties = d + return job_execution_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/job_execution_task_list.py b/src/blaxel/core/client/models/job_execution_task_list.py new file mode 100644 index 00000000..bfbdb71f --- /dev/null +++ b/src/blaxel/core/client/models/job_execution_task_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.job_execution_task import JobExecutionTask + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="JobExecutionTaskList") + + +@_attrs_define +class JobExecutionTaskList: + """Cursor-paginated list of an execution's tasks. Tasks are derived from event history; pagination slices the in-memory + list and the cursor is a base64-JSON offset bound to (workspace, job, execution). + + Attributes: + data (Union[Unset, list['JobExecutionTask']]): Page of execution tasks. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["JobExecutionTask"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.job_execution_task import JobExecutionTask + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = JobExecutionTask.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + job_execution_task_list = cls( + data=data, + meta=meta, + ) + + job_execution_task_list.additional_properties = d + return job_execution_task_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/job_list.py b/src/blaxel/core/client/models/job_list.py new file mode 100644 index 00000000..d74d73d6 --- /dev/null +++ b/src/blaxel/core/client/models/job_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.job import Job + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="JobList") + + +@_attrs_define +class JobList: + """Cursor-paginated list of batch jobs. Returned starting with API version 2026-04-28; older API versions return a bare + array. + + Attributes: + data (Union[Unset, list['Job']]): Page of jobs. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Job"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.job import Job + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Job.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + job_list = cls( + data=data, + meta=meta, + ) + + job_list.additional_properties = d + return job_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/list_agents_sort.py b/src/blaxel/core/client/models/list_agents_sort.py new file mode 100644 index 00000000..639624c5 --- /dev/null +++ b/src/blaxel/core/client/models/list_agents_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListAgentsSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListAgentsSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_drives_sort.py b/src/blaxel/core/client/models/list_drives_sort.py new file mode 100644 index 00000000..19ff48d0 --- /dev/null +++ b/src/blaxel/core/client/models/list_drives_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListDrivesSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListDrivesSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_functions_sort.py b/src/blaxel/core/client/models/list_functions_sort.py new file mode 100644 index 00000000..a5923dad --- /dev/null +++ b/src/blaxel/core/client/models/list_functions_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListFunctionsSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListFunctionsSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_job_execution_tasks_sort.py b/src/blaxel/core/client/models/list_job_execution_tasks_sort.py new file mode 100644 index 00000000..00004144 --- /dev/null +++ b/src/blaxel/core/client/models/list_job_execution_tasks_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListJobExecutionTasksSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListJobExecutionTasksSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_job_executions_sort.py b/src/blaxel/core/client/models/list_job_executions_sort.py new file mode 100644 index 00000000..d1e5348c --- /dev/null +++ b/src/blaxel/core/client/models/list_job_executions_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListJobExecutionsSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListJobExecutionsSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_jobs_sort.py b/src/blaxel/core/client/models/list_jobs_sort.py new file mode 100644 index 00000000..ad85ed14 --- /dev/null +++ b/src/blaxel/core/client/models/list_jobs_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListJobsSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListJobsSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_models_sort.py b/src/blaxel/core/client/models/list_models_sort.py new file mode 100644 index 00000000..693de7b0 --- /dev/null +++ b/src/blaxel/core/client/models/list_models_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListModelsSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListModelsSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_policies_sort.py b/src/blaxel/core/client/models/list_policies_sort.py new file mode 100644 index 00000000..8e67357d --- /dev/null +++ b/src/blaxel/core/client/models/list_policies_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListPoliciesSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListPoliciesSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_sandboxes_sort.py b/src/blaxel/core/client/models/list_sandboxes_sort.py new file mode 100644 index 00000000..afd1763e --- /dev/null +++ b/src/blaxel/core/client/models/list_sandboxes_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListSandboxesSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListSandboxesSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/list_volumes_sort.py b/src/blaxel/core/client/models/list_volumes_sort.py new file mode 100644 index 00000000..1e6581f3 --- /dev/null +++ b/src/blaxel/core/client/models/list_volumes_sort.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class ListVolumesSort(str, Enum): + CREATEDATASC = "createdAt:asc" + CREATEDATDESC = "createdAt:desc" + NAMEASC = "name:asc" + NAMEDESC = "name:desc" + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def _missing_(cls, value: object) -> "ListVolumesSort | None": + if isinstance(value, str): + upper_value = value.upper() + for member in cls: + if member.value.upper() == upper_value: + return member + return None diff --git a/src/blaxel/core/client/models/lite_volume.py b/src/blaxel/core/client/models/lite_volume.py new file mode 100644 index 00000000..d01a4844 --- /dev/null +++ b/src/blaxel/core/client/models/lite_volume.py @@ -0,0 +1,139 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.lite_volume_metadata import LiteVolumeMetadata + from ..models.lite_volume_spec import LiteVolumeSpec + from ..models.volume_state import VolumeState + + +T = TypeVar("T", bound="LiteVolume") + + +@_attrs_define +class LiteVolume: + """LiteVolume is the listing-shape projection of a Volume. Drops events to keep page payloads small. + + Attributes: + metadata (Union[Unset, LiteVolumeMetadata]): Compact metadata for a Volume, returned in listing responses. + spec (Union[Unset, LiteVolumeSpec]): Compact spec for a Volume, returned in listing responses. + state (Union[Unset, VolumeState]): Current runtime state of the volume including attachment status + status (Union[Unset, str]): Computed status of the volume. + terminated_at (Union[Unset, str]): Termination timestamp for soft-deleted volumes. + """ + + metadata: Union[Unset, "LiteVolumeMetadata"] = UNSET + spec: Union[Unset, "LiteVolumeSpec"] = UNSET + state: Union[Unset, "VolumeState"] = UNSET + status: Union[Unset, str] = UNSET + terminated_at: Union[Unset, str] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + metadata: Union[Unset, dict[str, Any]] = UNSET + if ( + self.metadata + and not isinstance(self.metadata, Unset) + and not isinstance(self.metadata, dict) + ): + metadata = self.metadata.to_dict() + elif self.metadata and isinstance(self.metadata, dict): + metadata = self.metadata + + spec: Union[Unset, dict[str, Any]] = UNSET + if self.spec and not isinstance(self.spec, Unset) and not isinstance(self.spec, dict): + spec = self.spec.to_dict() + elif self.spec and isinstance(self.spec, dict): + spec = self.spec + + state: Union[Unset, dict[str, Any]] = UNSET + if self.state and not isinstance(self.state, Unset) and not isinstance(self.state, dict): + state = self.state.to_dict() + elif self.state and isinstance(self.state, dict): + state = self.state + + status = self.status + + terminated_at = self.terminated_at + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if metadata is not UNSET: + field_dict["metadata"] = metadata + if spec is not UNSET: + field_dict["spec"] = spec + if state is not UNSET: + field_dict["state"] = state + if status is not UNSET: + field_dict["status"] = status + if terminated_at is not UNSET: + field_dict["terminatedAt"] = terminated_at + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.lite_volume_metadata import LiteVolumeMetadata + from ..models.lite_volume_spec import LiteVolumeSpec + from ..models.volume_state import VolumeState + + if not src_dict: + return None + d = src_dict.copy() + _metadata = d.pop("metadata", UNSET) + metadata: Union[Unset, LiteVolumeMetadata] + if isinstance(_metadata, Unset): + metadata = UNSET + else: + metadata = LiteVolumeMetadata.from_dict(_metadata) + + _spec = d.pop("spec", UNSET) + spec: Union[Unset, LiteVolumeSpec] + if isinstance(_spec, Unset): + spec = UNSET + else: + spec = LiteVolumeSpec.from_dict(_spec) + + _state = d.pop("state", UNSET) + state: Union[Unset, VolumeState] + if isinstance(_state, Unset): + state = UNSET + else: + state = VolumeState.from_dict(_state) + + status = d.pop("status", UNSET) + + terminated_at = d.pop("terminatedAt", d.pop("terminated_at", UNSET)) + + lite_volume = cls( + metadata=metadata, + spec=spec, + state=state, + status=status, + terminated_at=terminated_at, + ) + + lite_volume.additional_properties = d + return lite_volume + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/lite_volume_metadata.py b/src/blaxel/core/client/models/lite_volume_metadata.py new file mode 100644 index 00000000..08eb8e67 --- /dev/null +++ b/src/blaxel/core/client/models/lite_volume_metadata.py @@ -0,0 +1,88 @@ +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="LiteVolumeMetadata") + + +@_attrs_define +class LiteVolumeMetadata: + """Compact metadata for a Volume, returned in listing responses. + + Attributes: + created_at (Union[Unset, str]): + display_name (Union[Unset, str]): + name (Union[Unset, str]): + updated_at (Union[Unset, str]): + """ + + created_at: Union[Unset, str] = UNSET + display_name: Union[Unset, str] = UNSET + name: Union[Unset, str] = UNSET + updated_at: Union[Unset, str] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + created_at = self.created_at + + display_name = self.display_name + + name = self.name + + updated_at = self.updated_at + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if created_at is not UNSET: + field_dict["createdAt"] = created_at + if display_name is not UNSET: + field_dict["displayName"] = display_name + if name is not UNSET: + field_dict["name"] = name + if updated_at is not UNSET: + field_dict["updatedAt"] = updated_at + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + created_at = d.pop("createdAt", d.pop("created_at", UNSET)) + + display_name = d.pop("displayName", d.pop("display_name", UNSET)) + + name = d.pop("name", UNSET) + + updated_at = d.pop("updatedAt", d.pop("updated_at", UNSET)) + + lite_volume_metadata = cls( + created_at=created_at, + display_name=display_name, + name=name, + updated_at=updated_at, + ) + + lite_volume_metadata.additional_properties = d + return lite_volume_metadata + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/lite_volume_spec.py b/src/blaxel/core/client/models/lite_volume_spec.py new file mode 100644 index 00000000..685b66cd --- /dev/null +++ b/src/blaxel/core/client/models/lite_volume_spec.py @@ -0,0 +1,70 @@ +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="LiteVolumeSpec") + + +@_attrs_define +class LiteVolumeSpec: + """Compact spec for a Volume, returned in listing responses. + + Attributes: + region (Union[Unset, str]): Region the volume is provisioned in. + size (Union[Unset, int]): Volume size in gigabytes. + """ + + region: Union[Unset, str] = UNSET + size: Union[Unset, int] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + region = self.region + + size = self.size + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if region is not UNSET: + field_dict["region"] = region + if size is not UNSET: + field_dict["size"] = size + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + region = d.pop("region", UNSET) + + size = d.pop("size", UNSET) + + lite_volume_spec = cls( + region=region, + size=size, + ) + + lite_volume_spec.additional_properties = d + return lite_volume_spec + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/model_list.py b/src/blaxel/core/client/models/model_list.py new file mode 100644 index 00000000..ebaea83e --- /dev/null +++ b/src/blaxel/core/client/models/model_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.model import Model + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="ModelList") + + +@_attrs_define +class ModelList: + """Cursor-paginated list of model gateway endpoints. Returned starting with API version 2026-04-28; older API versions + return a bare array. + + Attributes: + data (Union[Unset, list['Model']]): Page of models. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Model"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.model import Model + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Model.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + model_list = cls( + data=data, + meta=meta, + ) + + model_list.additional_properties = d + return model_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/pagination_meta.py b/src/blaxel/core/client/models/pagination_meta.py new file mode 100644 index 00000000..a9568bae --- /dev/null +++ b/src/blaxel/core/client/models/pagination_meta.py @@ -0,0 +1,83 @@ +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="PaginationMeta") + + +@_attrs_define +class PaginationMeta: + """Pagination metadata returned alongside a page of listing results. Always present on listing endpoints starting with + API version 2026-04-28. + + Attributes: + has_more (Union[Unset, bool]): True when more pages are available beyond the current one. + next_cursor (Union[Unset, str]): Opaque cursor to pass back as the `cursor` query param for the next page. Empty + when there are no more pages. + total (Union[Unset, int]): Total number of items in the workspace, ignoring the current page's filters. Lets the + UI render "page X of Y" without walking the cursor chain. Computed from the hash-only metadata.workspace GSI + count, so search (`q`) does not narrow it. + """ + + has_more: Union[Unset, bool] = UNSET + next_cursor: Union[Unset, str] = UNSET + total: Union[Unset, int] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + has_more = self.has_more + + next_cursor = self.next_cursor + + total = self.total + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if has_more is not UNSET: + field_dict["hasMore"] = has_more + if next_cursor is not UNSET: + field_dict["nextCursor"] = next_cursor + if total is not UNSET: + field_dict["total"] = total + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + has_more = d.pop("hasMore", d.pop("has_more", UNSET)) + + next_cursor = d.pop("nextCursor", d.pop("next_cursor", UNSET)) + + total = d.pop("total", UNSET) + + pagination_meta = cls( + has_more=has_more, + next_cursor=next_cursor, + total=total, + ) + + pagination_meta.additional_properties = d + return pagination_meta + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy.py b/src/blaxel/core/client/models/policy.py index 9b913727..ae25aec4 100644 --- a/src/blaxel/core/client/models/policy.py +++ b/src/blaxel/core/client/models/policy.py @@ -1,11 +1,14 @@ -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..types import UNSET, Unset + if TYPE_CHECKING: from ..models.metadata import Metadata from ..models.policy_spec import PolicySpec + from ..models.policy_usage_counts import PolicyUsageCounts T = TypeVar("T", bound="Policy") @@ -19,10 +22,14 @@ class Policy: metadata (Metadata): Common metadata fields shared by all Blaxel resources including name, labels, timestamps, and ownership information spec (PolicySpec): Policy specification + usage (Union[Unset, PolicyUsageCounts]): Per-resource counts of how many resources reference a policy. Computed + by the policies listing endpoint to avoid client-side fan-out across the agents/models/functions/sandboxes/jobs + listings. """ metadata: "Metadata" spec: "PolicySpec" + usage: Union[Unset, "PolicyUsageCounts"] = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -37,6 +44,12 @@ def to_dict(self) -> dict[str, Any]: else: spec = self.spec.to_dict() + usage: Union[Unset, dict[str, Any]] = UNSET + if self.usage and not isinstance(self.usage, Unset) and not isinstance(self.usage, dict): + usage = self.usage.to_dict() + elif self.usage and isinstance(self.usage, dict): + usage = self.usage + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -45,6 +58,8 @@ def to_dict(self) -> dict[str, Any]: "spec": spec, } ) + if usage is not UNSET: + field_dict["usage"] = usage return field_dict @@ -52,6 +67,7 @@ def to_dict(self) -> dict[str, Any]: def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: from ..models.metadata import Metadata from ..models.policy_spec import PolicySpec + from ..models.policy_usage_counts import PolicyUsageCounts if not src_dict: return None @@ -60,9 +76,17 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: spec = PolicySpec.from_dict(d.pop("spec")) + _usage = d.pop("usage", UNSET) + usage: Union[Unset, PolicyUsageCounts] + if isinstance(_usage, Unset): + usage = UNSET + else: + usage = PolicyUsageCounts.from_dict(_usage) + policy = cls( metadata=metadata, spec=spec, + usage=usage, ) policy.additional_properties = d diff --git a/src/blaxel/core/client/models/policy_list.py b/src/blaxel/core/client/models/policy_list.py new file mode 100644 index 00000000..1f24b2a2 --- /dev/null +++ b/src/blaxel/core/client/models/policy_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.pagination_meta import PaginationMeta + from ..models.policy import Policy + + +T = TypeVar("T", bound="PolicyList") + + +@_attrs_define +class PolicyList: + """Cursor-paginated list of policies. Returned starting with API version 2026-04-28; older API versions return a bare + array. + + Attributes: + data (Union[Unset, list['Policy']]): Page of policies. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Policy"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.pagination_meta import PaginationMeta + from ..models.policy import Policy + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Policy.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + policy_list = cls( + data=data, + meta=meta, + ) + + policy_list.additional_properties = d + return policy_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usage_counts.py b/src/blaxel/core/client/models/policy_usage_counts.py new file mode 100644 index 00000000..a9435a08 --- /dev/null +++ b/src/blaxel/core/client/models/policy_usage_counts.py @@ -0,0 +1,98 @@ +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="PolicyUsageCounts") + + +@_attrs_define +class PolicyUsageCounts: + """Per-resource counts of how many resources reference a policy. Computed by the policies listing endpoint to avoid + client-side fan-out across the agents/models/functions/sandboxes/jobs listings. + + Attributes: + agents (Union[Unset, int]): + functions (Union[Unset, int]): + jobs (Union[Unset, int]): + models (Union[Unset, int]): + sandboxes (Union[Unset, int]): + """ + + agents: Union[Unset, int] = UNSET + functions: Union[Unset, int] = UNSET + jobs: Union[Unset, int] = UNSET + models: Union[Unset, int] = UNSET + sandboxes: Union[Unset, int] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + agents = self.agents + + functions = self.functions + + jobs = self.jobs + + models = self.models + + sandboxes = self.sandboxes + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if agents is not UNSET: + field_dict["agents"] = agents + if functions is not UNSET: + field_dict["functions"] = functions + if jobs is not UNSET: + field_dict["jobs"] = jobs + if models is not UNSET: + field_dict["models"] = models + if sandboxes is not UNSET: + field_dict["sandboxes"] = sandboxes + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + agents = d.pop("agents", UNSET) + + functions = d.pop("functions", UNSET) + + jobs = d.pop("jobs", UNSET) + + models = d.pop("models", UNSET) + + sandboxes = d.pop("sandboxes", UNSET) + + policy_usage_counts = cls( + agents=agents, + functions=functions, + jobs=jobs, + models=models, + sandboxes=sandboxes, + ) + + policy_usage_counts.additional_properties = d + return policy_usage_counts + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usages.py b/src/blaxel/core/client/models/policy_usages.py new file mode 100644 index 00000000..6120d063 --- /dev/null +++ b/src/blaxel/core/client/models/policy_usages.py @@ -0,0 +1,180 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.policy_usages_agents_item import PolicyUsagesAgentsItem + from ..models.policy_usages_functions_item import PolicyUsagesFunctionsItem + from ..models.policy_usages_jobs_item import PolicyUsagesJobsItem + from ..models.policy_usages_models_item import PolicyUsagesModelsItem + from ..models.policy_usages_sandboxes_item import PolicyUsagesSandboxesItem + + +T = TypeVar("T", bound="PolicyUsages") + + +@_attrs_define +class PolicyUsages: + """Resources currently referencing a policy. Returned by GET /policies/{name}/usages so the policies UI can render + attachments without fetching the agents/models/functions listings full. + + Attributes: + agents (Union[Unset, list['PolicyUsagesAgentsItem']]): Names of agents whose spec.policies contains this policy. + functions (Union[Unset, list['PolicyUsagesFunctionsItem']]): Names of functions whose spec.policies contains + this policy. + jobs (Union[Unset, list['PolicyUsagesJobsItem']]): Names of jobs whose spec.policies contains this policy. + models (Union[Unset, list['PolicyUsagesModelsItem']]): Names of models whose spec.policies contains this policy. + sandboxes (Union[Unset, list['PolicyUsagesSandboxesItem']]): Names of sandboxes whose spec.policies contains + this policy. + """ + + agents: Union[Unset, list["PolicyUsagesAgentsItem"]] = UNSET + functions: Union[Unset, list["PolicyUsagesFunctionsItem"]] = UNSET + jobs: Union[Unset, list["PolicyUsagesJobsItem"]] = UNSET + models: Union[Unset, list["PolicyUsagesModelsItem"]] = UNSET + sandboxes: Union[Unset, list["PolicyUsagesSandboxesItem"]] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + agents: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.agents, Unset): + agents = [] + for agents_item_data in self.agents: + if type(agents_item_data) is dict: + agents_item = agents_item_data + else: + agents_item = agents_item_data.to_dict() + agents.append(agents_item) + + functions: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.functions, Unset): + functions = [] + for functions_item_data in self.functions: + if type(functions_item_data) is dict: + functions_item = functions_item_data + else: + functions_item = functions_item_data.to_dict() + functions.append(functions_item) + + jobs: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.jobs, Unset): + jobs = [] + for jobs_item_data in self.jobs: + if type(jobs_item_data) is dict: + jobs_item = jobs_item_data + else: + jobs_item = jobs_item_data.to_dict() + jobs.append(jobs_item) + + models: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.models, Unset): + models = [] + for models_item_data in self.models: + if type(models_item_data) is dict: + models_item = models_item_data + else: + models_item = models_item_data.to_dict() + models.append(models_item) + + sandboxes: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.sandboxes, Unset): + sandboxes = [] + for sandboxes_item_data in self.sandboxes: + if type(sandboxes_item_data) is dict: + sandboxes_item = sandboxes_item_data + else: + sandboxes_item = sandboxes_item_data.to_dict() + sandboxes.append(sandboxes_item) + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if agents is not UNSET: + field_dict["agents"] = agents + if functions is not UNSET: + field_dict["functions"] = functions + if jobs is not UNSET: + field_dict["jobs"] = jobs + if models is not UNSET: + field_dict["models"] = models + if sandboxes is not UNSET: + field_dict["sandboxes"] = sandboxes + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.policy_usages_agents_item import PolicyUsagesAgentsItem + from ..models.policy_usages_functions_item import PolicyUsagesFunctionsItem + from ..models.policy_usages_jobs_item import PolicyUsagesJobsItem + from ..models.policy_usages_models_item import PolicyUsagesModelsItem + from ..models.policy_usages_sandboxes_item import PolicyUsagesSandboxesItem + + if not src_dict: + return None + d = src_dict.copy() + agents = [] + _agents = d.pop("agents", UNSET) + for agents_item_data in _agents or []: + agents_item = PolicyUsagesAgentsItem.from_dict(agents_item_data) + + agents.append(agents_item) + + functions = [] + _functions = d.pop("functions", UNSET) + for functions_item_data in _functions or []: + functions_item = PolicyUsagesFunctionsItem.from_dict(functions_item_data) + + functions.append(functions_item) + + jobs = [] + _jobs = d.pop("jobs", UNSET) + for jobs_item_data in _jobs or []: + jobs_item = PolicyUsagesJobsItem.from_dict(jobs_item_data) + + jobs.append(jobs_item) + + models = [] + _models = d.pop("models", UNSET) + for models_item_data in _models or []: + models_item = PolicyUsagesModelsItem.from_dict(models_item_data) + + models.append(models_item) + + sandboxes = [] + _sandboxes = d.pop("sandboxes", UNSET) + for sandboxes_item_data in _sandboxes or []: + sandboxes_item = PolicyUsagesSandboxesItem.from_dict(sandboxes_item_data) + + sandboxes.append(sandboxes_item) + + policy_usages = cls( + agents=agents, + functions=functions, + jobs=jobs, + models=models, + sandboxes=sandboxes, + ) + + policy_usages.additional_properties = d + return policy_usages + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usages_agents_item.py b/src/blaxel/core/client/models/policy_usages_agents_item.py new file mode 100644 index 00000000..575ef1ea --- /dev/null +++ b/src/blaxel/core/client/models/policy_usages_agents_item.py @@ -0,0 +1,45 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PolicyUsagesAgentsItem") + + +@_attrs_define +class PolicyUsagesAgentsItem: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + policy_usages_agents_item = cls() + + policy_usages_agents_item.additional_properties = d + return policy_usages_agents_item + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usages_functions_item.py b/src/blaxel/core/client/models/policy_usages_functions_item.py new file mode 100644 index 00000000..b0bf2753 --- /dev/null +++ b/src/blaxel/core/client/models/policy_usages_functions_item.py @@ -0,0 +1,45 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PolicyUsagesFunctionsItem") + + +@_attrs_define +class PolicyUsagesFunctionsItem: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + policy_usages_functions_item = cls() + + policy_usages_functions_item.additional_properties = d + return policy_usages_functions_item + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usages_jobs_item.py b/src/blaxel/core/client/models/policy_usages_jobs_item.py new file mode 100644 index 00000000..f7698c49 --- /dev/null +++ b/src/blaxel/core/client/models/policy_usages_jobs_item.py @@ -0,0 +1,45 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PolicyUsagesJobsItem") + + +@_attrs_define +class PolicyUsagesJobsItem: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + policy_usages_jobs_item = cls() + + policy_usages_jobs_item.additional_properties = d + return policy_usages_jobs_item + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usages_models_item.py b/src/blaxel/core/client/models/policy_usages_models_item.py new file mode 100644 index 00000000..fe89c9ba --- /dev/null +++ b/src/blaxel/core/client/models/policy_usages_models_item.py @@ -0,0 +1,45 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PolicyUsagesModelsItem") + + +@_attrs_define +class PolicyUsagesModelsItem: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + policy_usages_models_item = cls() + + policy_usages_models_item.additional_properties = d + return policy_usages_models_item + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/policy_usages_sandboxes_item.py b/src/blaxel/core/client/models/policy_usages_sandboxes_item.py new file mode 100644 index 00000000..a66d42fd --- /dev/null +++ b/src/blaxel/core/client/models/policy_usages_sandboxes_item.py @@ -0,0 +1,45 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PolicyUsagesSandboxesItem") + + +@_attrs_define +class PolicyUsagesSandboxesItem: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + policy_usages_sandboxes_item = cls() + + policy_usages_sandboxes_item.additional_properties = d + return policy_usages_sandboxes_item + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/sandbox_list.py b/src/blaxel/core/client/models/sandbox_list.py new file mode 100644 index 00000000..d97390ca --- /dev/null +++ b/src/blaxel/core/client/models/sandbox_list.py @@ -0,0 +1,104 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.pagination_meta import PaginationMeta + from ..models.sandbox import Sandbox + + +T = TypeVar("T", bound="SandboxList") + + +@_attrs_define +class SandboxList: + """Cursor-paginated list of sandboxes. Returned starting with API version 2026-04-28; older API versions return a bare + array. + + Attributes: + data (Union[Unset, list['Sandbox']]): Page of sandboxes. Items use the lite shape (no inline event history) to + keep the page payload small, matching the unpaginated response. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["Sandbox"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.pagination_meta import PaginationMeta + from ..models.sandbox import Sandbox + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = Sandbox.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + sandbox_list = cls( + data=data, + meta=meta, + ) + + sandbox_list.additional_properties = d + return sandbox_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/trigger.py b/src/blaxel/core/client/models/trigger.py index 8ddd3013..b2fe9592 100644 --- a/src/blaxel/core/client/models/trigger.py +++ b/src/blaxel/core/client/models/trigger.py @@ -20,7 +20,8 @@ class Trigger: Attributes: configuration (Union[Unset, TriggerConfiguration]): Trigger configuration enabled (Union[Unset, bool]): Enable or disable the trigger (default: true) Example: True. - id (Union[Unset, str]): The id of the trigger Example: trigger-1. + id (Union[Unset, str]): Identifier of the trigger. Optional — the server auto-generates a unique id when one is + not provided, and disambiguates duplicates within a resource. Example: trigger-1. type_ (Union[Unset, TriggerType]): The type of trigger, can be http or http-async Example: http. """ diff --git a/src/blaxel/core/client/models/volume_list.py b/src/blaxel/core/client/models/volume_list.py new file mode 100644 index 00000000..2025d792 --- /dev/null +++ b/src/blaxel/core/client/models/volume_list.py @@ -0,0 +1,103 @@ +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.lite_volume import LiteVolume + from ..models.pagination_meta import PaginationMeta + + +T = TypeVar("T", bound="VolumeList") + + +@_attrs_define +class VolumeList: + """Cursor-paginated list of volumes. Returned starting with API version 2026-04-28; older API versions return a bare + array. Items use the lite shape (no inline event history). + + Attributes: + data (Union[Unset, list['LiteVolume']]): Page of volumes. + meta (Union[Unset, PaginationMeta]): Pagination metadata returned alongside a page of listing results. Always + present on listing endpoints starting with API version 2026-04-28. + """ + + data: Union[Unset, list["LiteVolume"]] = UNSET + meta: Union[Unset, "PaginationMeta"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + data: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.data, Unset): + data = [] + for data_item_data in self.data: + if type(data_item_data) is dict: + data_item = data_item_data + else: + data_item = data_item_data.to_dict() + data.append(data_item) + + meta: Union[Unset, dict[str, Any]] = UNSET + if self.meta and not isinstance(self.meta, Unset) and not isinstance(self.meta, dict): + meta = self.meta.to_dict() + elif self.meta and isinstance(self.meta, dict): + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if data is not UNSET: + field_dict["data"] = data + if meta is not UNSET: + field_dict["meta"] = meta + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + from ..models.lite_volume import LiteVolume + from ..models.pagination_meta import PaginationMeta + + if not src_dict: + return None + d = src_dict.copy() + data = [] + _data = d.pop("data", UNSET) + for data_item_data in _data or []: + data_item = LiteVolume.from_dict(data_item_data) + + data.append(data_item) + + _meta = d.pop("meta", UNSET) + meta: Union[Unset, PaginationMeta] + if isinstance(_meta, Unset): + meta = UNSET + else: + meta = PaginationMeta.from_dict(_meta) + + volume_list = cls( + data=data, + meta=meta, + ) + + volume_list.additional_properties = d + return volume_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/models/workspace.py b/src/blaxel/core/client/models/workspace.py index 9a62f326..7643645a 100644 --- a/src/blaxel/core/client/models/workspace.py +++ b/src/blaxel/core/client/models/workspace.py @@ -9,6 +9,7 @@ if TYPE_CHECKING: from ..models.group_workspace_mapping import GroupWorkspaceMapping from ..models.metadata_labels import MetadataLabels + from ..models.workspace_resource_counts import WorkspaceResourceCounts from ..models.workspace_runtime import WorkspaceRuntime @@ -34,6 +35,9 @@ class Workspace: used to categorize resources by environment, project, team, or any custom taxonomy. name (Union[Unset, str]): Workspace name Example: my-workspace. region (Union[Unset, str]): Workspace write region Example: us-west-2. + resource_counts (Union[Unset, WorkspaceResourceCounts]): Per-resource counts (agents, functions, models, + sandboxes, policies, jobs, volumes, drives, volumetemplates, integrationconnections, previews, customdomains, + serviceaccounts, images). Only populated when GetWorkspace is called with ?countResources=true. runtime (Union[Unset, WorkspaceRuntime]): Runtime configuration for the workspace infrastructure status (Union[Unset, WorkspaceStatus]): Workspace status (created, account_binded, account_configured, workspace_configured, ready, error) Example: ready. @@ -51,6 +55,7 @@ class Workspace: labels: Union[Unset, "MetadataLabels"] = UNSET name: Union[Unset, str] = UNSET region: Union[Unset, str] = UNSET + resource_counts: Union[Unset, "WorkspaceResourceCounts"] = UNSET runtime: Union[Unset, "WorkspaceRuntime"] = UNSET status: Union[Unset, WorkspaceStatus] = UNSET status_reason: Union[Unset, str] = UNSET @@ -92,6 +97,16 @@ def to_dict(self) -> dict[str, Any]: region = self.region + resource_counts: Union[Unset, dict[str, Any]] = UNSET + if ( + self.resource_counts + and not isinstance(self.resource_counts, Unset) + and not isinstance(self.resource_counts, dict) + ): + resource_counts = self.resource_counts.to_dict() + elif self.resource_counts and isinstance(self.resource_counts, dict): + resource_counts = self.resource_counts + runtime: Union[Unset, dict[str, Any]] = UNSET if ( self.runtime @@ -133,6 +148,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["name"] = name if region is not UNSET: field_dict["region"] = region + if resource_counts is not UNSET: + field_dict["resourceCounts"] = resource_counts if runtime is not UNSET: field_dict["runtime"] = runtime if status is not UNSET: @@ -146,6 +163,7 @@ def to_dict(self) -> dict[str, Any]: def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: from ..models.group_workspace_mapping import GroupWorkspaceMapping from ..models.metadata_labels import MetadataLabels + from ..models.workspace_resource_counts import WorkspaceResourceCounts from ..models.workspace_runtime import WorkspaceRuntime if not src_dict: @@ -183,6 +201,13 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: region = d.pop("region", UNSET) + _resource_counts = d.pop("resourceCounts", d.pop("resource_counts", UNSET)) + resource_counts: Union[Unset, WorkspaceResourceCounts] + if isinstance(_resource_counts, Unset): + resource_counts = UNSET + else: + resource_counts = WorkspaceResourceCounts.from_dict(_resource_counts) + _runtime = d.pop("runtime", UNSET) runtime: Union[Unset, WorkspaceRuntime] if isinstance(_runtime, Unset): @@ -211,6 +236,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: labels=labels, name=name, region=region, + resource_counts=resource_counts, runtime=runtime, status=status, status_reason=status_reason, diff --git a/src/blaxel/core/client/models/workspace_resource_counts.py b/src/blaxel/core/client/models/workspace_resource_counts.py new file mode 100644 index 00000000..b550cb3d --- /dev/null +++ b/src/blaxel/core/client/models/workspace_resource_counts.py @@ -0,0 +1,49 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="WorkspaceResourceCounts") + + +@_attrs_define +class WorkspaceResourceCounts: + """Per-resource counts (agents, functions, models, sandboxes, policies, jobs, volumes, drives, volumetemplates, + integrationconnections, previews, customdomains, serviceaccounts, images). Only populated when GetWorkspace is + called with ?countResources=true. + + """ + + additional_properties: dict[str, int] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + if not src_dict: + return None + d = src_dict.copy() + workspace_resource_counts = cls() + + workspace_resource_counts.additional_properties = d + return workspace_resource_counts + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> int: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: int) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/blaxel/core/client/pagination.py b/src/blaxel/core/client/pagination.py new file mode 100644 index 00000000..c749b4f1 --- /dev/null +++ b/src/blaxel/core/client/pagination.py @@ -0,0 +1,164 @@ +from collections.abc import AsyncIterator, Awaitable, Callable, Iterator +from typing import Any, Generic, TypeVar + +from .types import UNSET, Unset + +T = TypeVar("T") +RawPage = TypeVar("RawPage") + +DEFAULT_PAGE_LIMIT = 50 + + +def get_page_data(page: Any) -> list[Any]: + if page is None: + return [] + if isinstance(page, list): + return page + + data = getattr(page, "data", UNSET) + if data is UNSET or data is None: + return [] + return list(data) + + +def get_page_meta(page: Any) -> Any: + if page is None or isinstance(page, list): + return UNSET + return getattr(page, "meta", UNSET) + + +def _has_more(meta: Any) -> bool: + return meta is not UNSET and meta is not None and bool(getattr(meta, "has_more", False)) + + +def _next_cursor(meta: Any) -> str | None: + if not _has_more(meta): + return None + + next_cursor = getattr(meta, "next_cursor", None) + if next_cursor is UNSET or next_cursor == "": + return None + return next_cursor + + +class PaginatedList(list[T], Generic[T]): + """A single cursor-paginated page returned by a list endpoint.""" + + def __init__( + self, + data: list[T] | None = None, + *, + meta: Any = UNSET, + fetch_next: Callable[[str], "PaginatedList[T]"] | None = None, + ): + super().__init__(data or []) + self.meta = meta + self._fetch_next = fetch_next + + @property + def data(self) -> list[T]: + return self + + @property + def has_more(self) -> bool: + return _has_more(self.meta) + + @property + def next_cursor(self) -> str | None: + return _next_cursor(self.meta) + + @property + def is_empty(self) -> bool: + return len(self) == 0 + + def next_page(self) -> "PaginatedList[T]": + if not self.next_cursor or self._fetch_next is None: + return PaginatedList([]) + return self._fetch_next(self.next_cursor) + + def auto_paging_iter(self) -> Iterator[T]: + page: PaginatedList[T] = self + while True: + yield from page + if not page.has_more: + break + page = page.next_page() + if page.is_empty: + break + + +class AsyncPaginatedList(list[T], Generic[T]): + """A single cursor-paginated page returned by an async list endpoint.""" + + def __init__( + self, + data: list[T] | None = None, + *, + meta: Any = UNSET, + fetch_next: Callable[[str], Awaitable["AsyncPaginatedList[T]"]] | None = None, + ): + super().__init__(data or []) + self.meta = meta + self._fetch_next = fetch_next + + @property + def data(self) -> list[T]: + return self + + @property + def has_more(self) -> bool: + return _has_more(self.meta) + + @property + def next_cursor(self) -> str | None: + return _next_cursor(self.meta) + + @property + def is_empty(self) -> bool: + return len(self) == 0 + + async def next_page(self) -> "AsyncPaginatedList[T]": + if not self.next_cursor or self._fetch_next is None: + return AsyncPaginatedList([]) + return await self._fetch_next(self.next_cursor) + + async def auto_paging_iter(self) -> AsyncIterator[T]: + page: AsyncPaginatedList[T] = self + while True: + for item in page: + yield item + if not page.has_more: + break + page = await page.next_page() + if page.is_empty: + break + + +def make_paginated_list( + page: RawPage, + *, + mapper: Callable[[Any], T], + fetch_next: Callable[[str], PaginatedList[T]] | None = None, +) -> PaginatedList[T]: + return PaginatedList( + [mapper(item) for item in get_page_data(page)], + meta=get_page_meta(page), + fetch_next=fetch_next, + ) + + +def make_async_paginated_list( + page: RawPage, + *, + mapper: Callable[[Any], T], + fetch_next: Callable[[str], Awaitable[AsyncPaginatedList[T]]] | None = None, +) -> AsyncPaginatedList[T]: + return AsyncPaginatedList( + [mapper(item) for item in get_page_data(page)], + meta=get_page_meta(page), + fetch_next=fetch_next, + ) + + +def normalize_cursor(cursor: str | None) -> str | Unset: + return cursor if cursor is not None else UNSET diff --git a/src/blaxel/core/common/settings.py b/src/blaxel/core/common/settings.py index c745238f..6926d89d 100644 --- a/src/blaxel/core/common/settings.py +++ b/src/blaxel/core/common/settings.py @@ -8,7 +8,7 @@ from ..authentication import BlaxelAuth, auth from .logger import init_logger -BLAXEL_API_VERSION = "2026-04-16" +BLAXEL_API_VERSION = "2026-04-28" def _get_os_arch() -> str: diff --git a/src/blaxel/core/drive/drive.py b/src/blaxel/core/drive/drive.py index f9cb0f9f..5bc52b49 100644 --- a/src/blaxel/core/drive/drive.py +++ b/src/blaxel/core/drive/drive.py @@ -2,7 +2,7 @@ import time import uuid import warnings -from typing import Callable, Dict, List, Union +from typing import Callable, Dict, Union from ..client.api.drives.create_drive import asyncio as create_drive from ..client.api.drives.create_drive import sync as create_drive_sync @@ -18,6 +18,13 @@ from ..client.errors import UnexpectedStatus from ..client.models import Drive, DriveSpec, Metadata from ..client.models.error import Error +from ..client.pagination import ( + AsyncPaginatedList, + PaginatedList, + make_async_paginated_list, + make_paginated_list, + normalize_cursor, +) from ..client.types import UNSET from ..common.settings import settings @@ -247,9 +254,43 @@ async def get(cls, drive_name: str) -> "DriveInstance": return cls(response) @classmethod - async def list(cls) -> list["DriveInstance"]: - response = await list_drives(client=client) - return [cls(drive) for drive in response or []] + async def list( + cls, limit: int = 50, cursor: str | None = None + ) -> AsyncPaginatedList["DriveInstance"]: + """List one page of drives. + + Args: + limit: Maximum number of drives to return in this page. + cursor: Cursor from a previous page. Leave unset for the first page. + + Returns: + AsyncPaginatedList[DriveInstance]: A list-like page with `.data`, `.meta`, + `.has_more`, `.next_cursor`, `.next_page()`, and `.auto_paging_iter()`. + + Example: + ```python + page = await DriveInstance.list(limit=50) + + for drive in page.data: + print(drive.name) + + if page.has_more: + next_page = await page.next_page() + + async for drive in page.auto_paging_iter(): + print(drive.name) + ``` + """ + + async def fetch_page(page_cursor: str | None): + response = await list_drives( + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + ) + return make_async_paginated_list(response, mapper=cls, fetch_next=fetch_page) + + return await fetch_page(cursor) @classmethod async def create_if_not_exists( @@ -398,10 +439,41 @@ def get(cls, drive_name: str) -> "SyncDriveInstance": return cls(response) @classmethod - def list(cls) -> List["SyncDriveInstance"]: - """List all drives synchronously.""" - response = list_drives_sync(client=client) - return [cls(drive) for drive in response or []] + def list(cls, limit: int = 50, cursor: str | None = None) -> PaginatedList["SyncDriveInstance"]: + """List one page of drives synchronously. + + Args: + limit: Maximum number of drives to return in this page. + cursor: Cursor from a previous page. Leave unset for the first page. + + Returns: + PaginatedList[SyncDriveInstance]: A list-like page with `.data`, `.meta`, + `.has_more`, `.next_cursor`, `.next_page()`, and `.auto_paging_iter()`. + + Example: + ```python + page = SyncDriveInstance.list(limit=50) + + for drive in page.data: + print(drive.name) + + if page.has_more: + next_page = page.next_page() + + for drive in page.auto_paging_iter(): + print(drive.name) + ``` + """ + + def fetch_page(page_cursor: str | None): + response = list_drives_sync( + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + ) + return make_paginated_list(response, mapper=cls, fetch_next=fetch_page) + + return fetch_page(cursor) @classmethod def create_if_not_exists( diff --git a/src/blaxel/core/jobs/__init__.py b/src/blaxel/core/jobs/__init__.py index 14ecb603..b044ea2f 100644 --- a/src/blaxel/core/jobs/__init__.py +++ b/src/blaxel/core/jobs/__init__.py @@ -18,6 +18,13 @@ CreateJobExecutionRequest, ) from ..client.models.job_execution import JobExecution +from ..client.pagination import ( + AsyncPaginatedList, + PaginatedList, + make_async_paginated_list, + make_paginated_list, + normalize_cursor, +) class BlJobWrapper: @@ -285,55 +292,99 @@ async def aget_execution(self, execution_id: str) -> JobExecution: return response.parsed - def list_executions(self, limit: int = 20, offset: int = 0) -> List[JobExecution]: + def list_executions( + self, limit: int = 20, offset: int = 0, cursor: str | None = None + ) -> PaginatedList[JobExecution]: """ - List all executions for this job. + List executions for this job. Args: - limit: Maximum number of executions to return - offset: Offset for pagination + limit: Maximum number of executions to return per page + offset: Legacy offset for older API versions + cursor: Starting cursor Returns: - List[JobExecution]: List of execution objects - """ - logger.debug(f"Listing executions for job: {self.name}") + PaginatedList[JobExecution]: Page of execution objects - response = list_job_executions.sync_detailed( - job_id=self.name, - client=client, - limit=limit, - offset=offset, - ) + Example: + ```python + page = bl_job("my-job").list_executions(limit=20) - if response.status_code != 200: - raise Exception(f"Failed to list job executions: {response.status_code}") + for execution in page.data: + print(execution.metadata.id) + + if page.has_more: + next_page = page.next_page() - return response.parsed or [] + for execution in page.auto_paging_iter(): + print(execution.metadata.id) + ``` + """ + logger.debug(f"Listing executions for job: {self.name}") - async def alist_executions(self, limit: int = 20, offset: int = 0) -> List[JobExecution]: + def fetch_page(page_cursor: str | None): + response = list_job_executions.sync_detailed( + job_id=self.name, + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + offset=offset, + ) + + if response.status_code != 200: + raise Exception(f"Failed to list job executions: {response.status_code}") + return make_paginated_list( + response.parsed, mapper=lambda execution: execution, fetch_next=fetch_page + ) + + return fetch_page(cursor) + + async def alist_executions( + self, limit: int = 20, offset: int = 0, cursor: str | None = None + ) -> AsyncPaginatedList[JobExecution]: """ - List all executions for this job (async). + List executions for this job (async). Args: - limit: Maximum number of executions to return - offset: Offset for pagination + limit: Maximum number of executions to return per page + offset: Legacy offset for older API versions + cursor: Starting cursor Returns: - List[JobExecution]: List of execution objects - """ - logger.debug(f"Listing executions for job: {self.name}") + AsyncPaginatedList[JobExecution]: Page of execution objects - response = await list_job_executions.asyncio_detailed( - job_id=self.name, - client=client, - limit=limit, - offset=offset, - ) + Example: + ```python + page = await bl_job("my-job").alist_executions(limit=20) - if response.status_code != 200: - raise Exception(f"Failed to list job executions: {response.status_code}") + for execution in page.data: + print(execution.metadata.id) + + if page.has_more: + next_page = await page.next_page() + + async for execution in page.auto_paging_iter(): + print(execution.metadata.id) + ``` + """ + logger.debug(f"Listing executions for job: {self.name}") - return response.parsed or [] + async def fetch_page(page_cursor: str | None): + response = await list_job_executions.asyncio_detailed( + job_id=self.name, + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + offset=offset, + ) + + if response.status_code != 200: + raise Exception(f"Failed to list job executions: {response.status_code}") + return make_async_paginated_list( + response.parsed, mapper=lambda execution: execution, fetch_next=fetch_page + ) + + return await fetch_page(cursor) def get_execution_status(self, execution_id: str) -> str: """ diff --git a/src/blaxel/core/sandbox/default/sandbox.py b/src/blaxel/core/sandbox/default/sandbox.py index 78ec28c9..962d5ba6 100644 --- a/src/blaxel/core/sandbox/default/sandbox.py +++ b/src/blaxel/core/sandbox/default/sandbox.py @@ -1,7 +1,7 @@ import logging import uuid import warnings -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Union if TYPE_CHECKING: import httpx @@ -26,6 +26,7 @@ ) from ...client.models.error import Error from ...client.models.sandbox_error import SandboxError +from ...client.pagination import AsyncPaginatedList, make_async_paginated_list, normalize_cursor from ...client.types import UNSET from ...common.settings import settings from ..types import ( @@ -323,9 +324,47 @@ async def get(cls, sandbox_name: str) -> "SandboxInstance": return cls(response) @classmethod - async def list(cls) -> List["SandboxInstance"]: - response = await list_sandboxes(client=client) - return [cls(sandbox) for sandbox in response] + async def list( + cls, limit: int = 50, cursor: str | None = None + ) -> AsyncPaginatedList["SandboxInstance"]: + """List one page of sandboxes. + + Args: + limit: Maximum number of sandboxes to return in this page. + cursor: Cursor from a previous page. Leave unset for the first page. + + Returns: + AsyncPaginatedList[SandboxInstance]: A list-like page with `.data`, `.meta`, + `.has_more`, `.next_cursor`, `.next_page()`, and `.auto_paging_iter()`. + + Example: + ```python + page = await SandboxInstance.list(limit=50) + + for sandbox in page.data: + print(sandbox.metadata.name) + + if page.has_more: + next_page = await page.next_page() + + async for sandbox in page.auto_paging_iter(): + print(sandbox.metadata.name) + ``` + """ + + async def fetch_page(page_cursor: str | None): + response = await list_sandboxes( + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + ) + if isinstance(response, Error): + status_code = response.code if response.code is not UNSET else None + message = response.message if response.message is not UNSET else response.error + raise SandboxAPIError(message, status_code=status_code, code=response.error) + return make_async_paginated_list(response, mapper=cls, fetch_next=fetch_page) + + return await fetch_page(cursor) @classmethod async def update_metadata( diff --git a/src/blaxel/core/sandbox/sync/sandbox.py b/src/blaxel/core/sandbox/sync/sandbox.py index 65280ce7..0c163bc4 100644 --- a/src/blaxel/core/sandbox/sync/sandbox.py +++ b/src/blaxel/core/sandbox/sync/sandbox.py @@ -1,7 +1,7 @@ import logging import uuid import warnings -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Union if TYPE_CHECKING: import httpx @@ -25,6 +25,7 @@ ) from ...client.models.error import Error from ...client.models.sandbox_error import SandboxError +from ...client.pagination import PaginatedList, make_paginated_list, normalize_cursor from ...client.types import UNSET from ...common.settings import settings from ..default.sandbox import SandboxAPIError @@ -287,9 +288,47 @@ def get(cls, sandbox_name: str) -> "SyncSandboxInstance": return cls(response) @classmethod - def list(cls) -> List["SyncSandboxInstance"]: - response = list_sandboxes(client=client) - return [cls(sandbox) for sandbox in response] + def list( + cls, limit: int = 50, cursor: str | None = None + ) -> PaginatedList["SyncSandboxInstance"]: + """List one page of sandboxes synchronously. + + Args: + limit: Maximum number of sandboxes to return in this page. + cursor: Cursor from a previous page. Leave unset for the first page. + + Returns: + PaginatedList[SyncSandboxInstance]: A list-like page with `.data`, `.meta`, + `.has_more`, `.next_cursor`, `.next_page()`, and `.auto_paging_iter()`. + + Example: + ```python + page = SyncSandboxInstance.list(limit=50) + + for sandbox in page.data: + print(sandbox.metadata.name) + + if page.has_more: + next_page = page.next_page() + + for sandbox in page.auto_paging_iter(): + print(sandbox.metadata.name) + ``` + """ + + def fetch_page(page_cursor: str | None): + response = list_sandboxes( + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + ) + if isinstance(response, Error): + status_code = response.code if response.code is not UNSET else None + message = response.message if response.message is not UNSET else response.error + raise SandboxAPIError(message, status_code=status_code, code=response.error) + return make_paginated_list(response, mapper=cls, fetch_next=fetch_page) + + return fetch_page(cursor) @classmethod def update_metadata( diff --git a/src/blaxel/core/volume/volume.py b/src/blaxel/core/volume/volume.py index 0ecfe5ca..0788a11b 100644 --- a/src/blaxel/core/volume/volume.py +++ b/src/blaxel/core/volume/volume.py @@ -2,7 +2,7 @@ import time import uuid import warnings -from typing import Callable, Dict, List, Union +from typing import Callable, Dict, Union from ..client.api.volumes.create_volume import asyncio as create_volume from ..client.api.volumes.create_volume import sync as create_volume_sync @@ -17,6 +17,13 @@ from ..client.client import client from ..client.models import Metadata, Volume, VolumeSpec from ..client.models.error import Error +from ..client.pagination import ( + AsyncPaginatedList, + PaginatedList, + make_async_paginated_list, + make_paginated_list, + normalize_cursor, +) from ..client.types import UNSET from ..common.settings import settings @@ -254,9 +261,43 @@ async def get(cls, volume_name: str) -> "VolumeInstance": return cls(response) @classmethod - async def list(cls) -> list["VolumeInstance"]: - response = await list_volumes(client=client) - return [cls(volume) for volume in response or []] + async def list( + cls, limit: int = 50, cursor: str | None = None + ) -> AsyncPaginatedList["VolumeInstance"]: + """List one page of volumes. + + Args: + limit: Maximum number of volumes to return in this page. + cursor: Cursor from a previous page. Leave unset for the first page. + + Returns: + AsyncPaginatedList[VolumeInstance]: A list-like page with `.data`, `.meta`, + `.has_more`, `.next_cursor`, `.next_page()`, and `.auto_paging_iter()`. + + Example: + ```python + page = await VolumeInstance.list(limit=50) + + for volume in page.data: + print(volume.name) + + if page.has_more: + next_page = await page.next_page() + + async for volume in page.auto_paging_iter(): + print(volume.name) + ``` + """ + + async def fetch_page(page_cursor: str | None): + response = await list_volumes( + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + ) + return make_async_paginated_list(response, mapper=cls, fetch_next=fetch_page) + + return await fetch_page(cursor) @classmethod async def create_if_not_exists( @@ -407,10 +448,43 @@ def get(cls, volume_name: str) -> "SyncVolumeInstance": return cls(response) @classmethod - def list(cls) -> List["SyncVolumeInstance"]: - """List all volumes synchronously.""" - response = list_volumes_sync(client=client) - return [cls(volume) for volume in response or []] + def list( + cls, limit: int = 50, cursor: str | None = None + ) -> PaginatedList["SyncVolumeInstance"]: + """List one page of volumes synchronously. + + Args: + limit: Maximum number of volumes to return in this page. + cursor: Cursor from a previous page. Leave unset for the first page. + + Returns: + PaginatedList[SyncVolumeInstance]: A list-like page with `.data`, `.meta`, + `.has_more`, `.next_cursor`, `.next_page()`, and `.auto_paging_iter()`. + + Example: + ```python + page = SyncVolumeInstance.list(limit=50) + + for volume in page.data: + print(volume.name) + + if page.has_more: + next_page = page.next_page() + + for volume in page.auto_paging_iter(): + print(volume.name) + ``` + """ + + def fetch_page(page_cursor: str | None): + response = list_volumes_sync( + client=client, + cursor=normalize_cursor(page_cursor), + limit=limit, + ) + return make_paginated_list(response, mapper=cls, fetch_next=fetch_page) + + return fetch_page(cursor) @classmethod def create_if_not_exists( diff --git a/tests/core/test_controlplane_pagination.py b/tests/core/test_controlplane_pagination.py new file mode 100644 index 00000000..f9cf12a8 --- /dev/null +++ b/tests/core/test_controlplane_pagination.py @@ -0,0 +1,160 @@ +from types import SimpleNamespace + +import pytest + +from blaxel.core.client.models import Drive, DriveSpec, Metadata, Sandbox, SandboxSpec +from blaxel.core.client.models.drive_list import DriveList +from blaxel.core.client.models.job_execution import JobExecution +from blaxel.core.client.models.job_execution_list import JobExecutionList +from blaxel.core.client.models.job_execution_metadata import JobExecutionMetadata +from blaxel.core.client.models.job_execution_spec import JobExecutionSpec +from blaxel.core.client.models.pagination_meta import PaginationMeta +from blaxel.core.client.models.sandbox_list import SandboxList +from blaxel.core.client.types import UNSET +from blaxel.core.drive.drive import SyncDriveInstance +from blaxel.core.jobs import bl_job +from blaxel.core.sandbox.default.sandbox import SandboxInstance + + +@pytest.mark.asyncio +async def test_sandbox_list_returns_page_with_next_page(monkeypatch): + pages = [ + SandboxList( + data=[Sandbox(metadata=Metadata(name="sandbox-a"), spec=SandboxSpec())], + meta=PaginationMeta(has_more=True, next_cursor="cursor-2"), + ), + SandboxList( + data=[Sandbox(metadata=Metadata(name="sandbox-b"), spec=SandboxSpec())], + meta=PaginationMeta(has_more=False), + ), + ] + calls = [] + + async def fake_list_sandboxes(*, client, cursor=UNSET, limit=50): + calls.append((cursor, limit)) + return pages.pop(0) + + monkeypatch.setattr("blaxel.core.sandbox.default.sandbox.list_sandboxes", fake_list_sandboxes) + + page = await SandboxInstance.list(limit=1) + next_page = await page.next_page() + + assert [sandbox.metadata.name for sandbox in page.data] == ["sandbox-a"] + assert page.has_more is True + assert page.next_cursor == "cursor-2" + assert [sandbox.metadata.name for sandbox in next_page.data] == ["sandbox-b"] + assert next_page.has_more is False + assert calls == [(UNSET, 1), ("cursor-2", 1)] + + +def test_drive_list_returns_page_with_next_page(monkeypatch): + pages = [ + DriveList( + data=[Drive(metadata=Metadata(name="drive-a"), spec=DriveSpec())], + meta=PaginationMeta(has_more=True, next_cursor="cursor-2"), + ), + DriveList( + data=[Drive(metadata=Metadata(name="drive-b"), spec=DriveSpec())], + meta=PaginationMeta(has_more=False), + ), + ] + calls = [] + + def fake_list_drives(*, client, cursor=UNSET, limit=50): + calls.append((cursor, limit)) + return pages.pop(0) + + monkeypatch.setattr("blaxel.core.drive.drive.list_drives_sync", fake_list_drives) + + page = SyncDriveInstance.list(limit=1) + next_page = page.next_page() + + assert [drive.name for drive in page.data] == ["drive-a"] + assert page.has_more is True + assert page.next_cursor == "cursor-2" + assert [drive.name for drive in next_page.data] == ["drive-b"] + assert next_page.has_more is False + assert calls == [(UNSET, 1), ("cursor-2", 1)] + + +def test_job_execution_list_supports_explicit_next_page(monkeypatch): + first_execution = JobExecution( + metadata=JobExecutionMetadata(id="execution-a"), + spec=JobExecutionSpec(), + ) + second_execution = JobExecution( + metadata=JobExecutionMetadata(id="execution-b"), + spec=JobExecutionSpec(), + ) + pages = [ + JobExecutionList( + data=[first_execution], + meta=PaginationMeta(has_more=True, next_cursor="cursor-2"), + ), + JobExecutionList( + data=[second_execution], + meta=PaginationMeta(has_more=False), + ), + ] + calls = [] + + def fake_list_job_executions(**kwargs): + calls.append(kwargs) + return SimpleNamespace( + status_code=200, + parsed=pages.pop(0), + ) + + monkeypatch.setattr( + "blaxel.core.jobs.list_job_executions.sync_detailed", + fake_list_job_executions, + ) + + executions = bl_job("job-a").list_executions(limit=10, cursor="cursor-1") + next_executions = executions.next_page() + + assert executions.data == [first_execution] + assert executions.has_more is True + assert executions.next_cursor == "cursor-2" + assert next_executions.data == [second_execution] + assert next_executions.has_more is False + assert calls[0]["job_id"] == "job-a" + assert calls[0]["limit"] == 10 + assert calls[0]["cursor"] == "cursor-1" + assert calls[1]["cursor"] == "cursor-2" + + +def test_job_execution_auto_paging_iter_is_explicit(monkeypatch): + first_execution = JobExecution( + metadata=JobExecutionMetadata(id="execution-a"), + spec=JobExecutionSpec(), + ) + second_execution = JobExecution( + metadata=JobExecutionMetadata(id="execution-b"), + spec=JobExecutionSpec(), + ) + pages = [ + JobExecutionList( + data=[first_execution], + meta=PaginationMeta(has_more=True, next_cursor="cursor-2"), + ), + JobExecutionList( + data=[second_execution], + meta=PaginationMeta(has_more=False), + ), + ] + + def fake_list_job_executions(**kwargs): + return SimpleNamespace( + status_code=200, + parsed=pages.pop(0), + ) + + monkeypatch.setattr( + "blaxel.core.jobs.list_job_executions.sync_detailed", + fake_list_job_executions, + ) + + executions = list(bl_job("job-a").list_executions(limit=10).auto_paging_iter()) + + assert executions == [first_execution, second_execution] diff --git a/tests/core/test_settings_api_version.py b/tests/core/test_settings_api_version.py index b75c8a46..d759520c 100644 --- a/tests/core/test_settings_api_version.py +++ b/tests/core/test_settings_api_version.py @@ -1,4 +1,5 @@ """Tests for the Blaxel-Version header in Settings.""" + import os from blaxel.core.common.settings import BLAXEL_API_VERSION, settings @@ -8,7 +9,7 @@ def test_default_api_version(): """Blaxel-Version defaults to the module constant when BL_API_VERSION is unset.""" os.environ.pop("BL_API_VERSION", None) assert settings.api_version == BLAXEL_API_VERSION - assert settings.api_version == "2026-04-16" + assert settings.api_version == "2026-04-28" def test_env_override_api_version(): From 57f03f77ff99ec933666b20fce5ea9101460c2c1 Mon Sep 17 00:00:00 2001 From: cploujoux Date: Tue, 19 May 2026 10:37:26 -0700 Subject: [PATCH 2/2] fix: accept legacy list responses for pagination models --- src/blaxel/core/client/models/agent_list.py | 10 +++-- src/blaxel/core/client/models/drive_list.py | 10 +++-- .../core/client/models/function_list.py | 10 +++-- .../core/client/models/job_execution_list.py | 10 +++-- .../client/models/job_execution_task_list.py | 10 +++-- src/blaxel/core/client/models/job_list.py | 10 +++-- src/blaxel/core/client/models/model_list.py | 10 +++-- src/blaxel/core/client/models/policy_list.py | 10 +++-- src/blaxel/core/client/models/sandbox_list.py | 10 +++-- src/blaxel/core/client/models/volume_list.py | 10 +++-- src/blaxel/core/client/pagination.py | 12 +++++ templates/model.py.jinja | 37 +++++++++++++-- tests/core/test_controlplane_pagination.py | 45 +++++++++++++++++++ 13 files changed, 150 insertions(+), 44 deletions(-) diff --git a/src/blaxel/core/client/models/agent_list.py b/src/blaxel/core/client/models/agent_list.py index 35bd5a8b..a3ba4501 100644 --- a/src/blaxel/core/client/models/agent_list.py +++ b/src/blaxel/core/client/models/agent_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.agent import Agent from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - agent_list.additional_properties = d + agent_list.additional_properties = additional_properties return agent_list @property diff --git a/src/blaxel/core/client/models/drive_list.py b/src/blaxel/core/client/models/drive_list.py index cea6fa5c..0ec0cc0c 100644 --- a/src/blaxel/core/client/models/drive_list.py +++ b/src/blaxel/core/client/models/drive_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.drive import Drive from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - drive_list.additional_properties = d + drive_list.additional_properties = additional_properties return drive_list @property diff --git a/src/blaxel/core/client/models/function_list.py b/src/blaxel/core/client/models/function_list.py index aa2928d0..c26d2232 100644 --- a/src/blaxel/core/client/models/function_list.py +++ b/src/blaxel/core/client/models/function_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.function import Function from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - function_list.additional_properties = d + function_list.additional_properties = additional_properties return function_list @property diff --git a/src/blaxel/core/client/models/job_execution_list.py b/src/blaxel/core/client/models/job_execution_list.py index d8ceb625..37b59dec 100644 --- a/src/blaxel/core/client/models/job_execution_list.py +++ b/src/blaxel/core/client/models/job_execution_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.job_execution import JobExecution from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - job_execution_list.additional_properties = d + job_execution_list.additional_properties = additional_properties return job_execution_list @property diff --git a/src/blaxel/core/client/models/job_execution_task_list.py b/src/blaxel/core/client/models/job_execution_task_list.py index bfbdb71f..00417091 100644 --- a/src/blaxel/core/client/models/job_execution_task_list.py +++ b/src/blaxel/core/client/models/job_execution_task_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.job_execution_task import JobExecutionTask from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - job_execution_task_list.additional_properties = d + job_execution_task_list.additional_properties = additional_properties return job_execution_task_list @property diff --git a/src/blaxel/core/client/models/job_list.py b/src/blaxel/core/client/models/job_list.py index d74d73d6..bc1c1636 100644 --- a/src/blaxel/core/client/models/job_list.py +++ b/src/blaxel/core/client/models/job_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.job import Job from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - job_list.additional_properties = d + job_list.additional_properties = additional_properties return job_list @property diff --git a/src/blaxel/core/client/models/model_list.py b/src/blaxel/core/client/models/model_list.py index ebaea83e..bee5eee3 100644 --- a/src/blaxel/core/client/models/model_list.py +++ b/src/blaxel/core/client/models/model_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.model import Model from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - model_list.additional_properties = d + model_list.additional_properties = additional_properties return model_list @property diff --git a/src/blaxel/core/client/models/policy_list.py b/src/blaxel/core/client/models/policy_list.py index 1f24b2a2..d95f6bb2 100644 --- a/src/blaxel/core/client/models/policy_list.py +++ b/src/blaxel/core/client/models/policy_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.pagination_meta import PaginationMeta from ..models.policy import Policy - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - policy_list.additional_properties = d + policy_list.additional_properties = additional_properties return policy_list @property diff --git a/src/blaxel/core/client/models/sandbox_list.py b/src/blaxel/core/client/models/sandbox_list.py index d97390ca..f3fc9686 100644 --- a/src/blaxel/core/client/models/sandbox_list.py +++ b/src/blaxel/core/client/models/sandbox_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -58,13 +59,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.pagination_meta import PaginationMeta from ..models.sandbox import Sandbox - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -84,7 +86,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - sandbox_list.additional_properties = d + sandbox_list.additional_properties = additional_properties return sandbox_list @property diff --git a/src/blaxel/core/client/models/volume_list.py b/src/blaxel/core/client/models/volume_list.py index 2025d792..10d66277 100644 --- a/src/blaxel/core/client/models/volume_list.py +++ b/src/blaxel/core/client/models/volume_list.py @@ -3,6 +3,7 @@ from attrs import define as _attrs_define from attrs import field as _attrs_field +from ..pagination import split_list_response from ..types import UNSET, Unset if TYPE_CHECKING: @@ -57,13 +58,14 @@ def to_dict(self) -> dict[str, Any]: return field_dict @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any] | list[Any]) -> T | None: from ..models.lite_volume import LiteVolume from ..models.pagination_meta import PaginationMeta - if not src_dict: + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): return None - d = src_dict.copy() + d = {"data": _data, "meta": _meta} data = [] _data = d.pop("data", UNSET) for data_item_data in _data or []: @@ -83,7 +85,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: meta=meta, ) - volume_list.additional_properties = d + volume_list.additional_properties = additional_properties return volume_list @property diff --git a/src/blaxel/core/client/pagination.py b/src/blaxel/core/client/pagination.py index c749b4f1..f742b49d 100644 --- a/src/blaxel/core/client/pagination.py +++ b/src/blaxel/core/client/pagination.py @@ -9,6 +9,18 @@ DEFAULT_PAGE_LIMIT = 50 +def split_list_response(src: Any) -> tuple[list[Any], Any, dict[str, Any]]: + if not src: + return [], UNSET, {} + if isinstance(src, list): + return src, UNSET, {} + + data = src.copy() + items = data.pop("data", UNSET) + meta = data.pop("meta", UNSET) + return list(items or []), meta, data + + def get_page_data(page: Any) -> list[Any]: if page is None: return [] diff --git a/templates/model.py.jinja b/templates/model.py.jinja index 120cf564..38b636db 100644 --- a/templates/model.py.jinja +++ b/templates/model.py.jinja @@ -1,3 +1,21 @@ +{% set class_name = model.class_info.name %} +{% set module_name = model.class_info.module_name %} +{% set paginated_list = namespace(value=false) %} +{% set has_data = namespace(value=false) %} +{% set has_meta = namespace(value=false) %} +{% if class_name.endswith("List") %} +{% for property in model.required_properties + model.optional_properties %} +{% if property.python_name == "data" %} +{% set has_data.value = true %} +{% endif %} +{% if property.python_name == "meta" %} +{% set has_meta.value = true %} +{% endif %} +{% endfor %} +{% if has_data.value and has_meta.value %} +{% set paginated_list.value = true %} +{% endif %} +{% endif %} from typing import Any, TypeVar, Optional, BinaryIO, TextIO, TYPE_CHECKING from attrs import define as _attrs_define @@ -6,6 +24,9 @@ from attrs import field as _attrs_field import json {% endif %} +{% if paginated_list.value %} +from ..pagination import split_list_response +{% endif %} from ..types import UNSET, Unset {% for relative in model.relative_imports | sort %} @@ -24,9 +45,6 @@ if TYPE_CHECKING: {% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string(quoted=not model.additional_properties.is_base_type) %} {% endif %} -{% set class_name = model.class_info.name %} -{% set module_name = model.class_info.module_name %} - {% from "helpers.jinja" import safe_docstring %} T = TypeVar("T", bound="{{ class_name }}") @@ -129,14 +147,21 @@ return field_dict {% endif %} @classmethod - def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T | None: + def from_dict(cls: type[T], src_dict: dict[str, Any]{% if paginated_list.value %} | list[Any]{% endif %}) -> T | None: {% for lazy_import in model.lazy_imports %} {{ lazy_import }} {% endfor %} {% if (model.required_properties or model.optional_properties or model.additional_properties) %} +{% if paginated_list.value %} + _data, _meta, additional_properties = split_list_response(src_dict) + if not _data and not additional_properties and isinstance(_meta, Unset): + return None + d = {"data": _data, "meta": _meta} +{% else %} if not src_dict: return None d = src_dict.copy() +{% endif %} {% for property in model.required_properties + model.optional_properties %} {% if property.required %} {% if property.name != property.python_name %} @@ -186,7 +211,11 @@ return field_dict {{ module_name }}.additional_properties = additional_properties {% else %} +{% if paginated_list.value %} + {{ module_name }}.additional_properties = additional_properties +{% else %} {{ module_name }}.additional_properties = d +{% endif %} {% endif %} {% endif %} return {{ module_name }} diff --git a/tests/core/test_controlplane_pagination.py b/tests/core/test_controlplane_pagination.py index f9cf12a8..691fd142 100644 --- a/tests/core/test_controlplane_pagination.py +++ b/tests/core/test_controlplane_pagination.py @@ -8,8 +8,12 @@ from blaxel.core.client.models.job_execution_list import JobExecutionList from blaxel.core.client.models.job_execution_metadata import JobExecutionMetadata from blaxel.core.client.models.job_execution_spec import JobExecutionSpec +from blaxel.core.client.models.lite_volume import LiteVolume +from blaxel.core.client.models.lite_volume_metadata import LiteVolumeMetadata +from blaxel.core.client.models.lite_volume_spec import LiteVolumeSpec from blaxel.core.client.models.pagination_meta import PaginationMeta from blaxel.core.client.models.sandbox_list import SandboxList +from blaxel.core.client.models.volume_list import VolumeList from blaxel.core.client.types import UNSET from blaxel.core.drive.drive import SyncDriveInstance from blaxel.core.jobs import bl_job @@ -77,6 +81,47 @@ def fake_list_drives(*, client, cursor=UNSET, limit=50): assert calls == [(UNSET, 1), ("cursor-2", 1)] +def test_generated_list_models_accept_legacy_array_responses(): + sandbox_page = SandboxList.from_dict( + [Sandbox(metadata=Metadata(name="sandbox-a"), spec=SandboxSpec()).to_dict()] + ) + drive_page = DriveList.from_dict( + [Drive(metadata=Metadata(name="drive-a"), spec=DriveSpec()).to_dict()] + ) + volume_page = VolumeList.from_dict( + [ + LiteVolume( + metadata=LiteVolumeMetadata(name="volume-a"), + spec=LiteVolumeSpec(size=10), + ).to_dict() + ] + ) + execution_page = JobExecutionList.from_dict( + [ + JobExecution( + metadata=JobExecutionMetadata(id="execution-a"), + spec=JobExecutionSpec(), + ).to_dict() + ] + ) + + assert sandbox_page is not None + assert sandbox_page.meta is UNSET + assert [sandbox.metadata.name for sandbox in sandbox_page.data] == ["sandbox-a"] + + assert drive_page is not None + assert drive_page.meta is UNSET + assert [drive.metadata.name for drive in drive_page.data] == ["drive-a"] + + assert volume_page is not None + assert volume_page.meta is UNSET + assert [volume.metadata.name for volume in volume_page.data] == ["volume-a"] + + assert execution_page is not None + assert execution_page.meta is UNSET + assert [execution.metadata.id for execution in execution_page.data] == ["execution-a"] + + def test_job_execution_list_supports_explicit_next_page(monkeypatch): first_execution = JobExecution( metadata=JobExecutionMetadata(id="execution-a"),