Skip to content

Commit 24b5387

Browse files
committed
list_scenario_runs() now returns a list of ScenarioRun/AsyncScenarioRun objects
1 parent 3dbc3ab commit 24b5387

6 files changed

Lines changed: 121 additions & 58 deletions

File tree

src/runloop_api_client/sdk/async_benchmark_run.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from typing import List
66
from typing_extensions import Unpack, override
77

8-
from ..types import ScenarioRunView, BenchmarkRunView
8+
from ..types import BenchmarkRunView
99
from ._types import BaseRequestOptions, LongRequestOptions, SDKBenchmarkRunListScenarioRunsParams
1010
from .._client import AsyncRunloop
11+
from .async_scenario_run import AsyncScenarioRun
1112

1213

1314
class AsyncBenchmarkRun:
@@ -112,15 +113,15 @@ async def complete(
112113
async def list_scenario_runs(
113114
self,
114115
**params: Unpack[SDKBenchmarkRunListScenarioRunsParams],
115-
) -> List[ScenarioRunView]:
116+
) -> List[AsyncScenarioRun]:
116117
"""List all scenario runs for this benchmark run.
117118
118119
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkRunListScenarioRunsParams` for available parameters
119-
:return: List of scenario run views
120-
:rtype: List[ScenarioRunView]
120+
:return: List of async scenario run objects
121+
:rtype: List[AsyncScenarioRun]
121122
"""
122-
page = self._client.benchmarks.runs.list_scenario_runs(
123+
page = await self._client.benchmarks.runs.list_scenario_runs(
123124
self._id,
124125
**params,
125126
)
126-
return [item async for item in page]
127+
return [AsyncScenarioRun(self._client, run.id, run.devbox_id) for run in page.runs]

src/runloop_api_client/sdk/benchmark_run.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from typing import List
66
from typing_extensions import Unpack, override
77

8-
from ..types import ScenarioRunView, BenchmarkRunView
8+
from ..types import BenchmarkRunView
99
from ._types import BaseRequestOptions, LongRequestOptions, SDKBenchmarkRunListScenarioRunsParams
1010
from .._client import Runloop
11+
from .scenario_run import ScenarioRun
1112

1213

1314
class BenchmarkRun:
@@ -112,15 +113,15 @@ def complete(
112113
def list_scenario_runs(
113114
self,
114115
**params: Unpack[SDKBenchmarkRunListScenarioRunsParams],
115-
) -> List[ScenarioRunView]:
116+
) -> List[ScenarioRun]:
116117
"""List all scenario runs for this benchmark run.
117118
118119
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBenchmarkRunListScenarioRunsParams` for available parameters
119-
:return: List of scenario run views
120-
:rtype: List[ScenarioRunView]
120+
:return: List of scenario run objects
121+
:rtype: List[ScenarioRun]
121122
"""
122123
page = self._client.benchmarks.runs.list_scenario_runs(
123124
self._id,
124125
**params,
125126
)
126-
return list(page)
127+
return [ScenarioRun(self._client, run.id, run.devbox_id) for run in page.runs]

tests/sdk/test_async_benchmark_run.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from __future__ import annotations
44

5+
from types import SimpleNamespace
56
from unittest.mock import AsyncMock
67

7-
from tests.sdk.conftest import AsyncIterableMock, MockScenarioRunView, MockBenchmarkRunView
8+
from tests.sdk.conftest import MockScenarioRunView, MockBenchmarkRunView
9+
from runloop_api_client.sdk.async_scenario_run import AsyncScenarioRun
810
from runloop_api_client.sdk.async_benchmark_run import AsyncBenchmarkRun
911

1012

@@ -56,39 +58,63 @@ async def test_complete(self, mock_async_client: AsyncMock, benchmark_run_view:
5658
assert result.state == "completed"
5759
mock_async_client.benchmarks.runs.complete.assert_awaited_once_with("bench_run_123")
5860

59-
async def test_list_scenario_runs(
61+
async def test_list_scenario_runs_empty(self, mock_async_client: AsyncMock) -> None:
62+
"""Test list_scenario_runs method with empty results."""
63+
page = SimpleNamespace(runs=[])
64+
mock_async_client.benchmarks.runs.list_scenario_runs = AsyncMock(return_value=page)
65+
66+
run = AsyncBenchmarkRun(mock_async_client, "bench_run_123", "bench_123")
67+
result = await run.list_scenario_runs()
68+
69+
assert len(result) == 0
70+
mock_async_client.benchmarks.runs.list_scenario_runs.assert_awaited_once_with("bench_run_123")
71+
72+
async def test_list_scenario_runs_single(
6073
self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView
6174
) -> None:
62-
"""Test list_scenario_runs method."""
63-
mock_async_client.benchmarks.runs.list_scenario_runs.return_value = AsyncIterableMock([scenario_run_view])
75+
"""Test list_scenario_runs method with single result."""
76+
page = SimpleNamespace(runs=[scenario_run_view])
77+
mock_async_client.benchmarks.runs.list_scenario_runs = AsyncMock(return_value=page)
6478

6579
run = AsyncBenchmarkRun(mock_async_client, "bench_run_123", "bench_123")
6680
result = await run.list_scenario_runs()
6781

6882
assert len(result) == 1
69-
assert result[0] == scenario_run_view
70-
mock_async_client.benchmarks.runs.list_scenario_runs.assert_called_once_with("bench_run_123")
83+
assert isinstance(result[0], AsyncScenarioRun)
84+
assert result[0].id == scenario_run_view.id
85+
assert result[0].devbox_id == scenario_run_view.devbox_id
86+
mock_async_client.benchmarks.runs.list_scenario_runs.assert_awaited_once_with("bench_run_123")
87+
88+
async def test_list_scenario_runs_multiple(self, mock_async_client: AsyncMock) -> None:
89+
"""Test list_scenario_runs method with multiple results."""
90+
scenario_run_view1 = MockScenarioRunView(id="run_001", devbox_id="dev_001")
91+
scenario_run_view2 = MockScenarioRunView(id="run_002", devbox_id="dev_002")
92+
page = SimpleNamespace(runs=[scenario_run_view1, scenario_run_view2])
93+
mock_async_client.benchmarks.runs.list_scenario_runs = AsyncMock(return_value=page)
94+
95+
run = AsyncBenchmarkRun(mock_async_client, "bench_run_123", "bench_123")
96+
result = await run.list_scenario_runs()
97+
98+
assert len(result) == 2
99+
assert isinstance(result[0], AsyncScenarioRun)
100+
assert isinstance(result[1], AsyncScenarioRun)
101+
assert result[0].id == "run_001"
102+
assert result[1].id == "run_002"
103+
mock_async_client.benchmarks.runs.list_scenario_runs.assert_awaited_once_with("bench_run_123")
71104

72105
async def test_list_scenario_runs_with_params(
73106
self, mock_async_client: AsyncMock, scenario_run_view: MockScenarioRunView
74107
) -> None:
75108
"""Test list_scenario_runs method with filtering parameters."""
76-
mock_async_client.benchmarks.runs.list_scenario_runs.return_value = AsyncIterableMock([scenario_run_view])
109+
page = SimpleNamespace(runs=[scenario_run_view])
110+
mock_async_client.benchmarks.runs.list_scenario_runs = AsyncMock(return_value=page)
77111

78112
run = AsyncBenchmarkRun(mock_async_client, "bench_run_123", "bench_123")
79113
result = await run.list_scenario_runs(limit=10, state="completed")
80114

81115
assert len(result) == 1
82-
mock_async_client.benchmarks.runs.list_scenario_runs.assert_called_once_with(
116+
assert isinstance(result[0], AsyncScenarioRun)
117+
assert result[0].id == scenario_run_view.id
118+
mock_async_client.benchmarks.runs.list_scenario_runs.assert_awaited_once_with(
83119
"bench_run_123", limit=10, state="completed"
84120
)
85-
86-
async def test_list_scenario_runs_empty(self, mock_async_client: AsyncMock) -> None:
87-
"""Test list_scenario_runs returns empty list when no scenario runs."""
88-
mock_async_client.benchmarks.runs.list_scenario_runs.return_value = AsyncIterableMock([])
89-
90-
run = AsyncBenchmarkRun(mock_async_client, "bench_run_123", "bench_123")
91-
result = await run.list_scenario_runs()
92-
93-
assert result == []
94-
mock_async_client.benchmarks.runs.list_scenario_runs.assert_called_once_with("bench_run_123")

tests/sdk/test_benchmark_run.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from __future__ import annotations
44

5+
from types import SimpleNamespace
56
from unittest.mock import Mock
67

78
from tests.sdk.conftest import MockScenarioRunView, MockBenchmarkRunView
9+
from runloop_api_client.sdk.scenario_run import ScenarioRun
810
from runloop_api_client.sdk.benchmark_run import BenchmarkRun
911

1012

@@ -56,37 +58,59 @@ def test_complete(self, mock_client: Mock, benchmark_run_view: MockBenchmarkRunV
5658
assert result.state == "completed"
5759
mock_client.benchmarks.runs.complete.assert_called_once_with("bench_run_123")
5860

59-
def test_list_scenario_runs(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
60-
"""Test list_scenario_runs method."""
61-
mock_page = [scenario_run_view]
62-
mock_client.benchmarks.runs.list_scenario_runs.return_value = mock_page
61+
def test_list_scenario_runs_empty(self, mock_client: Mock) -> None:
62+
"""Test list_scenario_runs method with empty results."""
63+
page = SimpleNamespace(runs=[])
64+
mock_client.benchmarks.runs.list_scenario_runs.return_value = page
65+
66+
run = BenchmarkRun(mock_client, "bench_run_123", "bench_123")
67+
result = run.list_scenario_runs()
68+
69+
assert len(result) == 0
70+
mock_client.benchmarks.runs.list_scenario_runs.assert_called_once_with("bench_run_123")
71+
72+
def test_list_scenario_runs_single(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
73+
"""Test list_scenario_runs method with single result."""
74+
page = SimpleNamespace(runs=[scenario_run_view])
75+
mock_client.benchmarks.runs.list_scenario_runs.return_value = page
6376

6477
run = BenchmarkRun(mock_client, "bench_run_123", "bench_123")
6578
result = run.list_scenario_runs()
6679

6780
assert len(result) == 1
68-
assert result[0] == scenario_run_view
81+
assert isinstance(result[0], ScenarioRun)
82+
assert result[0].id == scenario_run_view.id
83+
assert result[0].devbox_id == scenario_run_view.devbox_id
84+
mock_client.benchmarks.runs.list_scenario_runs.assert_called_once_with("bench_run_123")
85+
86+
def test_list_scenario_runs_multiple(self, mock_client: Mock) -> None:
87+
"""Test list_scenario_runs method with multiple results."""
88+
scenario_run_view1 = MockScenarioRunView(id="run_001", devbox_id="dev_001")
89+
scenario_run_view2 = MockScenarioRunView(id="run_002", devbox_id="dev_002")
90+
page = SimpleNamespace(runs=[scenario_run_view1, scenario_run_view2])
91+
mock_client.benchmarks.runs.list_scenario_runs.return_value = page
92+
93+
run = BenchmarkRun(mock_client, "bench_run_123", "bench_123")
94+
result = run.list_scenario_runs()
95+
96+
assert len(result) == 2
97+
assert isinstance(result[0], ScenarioRun)
98+
assert isinstance(result[1], ScenarioRun)
99+
assert result[0].id == "run_001"
100+
assert result[1].id == "run_002"
69101
mock_client.benchmarks.runs.list_scenario_runs.assert_called_once_with("bench_run_123")
70102

71103
def test_list_scenario_runs_with_params(self, mock_client: Mock, scenario_run_view: MockScenarioRunView) -> None:
72104
"""Test list_scenario_runs method with filtering parameters."""
73-
mock_page = [scenario_run_view]
74-
mock_client.benchmarks.runs.list_scenario_runs.return_value = mock_page
105+
page = SimpleNamespace(runs=[scenario_run_view])
106+
mock_client.benchmarks.runs.list_scenario_runs.return_value = page
75107

76108
run = BenchmarkRun(mock_client, "bench_run_123", "bench_123")
77109
result = run.list_scenario_runs(limit=10, state="completed")
78110

79111
assert len(result) == 1
112+
assert isinstance(result[0], ScenarioRun)
113+
assert result[0].id == scenario_run_view.id
80114
mock_client.benchmarks.runs.list_scenario_runs.assert_called_once_with(
81115
"bench_run_123", limit=10, state="completed"
82116
)
83-
84-
def test_list_scenario_runs_empty(self, mock_client: Mock) -> None:
85-
"""Test list_scenario_runs returns empty list when no scenario runs."""
86-
mock_client.benchmarks.runs.list_scenario_runs.return_value = []
87-
88-
run = BenchmarkRun(mock_client, "bench_run_123", "bench_123")
89-
result = run.list_scenario_runs()
90-
91-
assert result == []
92-
mock_client.benchmarks.runs.list_scenario_runs.assert_called_once_with("bench_run_123")

tests/smoketests/sdk/test_async_benchmark_run.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest
1111

1212
from runloop_api_client.sdk import AsyncRunloopSDK
13+
from runloop_api_client.sdk.async_scenario_run import AsyncScenarioRun
1314
from runloop_api_client.sdk.async_benchmark_run import AsyncBenchmarkRun
1415

1516
pytestmark = [pytest.mark.smoketest]
@@ -30,8 +31,8 @@ async def test_benchmark_run_from_existing(self, async_sdk_client: AsyncRunloopS
3031
3. Validates get_info returns correct data
3132
"""
3233
# List existing benchmark runs via raw API
33-
runs_page = async_sdk_client.api.benchmarks.runs.list(limit=1)
34-
runs = [run async for run in runs_page]
34+
runs_page = await async_sdk_client.api.benchmarks.runs.list(limit=1)
35+
runs = runs_page.runs
3536

3637
if not runs:
3738
pytest.skip("No benchmark runs available to test")
@@ -62,8 +63,8 @@ async def test_benchmark_run_list_scenario_runs(self, async_sdk_client: AsyncRun
6263
2. Lists its scenario runs
6364
"""
6465
# List existing benchmark runs via raw API
65-
runs_page = async_sdk_client.api.benchmarks.runs.list(limit=1)
66-
runs = [run async for run in runs_page]
66+
runs_page = await async_sdk_client.api.benchmarks.runs.list(limit=1)
67+
runs = runs_page.runs
6768

6869
if not runs:
6970
pytest.skip("No benchmark runs available to test")
@@ -81,6 +82,12 @@ async def test_benchmark_run_list_scenario_runs(self, async_sdk_client: AsyncRun
8182
scenario_runs = await benchmark_run.list_scenario_runs()
8283
assert isinstance(scenario_runs, list)
8384

85+
# Verify returned items are AsyncScenarioRun objects
86+
for scenario_run in scenario_runs:
87+
assert isinstance(scenario_run, AsyncScenarioRun)
88+
assert scenario_run.id is not None
89+
assert scenario_run.devbox_id is not None
90+
8491

8592
class TestAsyncBenchmarkRunLifecycle:
8693
"""Test AsyncBenchmarkRun lifecycle operations."""
@@ -96,8 +103,8 @@ async def test_benchmark_run_create_and_cancel(self, async_sdk_client: AsyncRunl
96103
4. Cancels the run
97104
"""
98105
# Find an existing benchmark via raw API
99-
benchmarks_page = async_sdk_client.api.benchmarks.list(limit=1)
100-
benchmarks = [b async for b in benchmarks_page]
106+
benchmarks_page = await async_sdk_client.api.benchmarks.list(limit=1)
107+
benchmarks = benchmarks_page.benchmarks
101108

102109
if not benchmarks:
103110
pytest.skip("No benchmarks available to test")

tests/smoketests/sdk/test_benchmark_run.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest
1111

1212
from runloop_api_client.sdk import RunloopSDK
13+
from runloop_api_client.sdk.scenario_run import ScenarioRun
1314
from runloop_api_client.sdk.benchmark_run import BenchmarkRun
1415

1516
pytestmark = [pytest.mark.smoketest]
@@ -30,8 +31,7 @@ def test_benchmark_run_from_existing(self, sdk_client: RunloopSDK) -> None:
3031
3. Validates get_info returns correct data
3132
"""
3233
# List existing benchmark runs via raw API
33-
runs_page = sdk_client.api.benchmarks.runs.list(limit=1)
34-
runs = list(runs_page)
34+
runs = sdk_client.api.benchmarks.runs.list(limit=1).runs
3535

3636
if not runs:
3737
pytest.skip("No benchmark runs available to test")
@@ -62,8 +62,7 @@ def test_benchmark_run_list_scenario_runs(self, sdk_client: RunloopSDK) -> None:
6262
2. Lists its scenario runs
6363
"""
6464
# List existing benchmark runs via raw API
65-
runs_page = sdk_client.api.benchmarks.runs.list(limit=1)
66-
runs = list(runs_page)
65+
runs = sdk_client.api.benchmarks.runs.list(limit=1).runs
6766

6867
if not runs:
6968
pytest.skip("No benchmark runs available to test")
@@ -81,6 +80,12 @@ def test_benchmark_run_list_scenario_runs(self, sdk_client: RunloopSDK) -> None:
8180
scenario_runs = benchmark_run.list_scenario_runs()
8281
assert isinstance(scenario_runs, list)
8382

83+
# Verify returned items are ScenarioRun objects
84+
for scenario_run in scenario_runs:
85+
assert isinstance(scenario_run, ScenarioRun)
86+
assert scenario_run.id is not None
87+
assert scenario_run.devbox_id is not None
88+
8489

8590
class TestBenchmarkRunLifecycle:
8691
"""Test BenchmarkRun lifecycle operations."""
@@ -96,8 +101,7 @@ def test_benchmark_run_create_and_cancel(self, sdk_client: RunloopSDK) -> None:
96101
4. Cancels the run
97102
"""
98103
# Find an existing benchmark via raw API
99-
benchmarks_page = sdk_client.api.benchmarks.list(limit=1)
100-
benchmarks = list(benchmarks_page)
104+
benchmarks = sdk_client.api.benchmarks.list(limit=1).benchmarks
101105

102106
if not benchmarks:
103107
pytest.skip("No benchmarks available to test")

0 commit comments

Comments
 (0)