Split block.py 8/8: extract control-flow blocks → models/control_flow_blocks.py

Part 8/8 (final). ForLoopBlock, WhileLoopBlock, ConditionalBlock + LoopBlockExecutedResult /
compute_conditional_scopes move to control_flow_blocks.py. block.py is now a 495-line facade:
block_base re-export + submodule facade imports + BlockSubclasses union + BlockTypeVar + dispatch
(get_all_blocks, resolve_conditional_merge_edges) + shared helpers only.

Fixes a pydantic forward-ref regression: block.py injects BlockTypeVar into control_flow_blocks and
rebuilds ForLoop/WhileLoop after the discriminated union is complete (they type loop_blocks as
list[BlockTypeVar]); otherwise WorkflowDefinition.model_rebuild() fails. Block-type test patch targets
repointed to their owning modules now that block.py is stripped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AronPerez 2026-07-07 19:20:46 -05:00
parent c4c208265c
commit 1b80dcc716
No known key found for this signature in database
GPG key ID: 83807A86399C588E
17 changed files with 2355 additions and 2306 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -79,7 +79,6 @@ def _install_context(monkeypatch: pytest.MonkeyPatch, values: dict[str, Any]) ->
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
monkeypatch.setattr("skyvern.forge.sdk.workflow.models.pdf_fill_block.app", mock_app)
monkeypatch.setattr("skyvern.forge.sdk.workflow.models.block.app", mock_app)
monkeypatch.setattr("skyvern.forge.sdk.workflow.models.block_base.app", mock_app)
return context

View file

@ -64,7 +64,7 @@ class TestDescriptionSkippedOnLoopIterations:
block = _make_block()
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch.object(TaskBlock, "execute", new_callable=AsyncMock, return_value=_block_result()),
patch.object(Block, "_generate_workflow_run_block_description", new_callable=AsyncMock) as mock_gen_desc,
):
@ -80,7 +80,7 @@ class TestDescriptionSkippedOnLoopIterations:
block = _make_block()
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch.object(TaskBlock, "execute", new_callable=AsyncMock, return_value=_block_result()),
patch.object(Block, "_generate_workflow_run_block_description", new_callable=AsyncMock) as mock_gen_desc,
):
@ -96,7 +96,7 @@ class TestDescriptionSkippedOnLoopIterations:
block = _make_block()
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch.object(TaskBlock, "execute", new_callable=AsyncMock, return_value=_block_result()),
patch.object(Block, "_generate_workflow_run_block_description", new_callable=AsyncMock) as mock_gen_desc,
):
@ -112,7 +112,7 @@ class TestDescriptionSkippedOnLoopIterations:
block = _make_block()
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch.object(TaskBlock, "execute", new_callable=AsyncMock, return_value=_block_result()),
patch.object(Block, "_generate_workflow_run_block_description", new_callable=AsyncMock) as mock_gen_desc,
):

View file

@ -124,8 +124,7 @@ async def test_baseline_captured_when_loop_internal_state_is_none():
mock_storage.get_downloaded_files = AsyncMock(return_value=[file1])
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
await capture_block_download_baseline(context, "org_1", "wr_test", "block_1")
@ -153,8 +152,7 @@ async def test_baseline_recaptured_when_set_by_previous_block():
mock_storage.get_downloaded_files = AsyncMock(return_value=[file1, file2])
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
await capture_block_download_baseline(context, "org_1", "wr_test", "block_2")
@ -183,8 +181,7 @@ async def test_baseline_recaptured_even_when_loop_set_it():
)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
await capture_block_download_baseline(context, "org_1", "wr_test", "block_2")
@ -203,8 +200,7 @@ async def test_baseline_capture_degrades_on_timeout():
mock_storage.get_downloaded_files = AsyncMock(side_effect=asyncio.TimeoutError)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
# Must not raise
@ -225,8 +221,7 @@ async def test_stale_loop_baseline_overwritten_by_fresh_capture():
mock_storage.get_downloaded_files = AsyncMock(return_value=[])
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
await capture_block_download_baseline(context, "org_1", "wr_test", "block_1")
@ -244,8 +239,7 @@ async def test_baseline_capture_degrades_on_generic_exception():
mock_storage.get_downloaded_files = AsyncMock(side_effect=RuntimeError("S3 blip"))
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
# Must not raise — baseline capture is best-effort
@ -317,8 +311,7 @@ async def test_sibling_download_blocks_in_loop_iteration_scope_to_own_files():
async def run_download_block(produced: FileInfo) -> list[FileInfo]:
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.STORAGE = mock_storage
await capture_block_download_baseline(context, "org_1", "wr_test", "print")

