Skip to content

Commit d3a53ce

Browse files
committed
use Blueprint and Snapshot objects directly in ScenarioBuilder
1 parent f1b1ca6 commit d3a53ce

4 files changed

Lines changed: 112 additions & 80 deletions

File tree

src/runloop_api_client/sdk/async_scenario_builder.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from .._client import AsyncRunloop
99
from .async_scenario import AsyncScenario
10+
from .async_snapshot import AsyncSnapshot
11+
from .async_blueprint import AsyncBlueprint
1012
from ..types.scoring_function_param import (
1113
Scorer,
1214
ScoringFunctionParam,
@@ -28,7 +30,7 @@ class AsyncScenarioBuilder:
2830
2931
Example:
3032
>>> builder = sdk.scenario.builder("my-scenario")
31-
>>> builder.from_blueprint_id("bp-xxx")
33+
>>> builder.from_blueprint(blueprint)
3234
>>> builder.with_working_directory("/app")
3335
>>> builder.with_problem_statement("Fix the bug in main.py")
3436
>>> builder.add_test_scorer("tests", test_command="pytest")
@@ -47,8 +49,8 @@ def __init__(self, client: AsyncRunloop, name: str) -> None:
4749
self._name = name
4850

4951
# Environment configuration
50-
self._blueprint_id: Optional[str] = None
51-
self._snapshot_id: Optional[str] = None
52+
self._blueprint: Optional[AsyncBlueprint] = None
53+
self._snapshot: Optional[AsyncSnapshot] = None
5254
self._working_directory: Optional[str] = None
5355

5456
# Input context
@@ -78,28 +80,28 @@ def name(self) -> str:
7880
"""
7981
return self._name
8082

81-
def from_blueprint_id(self, blueprint_id: str) -> Self:
82-
"""Set the blueprint ID for the scenario environment.
83+
def from_blueprint(self, blueprint: AsyncBlueprint) -> Self:
84+
"""Set the blueprint for the scenario environment.
8385
84-
:param blueprint_id: Blueprint ID to use
85-
:type blueprint_id: str
86+
:param blueprint: Blueprint to use
87+
:type blueprint: AsyncBlueprint
8688
:return: Self for method chaining
8789
:rtype: Self
8890
"""
89-
self._blueprint_id = blueprint_id
90-
self._snapshot_id = None # Clear snapshot if blueprint is set
91+
self._blueprint = blueprint
92+
self._snapshot = None # Clear snapshot if blueprint is set
9193
return self
9294

93-
def from_snapshot_id(self, snapshot_id: str) -> Self:
94-
"""Set the snapshot ID for the scenario environment.
95+
def from_snapshot(self, snapshot: AsyncSnapshot) -> Self:
96+
"""Set the snapshot for the scenario environment.
9597
96-
:param snapshot_id: Snapshot ID to use
97-
:type snapshot_id: str
98+
:param snapshot: Snapshot to use
99+
:type snapshot: AsyncSnapshot
98100
:return: Self for method chaining
99101
:rtype: Self
100102
"""
101-
self._snapshot_id = snapshot_id
102-
self._blueprint_id = None # Clear blueprint if snapshot is set
103+
self._snapshot = snapshot
104+
self._blueprint = None # Clear blueprint if snapshot is set
103105
return self
104106

105107
def with_working_directory(self, directory: str) -> Self:
@@ -415,10 +417,10 @@ def _build_params(self) -> Dict[str, Any]:
415417

416418
# Build environment parameters if any are set
417419
env_params: Dict[str, Any] = {}
418-
if self._blueprint_id:
419-
env_params["blueprint_id"] = self._blueprint_id
420-
if self._snapshot_id:
421-
env_params["snapshot_id"] = self._snapshot_id
420+
if self._blueprint:
421+
env_params["blueprint_id"] = self._blueprint.id
422+
if self._snapshot:
423+
env_params["snapshot_id"] = self._snapshot.id
422424
if self._working_directory:
423425
env_params["working_directory"] = self._working_directory
424426

src/runloop_api_client/sdk/scenario_builder.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from .._client import Runloop
99
from .scenario import Scenario
10+
from .snapshot import Snapshot
11+
from .blueprint import Blueprint
1012
from ..types.scoring_function_param import (
1113
Scorer,
1214
ScoringFunctionParam,
@@ -28,7 +30,7 @@ class ScenarioBuilder:
2830
2931
Example:
3032
>>> builder = sdk.scenario.builder("my-scenario")
31-
>>> builder.from_blueprint_id("bp-xxx")
33+
>>> builder.from_blueprint(blueprint)
3234
>>> builder.with_working_directory("/app")
3335
>>> builder.with_problem_statement("Fix the bug in main.py")
3436
>>> builder.add_test_scorer("tests", test_command="pytest")
@@ -47,8 +49,8 @@ def __init__(self, client: Runloop, name: str) -> None:
4749
self._name = name
4850

4951
# Environment configuration
50-
self._blueprint_id: Optional[str] = None
51-
self._snapshot_id: Optional[str] = None
52+
self._blueprint: Optional[Blueprint] = None
53+
self._snapshot: Optional[Snapshot] = None
5254
self._working_directory: Optional[str] = None
5355

5456
# Input context
@@ -78,28 +80,28 @@ def name(self) -> str:
7880
"""
7981
return self._name
8082

81-
def from_blueprint_id(self, blueprint_id: str) -> Self:
82-
"""Set the blueprint ID for the scenario environment.
83+
def from_blueprint(self, blueprint: Blueprint) -> Self:
84+
"""Set the blueprint for the scenario environment.
8385
84-
:param blueprint_id: Blueprint ID to use
85-
:type blueprint_id: str
86+
:param blueprint: Blueprint to use
87+
:type blueprint: Blueprint
8688
:return: Self for method chaining
8789
:rtype: Self
8890
"""
89-
self._blueprint_id = blueprint_id
90-
self._snapshot_id = None # Clear snapshot if blueprint is set
91+
self._blueprint = blueprint
92+
self._snapshot = None # Clear snapshot if blueprint is set
9193
return self
9294

93-
def from_snapshot_id(self, snapshot_id: str) -> Self:
94-
"""Set the snapshot ID for the scenario environment.
95+
def from_snapshot(self, snapshot: Snapshot) -> Self:
96+
"""Set the snapshot for the scenario environment.
9597
96-
:param snapshot_id: Snapshot ID to use
97-
:type snapshot_id: str
98+
:param snapshot: Snapshot to use
99+
:type snapshot: Snapshot
98100
:return: Self for method chaining
99101
:rtype: Self
100102
"""
101-
self._snapshot_id = snapshot_id
102-
self._blueprint_id = None # Clear blueprint if snapshot is set
103+
self._snapshot = snapshot
104+
self._blueprint = None # Clear blueprint if snapshot is set
103105
return self
104106

105107
def with_working_directory(self, directory: str) -> Self:
@@ -415,10 +417,10 @@ def _build_params(self) -> Dict[str, Any]:
415417

416418
# Build environment parameters if any are set
417419
env_params: Dict[str, Any] = {}
418-
if self._blueprint_id:
419-
env_params["blueprint_id"] = self._blueprint_id
420-
if self._snapshot_id:
421-
env_params["snapshot_id"] = self._snapshot_id
420+
if self._blueprint:
421+
env_params["blueprint_id"] = self._blueprint.id
422+
if self._snapshot:
423+
env_params["snapshot_id"] = self._snapshot.id
422424
if self._working_directory:
423425
env_params["working_directory"] = self._working_directory
424426

tests/sdk/test_async_scenario_builder.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import pytest
88

9+
from runloop_api_client.sdk.async_snapshot import AsyncSnapshot
10+
from runloop_api_client.sdk.async_blueprint import AsyncBlueprint
911
from runloop_api_client.sdk.async_scenario_builder import AsyncScenarioBuilder
1012

1113

@@ -20,6 +22,16 @@ def mock_async_client(self) -> MagicMock:
2022
client.scenarios.create = AsyncMock()
2123
return client
2224

25+
@pytest.fixture
26+
def mock_blueprint(self, mock_async_client: MagicMock) -> AsyncBlueprint:
27+
"""Create a mock AsyncBlueprint object."""
28+
return AsyncBlueprint(mock_async_client, "bp-123")
29+
30+
@pytest.fixture
31+
def mock_snapshot(self, mock_async_client: MagicMock) -> AsyncSnapshot:
32+
"""Create a mock AsyncSnapshot object."""
33+
return AsyncSnapshot(mock_async_client, "snap-123")
34+
2335
@pytest.fixture
2436
def builder(self, mock_async_client: MagicMock) -> AsyncScenarioBuilder:
2537
"""Create an AsyncScenarioBuilder instance with mock client."""
@@ -37,21 +49,21 @@ def test_repr(self, builder: AsyncScenarioBuilder) -> None:
3749
"""Test builder __repr__."""
3850
assert repr(builder) == "<AsyncScenarioBuilder name='test-scenario'>"
3951

40-
def test_from_blueprint_id_returns_self(self, builder: AsyncScenarioBuilder) -> None:
41-
"""Test from_blueprint_id returns self for chaining."""
42-
result = builder.from_blueprint_id("bp-123")
52+
def test_from_blueprint_returns_self(self, builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint) -> None:
53+
"""Test from_blueprint returns self for chaining."""
54+
result = builder.from_blueprint(mock_blueprint)
4355

4456
assert result is builder
45-
assert builder._blueprint_id == "bp-123"
46-
assert builder._snapshot_id is None
57+
assert builder._blueprint is mock_blueprint
58+
assert builder._snapshot is None
4759

48-
def test_from_snapshot_id_returns_self(self, builder: AsyncScenarioBuilder) -> None:
49-
"""Test from_snapshot_id returns self for chaining."""
50-
result = builder.from_snapshot_id("snap-123")
60+
def test_from_snapshot_returns_self(self, builder: AsyncScenarioBuilder, mock_snapshot: AsyncSnapshot) -> None:
61+
"""Test from_snapshot returns self for chaining."""
62+
result = builder.from_snapshot(mock_snapshot)
5163

5264
assert result is builder
53-
assert builder._snapshot_id == "snap-123"
54-
assert builder._blueprint_id is None
65+
assert builder._snapshot is mock_snapshot
66+
assert builder._blueprint is None
5567

5668
def test_with_working_directory_returns_self(self, builder: AsyncScenarioBuilder) -> None:
5769
"""Test with_working_directory returns self for chaining."""
@@ -125,11 +137,11 @@ def test_build_params_minimal(self, builder: AsyncScenarioBuilder) -> None:
125137
assert params["input_context"]["problem_statement"] == "Fix the bug"
126138
assert len(params["scoring_contract"]["scoring_function_parameters"]) == 1
127139

128-
def test_build_params_with_environment(self, builder: AsyncScenarioBuilder) -> None:
140+
def test_build_params_with_environment(self, builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint) -> None:
129141
"""Test _build_params includes environment parameters."""
130142
builder.with_problem_statement("Fix the bug")
131143
builder.add_test_scorer("tests", test_command="pytest")
132-
builder.from_blueprint_id("bp-123")
144+
builder.from_blueprint(mock_blueprint)
133145
builder.with_working_directory("/app")
134146

135147
params = builder._build_params()
@@ -156,10 +168,10 @@ async def test_push_calls_api_and_returns_scenario(
156168

157169
assert scenario.id == "scn-new-123"
158170

159-
def test_fluent_chaining(self, builder: AsyncScenarioBuilder) -> None:
171+
def test_fluent_chaining(self, builder: AsyncScenarioBuilder, mock_blueprint: AsyncBlueprint) -> None:
160172
"""Test that all builder methods can be chained fluently."""
161173
result = (
162-
builder.from_blueprint_id("bp-123")
174+
builder.from_blueprint(mock_blueprint)
163175
.with_working_directory("/app")
164176
.with_problem_statement("Fix the bug")
165177
.with_additional_context({"hint": "check main.py"})
@@ -172,7 +184,7 @@ def test_fluent_chaining(self, builder: AsyncScenarioBuilder) -> None:
172184
)
173185

174186
assert result is builder
175-
assert builder._blueprint_id == "bp-123"
187+
assert builder._blueprint is mock_blueprint
176188
assert builder._working_directory == "/app"
177189
assert builder._problem_statement == "Fix the bug"
178190
assert len(builder._scorers) == 1

0 commit comments

Comments
 (0)