mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
copilot: DifyPort.restore_graph (whole-graph draft restore)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
e464c2879f
commit
2f1e342ed0
5 files changed
+81
No files matched your search
@@ -124,6 +124,8 @@ class DifyPort(Protocol):
|
||||
|
||||
def publish(self, app_id: str, actor: Actor) -> None: ...
|
||||
|
||||
def restore_graph(self, app_id: str, actor: Actor, graph: Graph) -> str: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Repository(Protocol):
|
||||
|
||||
@@ -224,6 +224,34 @@ class WorkflowServiceDifyPort:
|
||||
changed_nodes=changed_nodes, new_hash=updated.unique_hash, changes=changes, scope=scope
|
||||
)
|
||||
|
||||
def restore_graph(self, app_id: str, actor: Actor, graph: Graph) -> str:
|
||||
"""Restore the draft to a prior snapshot graph (Slice 4 revert): write
|
||||
the whole graph back via the same graph-only sync apply_repair uses.
|
||||
Returns the new draft hash. Raises HashMismatchError if the draft
|
||||
changed since the caller read it (same contract as apply_repair)."""
|
||||
with _session_factory()() as session:
|
||||
account = resolve_account(session, actor)
|
||||
app = load_app(session, app_id, actor)
|
||||
workflow = _load_draft_workflow_or_raise(app, session=session)
|
||||
try:
|
||||
updated = WorkflowService().sync_draft_workflow(
|
||||
app_model=app,
|
||||
graph=graph,
|
||||
features=dict(workflow.features_dict),
|
||||
unique_hash=workflow.unique_hash,
|
||||
account=account,
|
||||
environment_variables=[],
|
||||
conversation_variables=workflow.conversation_variables,
|
||||
session=session,
|
||||
graph_only=True,
|
||||
sync_agent_bindings=False,
|
||||
commit=True,
|
||||
preserve_environment_variables=True,
|
||||
)
|
||||
except WorkflowHashNotEqualError as exc:
|
||||
raise HashMismatchError(f"draft workflow changed since read: {app_id}") from exc
|
||||
return updated.unique_hash
|
||||
|
||||
def run_draft(self, app_id: str, actor: Actor, inputs: Inputs, on_event: Callable[[NodeEvent], None]) -> Run:
|
||||
with _session_factory()() as session:
|
||||
account = resolve_account(session, actor)
|
||||
|
||||
@@ -287,6 +287,11 @@ class FakeDifyPort:
|
||||
def publish(self, _app_id: str, _actor: Actor) -> None:
|
||||
self.published = True
|
||||
|
||||
def restore_graph(self, _app_id: str, _actor: Actor, graph: Graph) -> str:
|
||||
self.graph = copy.deepcopy(graph)
|
||||
self.hash = "h-restored"
|
||||
return self.hash
|
||||
|
||||
|
||||
# ---- stub CopilotAgent ----------------------------------------------------
|
||||
|
||||
@@ -439,6 +444,11 @@ class FakeBuildDifyPort:
|
||||
def publish(self, _app_id: str, _actor: Actor) -> None:
|
||||
self.published = True
|
||||
|
||||
def restore_graph(self, _app_id: str, _actor: Actor, graph: Graph) -> str:
|
||||
self.graph = copy.deepcopy(graph)
|
||||
self.hash = "h-restored"
|
||||
return self.hash
|
||||
|
||||
|
||||
# ---- fake DifyPort pre-seeded with an existing graph (for Edit) -----------
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Slice 4 session-lifecycle-core tests: restore_graph, invalidation, revert,
|
||||
checkpoint surfacing, pause/resume."""
|
||||
|
||||
from core.workflow_copilot.models import Actor
|
||||
from core.workflow_copilot.ports import DifyPort
|
||||
from tests.unit_tests.core.workflow_copilot.fakes import FakeBuildDifyPort, FakeDifyPort
|
||||
|
||||
|
||||
def _actor() -> Actor:
|
||||
return Actor(account_id="a", tenant_id="t")
|
||||
|
||||
|
||||
def test_fake_dify_port_restore_graph_swaps_graph_and_returns_hash():
|
||||
dify = FakeDifyPort()
|
||||
dify.graph = {"nodes": [{"id": "x"}]}
|
||||
snapshot = {"nodes": [{"id": "start"}, {"id": "end"}]}
|
||||
new_hash = dify.restore_graph("app", _actor(), snapshot)
|
||||
assert isinstance(new_hash, str)
|
||||
assert new_hash
|
||||
got, got_hash = dify.read_graph("app", _actor())
|
||||
assert got == snapshot
|
||||
assert got_hash == new_hash
|
||||
# deep-copied: mutating the caller's graph does not corrupt the fake
|
||||
snapshot["nodes"].append({"id": "leak"})
|
||||
assert len(dify.read_graph("app", _actor())[0]["nodes"]) == 2
|
||||
|
||||
|
||||
def test_fake_build_dify_port_restore_graph_swaps_graph():
|
||||
dify = FakeBuildDifyPort()
|
||||
dify.graph = {"nodes": [{"id": "a"}], "edges": []}
|
||||
snapshot = {"nodes": [], "edges": []}
|
||||
dify.restore_graph("app", _actor(), snapshot)
|
||||
assert dify.read_graph("app", _actor())[0] == {"nodes": [], "edges": []}
|
||||
|
||||
|
||||
def test_fake_dify_ports_still_satisfy_dify_port_protocol():
|
||||
assert isinstance(FakeDifyPort(), DifyPort)
|
||||
assert isinstance(FakeBuildDifyPort(), DifyPort)
|
||||
@@ -159,6 +159,9 @@ class _StubDifyPort:
|
||||
def publish(self, _app_id, _actor):
|
||||
return None
|
||||
|
||||
def restore_graph(self, _app_id, _actor, _graph):
|
||||
return "hash-restored"
|
||||
|
||||
|
||||
class _StubRepository:
|
||||
"""A trivial conforming Repository."""
|
||||
|
||||
Reference in new issue
Block a user