View file

@ -19,7 +19,6 @@ import pytest
from skyvern.forge.sdk.core import skyvern_context
from skyvern.forge.sdk.core.skyvern_context import SkyvernContext
from skyvern.forge.sdk.schemas.files import FileInfo
from skyvern.forge.sdk.workflow.models import block as block_module
from skyvern.forge.sdk.workflow.models import block_base as block_base_module
from skyvern.forge.sdk.workflow.models import code_block as code_block_module
from skyvern.forge.sdk.workflow.models.block import CodeBlock
@ -103,7 +102,6 @@ def _fake_storage_app(monkeypatch: pytest.MonkeyPatch, *, save, get) -> None:
execute_code_block_override=AsyncMock(return_value=None),
),
)
monkeypatch.setattr(block_module, "app", fake_app)
monkeypatch.setattr(block_base_module, "app", fake_app)
monkeypatch.setattr(code_block_module, "app", fake_app)
@ -222,7 +220,7 @@ async def test_code_block_scopes_downloads_to_current_loop_iteration(
run_id="wr_1",
loop_internal_state={
"downloaded_file_signatures_before_iteration": [
block_module.to_downloaded_file_signature(prev_file),
block_base_module.to_downloaded_file_signature(prev_file),
],
},
)

View file

@ -25,7 +25,7 @@ from skyvern.forge.sdk.workflow.models.parameter import OutputParameter, Paramet
from skyvern.forge.sdk.workflow.service import WorkflowService
from skyvern.webeye.browser_artifacts import BrowserArtifacts, VideoArtifact
_BLOCK_PATH = "skyvern.forge.sdk.workflow.models.block.app"
_BLOCK_PATH = "skyvern.forge.sdk.workflow.models.code_block.app"
_MANAGER_PATH = "skyvern.forge.sdk.artifact.manager.app"
_SERVICE_PATH = "skyvern.forge.sdk.workflow.service.app"

View file

@ -762,7 +762,7 @@ async def wrapper({default_args}):
return None
monkeypatch.setattr(
"skyvern.forge.sdk.workflow.models.block.app.AGENT_FUNCTION.validate_code_block",
"skyvern.forge.sdk.workflow.models.code_block.app.AGENT_FUNCTION.validate_code_block",
validate_code_block,
)
monkeypatch.setattr(CodeBlock, "get_or_create_browser_state", get_browser_state)
@ -986,7 +986,7 @@ class _FakeWorkflowRun:
def _patch_context_resolution(monkeypatch: "pytest.MonkeyPatch", wrc) -> None:
monkeypatch.setattr(
"skyvern.forge.sdk.workflow.models.block.app.WORKFLOW_CONTEXT_MANAGER.get_workflow_run_context",
"skyvern.forge.sdk.workflow.models.code_block.app.WORKFLOW_CONTEXT_MANAGER.get_workflow_run_context",
lambda *args, **kwargs: wrc,
)
@ -1024,7 +1024,7 @@ async def _run_credential_code_block(monkeypatch: "pytest.MonkeyPatch", wrc, cod
persisted["value"] = value
monkeypatch.setattr(
"skyvern.forge.sdk.workflow.models.block.app.AGENT_FUNCTION.validate_code_block",
"skyvern.forge.sdk.workflow.models.code_block.app.AGENT_FUNCTION.validate_code_block",
validate_code_block,
)
monkeypatch.setattr(CodeBlock, "get_or_create_browser_state", get_browser_state)

View file

@ -320,8 +320,7 @@ async def test_block_adoption_seam_rebinds_to_run_dir() -> None:
browser_state.browser_context.browser = browser
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state = AsyncMock(return_value=browser_state)
mock_app.BROWSER_MANAGER.get_or_create_for_workflow_run = AsyncMock()
@ -350,8 +349,7 @@ async def test_block_adoption_seam_rebinds_via_context_page_without_owning_brows
browser_state.get_working_page = AsyncMock(return_value=page)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state = AsyncMock(return_value=browser_state)
@ -376,8 +374,7 @@ async def test_block_adoption_seam_no_browser_no_page_returns_state() -> None:
browser_state.get_working_page = AsyncMock(return_value=None)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state = AsyncMock(return_value=browser_state)
@ -401,8 +398,7 @@ async def test_block_non_adoption_cache_hit_rebinds_to_run_dir() -> None:
browser_state.is_connected = MagicMock(return_value=True)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.BROWSER_MANAGER.get_for_workflow_run = MagicMock(return_value=browser_state)
@ -430,8 +426,7 @@ async def test_block_non_adoption_rebinds_when_org_id_missing() -> None:
browser_state.is_connected = MagicMock(return_value=True)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.BROWSER_MANAGER.get_for_workflow_run = MagicMock(return_value=browser_state)
@ -459,8 +454,7 @@ async def test_block_non_adoption_rebinds_via_context_page_when_browser_is_none(
browser_state.get_working_page = AsyncMock(return_value=page)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.BROWSER_MANAGER.get_for_workflow_run = MagicMock(return_value=browser_state)
@ -489,9 +483,8 @@ async def test_block_non_adoption_fresh_create_empty_context_rebinds_with_workfl
empty_ctx = SkyvernContext(run_id=None, workflow_run_id=None, task_id=None)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context.current", return_value=empty_ctx),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.skyvern_context.current", return_value=empty_ctx),
):
mock_app.BROWSER_MANAGER.get_for_workflow_run = MagicMock(return_value=None)
mock_app.WORKFLOW_SERVICE.get_workflow_run = AsyncMock(return_value=MagicMock())
@ -518,8 +511,7 @@ async def test_block_non_adoption_rebind_fail_open() -> None:
browser_state.is_connected = MagicMock(return_value=True)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch(
"skyvern.forge.sdk.workflow.models.block_base.rebind_download_dir",
new_callable=AsyncMock,
@ -546,8 +538,7 @@ async def test_block_adoption_seam_fail_open_on_rebind_error() -> None:
browser_state.browser_context.browser = MagicMock()
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch(
"skyvern.forge.sdk.workflow.models.block_base.rebind_download_dir",
new_callable=AsyncMock,
@ -595,9 +586,8 @@ async def test_block_adoption_prefers_context_run_id_over_workflow_run_id() -> N
ctx = SkyvernContext(run_id="run_ctx", workflow_run_id="wr_block")
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context.current", return_value=ctx),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.skyvern_context.current", return_value=ctx),
):
mock_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state = AsyncMock(return_value=browser_state)
mock_app.BROWSER_MANAGER.get_or_create_for_workflow_run = AsyncMock()
@ -1108,9 +1098,8 @@ async def test_block_non_adoption_override_takes_precedence_over_context() -> No
ctx = SkyvernContext(run_id=None, workflow_run_id="wr_block", task_id=None)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context.current", return_value=ctx),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.skyvern_context.current", return_value=ctx),
):
mock_app.BROWSER_MANAGER.get_for_workflow_run = MagicMock(return_value=browser_state)
@ -1136,8 +1125,7 @@ async def test_block_non_adoption_reused_browser_rebinds_to_second_run() -> None
browser_state.is_connected = MagicMock(return_value=True)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
):
mock_app.BROWSER_MANAGER.get_for_workflow_run = MagicMock(return_value=browser_state)
@ -1167,8 +1155,7 @@ async def test_register_downloaded_files_uses_download_run_id_as_storage_key() -
block = CodeBlock.__new__(CodeBlock)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.code_block.app", mock_app),
):
mock_app.STORAGE.save_downloaded_files = AsyncMock()
@ -1192,8 +1179,7 @@ async def test_register_downloaded_files_defaults_to_workflow_run_id() -> None:
block = CodeBlock.__new__(CodeBlock)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.block_base.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.code_block.app", mock_app),
):
mock_app.STORAGE.save_downloaded_files = AsyncMock()
@ -1215,7 +1201,7 @@ async def test_register_pdf_uses_download_run_id_as_storage_key() -> None:
block = PrintPageBlock.__new__(PrintPageBlock)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.misc_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.misc_blocks.app", mock_app),
):
@ -1240,7 +1226,7 @@ async def test_register_pdf_defaults_to_workflow_run_id() -> None:
block = PrintPageBlock.__new__(PrintPageBlock)
with (
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.misc_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.misc_blocks.app", mock_app),
):
@ -1312,7 +1298,7 @@ async def test_print_page_block_threads_resolved_id_to_all_sinks(tmp_path) -> No
),
patch("skyvern.forge.sdk.workflow.models.misc_blocks.get_download_dir", side_effect=fake_get_download_dir),
patch("skyvern.forge.sdk.workflow.models.misc_blocks.skyvern_context.current", return_value=ctx),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.misc_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block_base.app", mock_app),
patch("skyvern.forge.sdk.workflow.models.misc_blocks.app", mock_app),
):

View file

@ -18,7 +18,7 @@ from skyvern.forge.sdk.workflow.models.block import Block, ForLoopBlock, LoopBlo
from skyvern.forge.sdk.workflow.models.parameter import OutputParameter
from skyvern.schemas.workflows import BlockResult, BlockStatus
INTERVAL_PATCH = "skyvern.forge.sdk.workflow.models.block.PERSIST_LOOP_OUTPUT_INTERVAL"
INTERVAL_PATCH = "skyvern.forge.sdk.workflow.models.control_flow_blocks.PERSIST_LOOP_OUTPUT_INTERVAL"
def _make_output_param(label: str) -> OutputParameter:
@ -71,7 +71,7 @@ class TestExecuteCallsRecordOnceAtEnd:
patch.object(ForLoopBlock, "execute_loop_helper", new_callable=AsyncMock, return_value=loop_result),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock) as mock_record,
patch.object(Block, "build_block_result", new_callable=AsyncMock, return_value=final_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
@ -116,8 +116,8 @@ class TestExecuteLoopHelperPersistsToDbDirectly:
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch.object(ForLoopBlock, "get_loop_block_context_parameters", return_value=[]),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock) as mock_record,
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
@ -166,8 +166,8 @@ class TestExecuteLoopHelperPersistsToDbDirectly:
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch.object(ForLoopBlock, "get_loop_block_context_parameters", return_value=[]),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
@ -207,8 +207,8 @@ class TestExecuteLoopHelperPersistsToDbDirectly:
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch.object(ForLoopBlock, "get_loop_block_context_parameters", return_value=[]),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
@ -252,8 +252,8 @@ class TestIncrementalPersistFailureResilience:
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch.object(ForLoopBlock, "get_loop_block_context_parameters", return_value=[]),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
@ -305,8 +305,8 @@ class TestIncrementalPersistFailureResilience:
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch.object(ForLoopBlock, "get_loop_block_context_parameters", return_value=[]),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
@ -356,8 +356,8 @@ class TestPersistIntervalBatching:
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch.object(ForLoopBlock, "get_loop_block_context_parameters", return_value=[]),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
@ -389,7 +389,7 @@ class TestPersistIntervalBatching:
mock_db_upsert = AsyncMock()
with patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app:
with patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app:
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = mock_db_upsert
await loop_block._persist_partial_loop_output(

View file

@ -145,7 +145,7 @@ class TestForLoopParentNextLoopOnFailureSwallowsLastIterationFailure:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -196,7 +196,7 @@ class TestForLoopParentNextLoopOnFailureSwallowsLastIterationFailure:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -252,7 +252,7 @@ class TestForLoopInnerNextLoopOnFailureSwallowsLastIterationFailure:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -307,7 +307,7 @@ class TestForLoopWithoutFlagsStillTerminatesOnBodyFailure:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -363,7 +363,7 @@ class TestWhileLoopParentNextLoopOnFailureSwallowsLastIterationFailure:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -421,7 +421,7 @@ class TestLoopBlockSwallowPathClearsFailureReason:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -473,7 +473,7 @@ class TestLoopBlockSwallowPathClearsFailureReason:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(
@ -543,7 +543,7 @@ class TestSyntheticSafetyLimitNotSwallowedByNextLoopOnFailure:
),
patch.object(Block, "record_output_parameter_value", new_callable=AsyncMock),
patch.object(Block, "build_block_result", side_effect=fake_build_block_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
):
mock_app.DATABASE.observer.update_workflow_run_block = AsyncMock()
await loop_block.execute(

View file

@ -240,8 +240,8 @@ class TestExecuteTopOfLoopSemantics:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new_callable=AsyncMock, return_value=False),
patch.object(Block, "execute_safe", new_callable=AsyncMock) as mock_execute_safe,
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -277,8 +277,8 @@ class TestExecuteTopOfLoopSemantics:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new=fake_eval),
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -328,8 +328,8 @@ class TestExecuteTopOfLoopSemantics:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new=fake_eval),
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -371,11 +371,11 @@ class TestExecuteMaxIterationsCap:
# Patch the cap to a small number so the test is fast.
with (
patch("skyvern.forge.sdk.workflow.models.block.DEFAULT_MAX_LOOP_ITERATIONS", 5),
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.DEFAULT_MAX_LOOP_ITERATIONS", 5),
patch.object(WhileLoopBlock, "_evaluate_condition", new_callable=AsyncMock, return_value=True),
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -418,11 +418,11 @@ class TestExecuteMaxIterationsCap:
return next(condition_results)
with (
patch("skyvern.forge.sdk.workflow.models.block.DEFAULT_MAX_LOOP_ITERATIONS", 3),
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.DEFAULT_MAX_LOOP_ITERATIONS", 3),
patch.object(WhileLoopBlock, "_evaluate_condition", new=fake_eval),
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -472,8 +472,8 @@ class TestCurrentIndexWrittenBeforeCondition:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new=fake_eval),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -513,8 +513,8 @@ class TestWhileLoopJinjaCurrentIndexIntegration:
with (
patch.object(Block, "execute_safe", new_callable=AsyncMock) as mock_execute_safe,
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -541,8 +541,8 @@ class TestWhileLoopJinjaCurrentIndexIntegration:
with (
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result) as mock_execute_safe,
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -571,8 +571,8 @@ class TestWhileLoopJinjaCurrentIndexIntegration:
with (
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=inner_result) as mock_execute_safe,
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -600,8 +600,8 @@ class TestExecuteConditionRenderingErrors:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new=raise_format_error),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -628,8 +628,8 @@ class TestExecuteConditionRenderingErrors:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new=raise_missing),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -666,8 +666,8 @@ class TestExecuteCancellationPropagation:
with (
patch.object(WhileLoopBlock, "_evaluate_condition", new_callable=AsyncMock, return_value=True),
patch.object(Block, "execute_safe", new_callable=AsyncMock, return_value=canceled_result),
patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.block.skyvern_context") as mock_skyvern_ctx,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app,
patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.skyvern_context") as mock_skyvern_ctx,
):
mock_skyvern_ctx.current.return_value = None
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
@ -703,7 +703,7 @@ class TestPromptCriteriaEvaluation:
mock_context = MagicMock()
with patch(
"skyvern.forge.sdk.workflow.models.block._evaluate_prompt_branch_conditions_batch",
"skyvern.forge.sdk.workflow.models.control_flow_blocks._evaluate_prompt_branch_conditions_batch",
new_callable=AsyncMock,
) as mock_batch:
mock_batch.return_value = ([True], ["dates on the page are still recent"], "goal", {})

View file

@ -31,7 +31,7 @@ async def test_persist_partial_while_loop_output_calls_db() -> None:
loop_blocks=[inner],
condition=JinjaBranchCriteria(expression="{{ false }}"),
)
with patch("skyvern.forge.sdk.workflow.models.block.app") as mock_app:
with patch("skyvern.forge.sdk.workflow.models.control_flow_blocks.app") as mock_app:
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter = AsyncMock()
await loop._persist_partial_loop_output("wr1", [[]], loop_idx=0)
mock_app.DATABASE.workflow_runs.create_or_update_workflow_run_output_parameter.assert_awaited_once()

View file

@ -423,7 +423,7 @@ def _patch_execute_environment(
"update_step": AsyncMock(return_value=None),
}
monkeypatch.setattr(
"skyvern.forge.sdk.workflow.models.block.app.AGENT_FUNCTION.validate_code_block", validate_code_block
"skyvern.forge.sdk.workflow.models.code_block.app.AGENT_FUNCTION.validate_code_block", validate_code_block
)
monkeypatch.setattr(CodeBlock, "get_or_create_browser_state", get_browser_state)
monkeypatch.setattr(CodeBlock, "get_workflow_run_context", lambda *args: context)

View file

@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import skyvern.forge.sdk.workflow.models.block as block_module
import skyvern.forge.sdk.workflow.models.block_base as block_module
from skyvern.exceptions import ConditionalBranchEvaluationError
from skyvern.forge.prompts import prompt_engine
from skyvern.forge.sdk.workflow.models.block import (

View file

@ -13,13 +13,13 @@ from skyvern.forge.sdk.core import skyvern_context
from skyvern.forge.sdk.core.skyvern_context import SkyvernContext
from skyvern.forge.sdk.experimentation import providers as providers_module
from skyvern.forge.sdk.workflow.exceptions import (
FailedToFormatJinjaStyleParameter,
InvalidWorkflowDefinition,
PayloadTemplateRenderError,
PayloadTemplateSyntaxError,
)
from skyvern.forge.sdk.workflow.models._jinja import _JSON_TYPE_MARKER
from skyvern.forge.sdk.workflow.models.block import (
FailedToFormatJinjaStyleParameter,
WorkflowTriggerBlock,
jinja_sandbox_env,
)

View file

@ -183,7 +183,7 @@ class TestFileParserBlock:
with patch.object(object.__getattribute__(app, "_inst"), "LLM_API_HANDLER") as mock_llm:
mock_llm.return_value = mock_response
with patch("skyvern.forge.sdk.workflow.models.block.prompt_engine.load_prompt") as mock_prompt:
with patch("skyvern.forge.sdk.workflow.models.parser_blocks.prompt_engine.load_prompt") as mock_prompt:
mock_prompt.return_value = "mocked prompt"
result = await file_parser_block._extract_with_ai([{"name": "John"}, {"name": "Jane"}], MagicMock())
@ -201,7 +201,7 @@ class TestFileParserBlock:
with patch.object(object.__getattribute__(app, "_inst"), "LLM_API_HANDLER") as mock_llm:
mock_llm.return_value = mock_response
with patch("skyvern.forge.sdk.workflow.models.block.prompt_engine.load_prompt") as mock_prompt:
with patch("skyvern.forge.sdk.workflow.models.parser_blocks.prompt_engine.load_prompt") as mock_prompt:
mock_prompt.return_value = "mocked prompt"
result = await file_parser_block._extract_with_ai("Some text content", MagicMock())