wf_api: new neutral protocol
crazy test case btw wtf
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .backend import TraceRange, WorkflowApiBackend
|
||||
from .service import WorkflowApi
|
||||
|
||||
__all__ = ["TraceRange", "WorkflowApi", "WorkflowApiBackend"]
|
||||
@@ -0,0 +1,321 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
from wf_artifacts import ArtifactKind
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TraceRange:
|
||||
"""Caller-bounded debug trace slice for durable deployment runs."""
|
||||
|
||||
start: int = 0
|
||||
limit: int = 25
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class WorkflowApiBackend(Protocol):
|
||||
"""High-level workflow operation protocol.
|
||||
|
||||
Implementations wrap a concrete service (e.g. WorkflowSurfaceHandlers
|
||||
backed by WfMcpService) so that wf_api never imports wf_mcp.
|
||||
"""
|
||||
|
||||
# -- capabilities --
|
||||
|
||||
async def list_capabilities(
|
||||
self,
|
||||
*,
|
||||
query: str | None = None,
|
||||
source_id: str | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def inspect_capability(
|
||||
self,
|
||||
*,
|
||||
qualified_name: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def call_capability(
|
||||
self,
|
||||
*,
|
||||
qualified_name: str,
|
||||
payload: dict[str, Any],
|
||||
deployment_id: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
# -- artifacts --
|
||||
|
||||
async def list_artifacts(
|
||||
self,
|
||||
*,
|
||||
query: str | None = None,
|
||||
kind: ArtifactKind | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def inspect_artifact(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def save_artifact(
|
||||
self,
|
||||
artifact: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
plan: dict[str, Any],
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def create_artifact_from_draft(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
draft: dict[str, Any],
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def create_wrapper_from_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
outcomes: Sequence[str],
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
# -- drafts --
|
||||
|
||||
async def validate_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def compile_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def patch_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
# -- draft workspaces --
|
||||
|
||||
async def list_draft_workspaces(self) -> dict[str, Any]: ...
|
||||
|
||||
async def create_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
draft: dict[str, Any],
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def get_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def delete_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def validate_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def patch_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def set_draft_name(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def set_draft_route(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def set_step_input_map(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def set_step_output_map(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def create_minimal_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
name: str,
|
||||
capability_name: str,
|
||||
input_schema: dict[str, Any],
|
||||
state_schema: dict[str, Any],
|
||||
output_schema: dict[str, Any],
|
||||
input: Sequence[Any] | None = None,
|
||||
output: Sequence[Any] | None = None,
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def create_draft_workspace_from_capability(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
capability_name: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
input_schema: dict[str, Any] | None = None,
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
input: Sequence[Any] | None = None,
|
||||
output: Sequence[Any] | None = None,
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
# -- deployments --
|
||||
|
||||
async def list_deployments(self) -> dict[str, Any]: ...
|
||||
|
||||
async def inspect_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def save_deployment(
|
||||
self,
|
||||
deployment: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def delete_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def validate_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
live_check: bool = False,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
# -- runs --
|
||||
|
||||
async def run_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
workflow_input: dict[str, Any],
|
||||
trace_range: TraceRange | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def resume_run(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
resume_payload: dict[str, Any],
|
||||
resume_outcome: str = "submitted",
|
||||
trace_range: TraceRange | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def inspect_run(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def read_run_trace(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
trace_range: TraceRange,
|
||||
) -> dict[str, Any]: ...
|
||||
@@ -0,0 +1,466 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from wf_artifacts import ArtifactKind
|
||||
|
||||
from .backend import TraceRange, WorkflowApiBackend
|
||||
|
||||
|
||||
class WorkflowApi:
|
||||
"""Protocol-neutral workflow application facade.
|
||||
|
||||
Delegates every operation to a WorkflowApiBackend implementation.
|
||||
This class owns no business logic; it exists so that callers
|
||||
(wf_cli, wf_mcp tools, future HTTP adapters) share one entry point
|
||||
that does not import wf_mcp.
|
||||
"""
|
||||
|
||||
def __init__(self, backend: WorkflowApiBackend) -> None:
|
||||
self.backend: WorkflowApiBackend = backend
|
||||
|
||||
# -- capabilities --
|
||||
|
||||
async def list_capabilities(
|
||||
self,
|
||||
*,
|
||||
query: str | None = None,
|
||||
source_id: str | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.list_capabilities(
|
||||
query=query, source_id=source_id, cursor=cursor, limit=limit,
|
||||
)
|
||||
|
||||
async def inspect_capability(
|
||||
self,
|
||||
*,
|
||||
qualified_name: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.inspect_capability(qualified_name=qualified_name)
|
||||
|
||||
async def call_capability(
|
||||
self,
|
||||
*,
|
||||
qualified_name: str,
|
||||
payload: dict[str, Any],
|
||||
deployment_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.call_capability(
|
||||
qualified_name=qualified_name, payload=payload, deployment_id=deployment_id,
|
||||
)
|
||||
|
||||
# -- artifacts --
|
||||
|
||||
async def list_artifacts(
|
||||
self,
|
||||
*,
|
||||
query: str | None = None,
|
||||
kind: ArtifactKind | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.list_artifacts(
|
||||
query=query, kind=kind, cursor=cursor, limit=limit,
|
||||
)
|
||||
|
||||
async def inspect_artifact(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.inspect_artifact(
|
||||
artifact_id=artifact_id, version=version,
|
||||
)
|
||||
|
||||
async def save_artifact(
|
||||
self,
|
||||
artifact: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.save_artifact(artifact)
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
plan: dict[str, Any],
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_artifact_from_plan(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
plan=plan,
|
||||
outcomes=outcomes,
|
||||
kind=kind,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
async def create_artifact_from_draft(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
draft: dict[str, Any],
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_artifact_from_draft(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
draft=draft,
|
||||
outcomes=outcomes,
|
||||
kind=kind,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_artifact_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
outcomes=outcomes,
|
||||
kind=kind,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
async def create_wrapper_from_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
outcomes: Sequence[str],
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_wrapper_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
outcomes=outcomes,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
# -- drafts --
|
||||
|
||||
async def validate_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.validate_draft(draft=draft)
|
||||
|
||||
async def compile_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.compile_draft(draft=draft)
|
||||
|
||||
async def patch_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.patch_draft(draft=draft, patch=patch)
|
||||
|
||||
# -- draft workspaces --
|
||||
|
||||
async def list_draft_workspaces(self) -> dict[str, Any]:
|
||||
return await self.backend.list_draft_workspaces()
|
||||
|
||||
async def create_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
draft: dict[str, Any],
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_draft_workspace(
|
||||
workspace_id=workspace_id, draft=draft, title=title,
|
||||
)
|
||||
|
||||
async def get_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.get_draft_workspace(
|
||||
workspace_id=workspace_id, include_draft=include_draft,
|
||||
)
|
||||
|
||||
async def delete_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.delete_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def validate_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.validate_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def patch_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.patch_draft_workspace(
|
||||
workspace_id=workspace_id, revision=revision, patch=patch,
|
||||
)
|
||||
|
||||
async def set_draft_name(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.set_draft_name(
|
||||
workspace_id=workspace_id, revision=revision, name=name,
|
||||
)
|
||||
|
||||
async def set_draft_route(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.set_draft_route(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
outcome=outcome,
|
||||
target=target,
|
||||
)
|
||||
|
||||
async def set_step_input_map(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.set_step_input_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
input_map=input_map,
|
||||
)
|
||||
|
||||
async def set_step_output_map(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.set_step_output_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
output_map=output_map,
|
||||
)
|
||||
|
||||
async def create_minimal_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
name: str,
|
||||
capability_name: str,
|
||||
input_schema: dict[str, Any],
|
||||
state_schema: dict[str, Any],
|
||||
output_schema: dict[str, Any],
|
||||
input: Sequence[Any] | None = None,
|
||||
output: Sequence[Any] | None = None,
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_minimal_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
name=name,
|
||||
capability_name=capability_name,
|
||||
input_schema=input_schema,
|
||||
state_schema=state_schema,
|
||||
output_schema=output_schema,
|
||||
input=input,
|
||||
output=output,
|
||||
input_map=input_map,
|
||||
output_map=output_map,
|
||||
error_message_source=error_message_source,
|
||||
title=title,
|
||||
)
|
||||
|
||||
async def create_draft_workspace_from_capability(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
capability_name: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
input_schema: dict[str, Any] | None = None,
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
input: Sequence[Any] | None = None,
|
||||
output: Sequence[Any] | None = None,
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.create_draft_workspace_from_capability(
|
||||
workspace_id=workspace_id,
|
||||
capability_name=capability_name,
|
||||
name=name,
|
||||
title=title,
|
||||
input_schema=input_schema,
|
||||
state_schema=state_schema,
|
||||
output_schema=output_schema,
|
||||
input=input,
|
||||
output=output,
|
||||
input_map=input_map,
|
||||
output_map=output_map,
|
||||
error_message_source=error_message_source,
|
||||
)
|
||||
|
||||
# -- deployments --
|
||||
|
||||
async def list_deployments(self) -> dict[str, Any]:
|
||||
return await self.backend.list_deployments()
|
||||
|
||||
async def inspect_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.inspect_deployment(deployment_id=deployment_id)
|
||||
|
||||
async def save_deployment(
|
||||
self,
|
||||
deployment: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.save_deployment(deployment)
|
||||
|
||||
async def delete_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.delete_deployment(deployment_id=deployment_id)
|
||||
|
||||
async def validate_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
live_check: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.validate_deployment(
|
||||
deployment_id=deployment_id, live_check=live_check,
|
||||
)
|
||||
|
||||
# -- runs --
|
||||
|
||||
async def run_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
workflow_input: dict[str, Any],
|
||||
trace_range: TraceRange | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.run_deployment(
|
||||
deployment_id=deployment_id,
|
||||
workflow_input=workflow_input,
|
||||
trace_range=trace_range,
|
||||
)
|
||||
|
||||
async def resume_run(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
resume_payload: dict[str, Any],
|
||||
resume_outcome: str = "submitted",
|
||||
trace_range: TraceRange | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.resume_run(
|
||||
run_id=run_id,
|
||||
resume_payload=resume_payload,
|
||||
resume_outcome=resume_outcome,
|
||||
trace_range=trace_range,
|
||||
)
|
||||
|
||||
async def inspect_run(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.inspect_run(run_id=run_id)
|
||||
|
||||
async def read_run_trace(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
trace_range: TraceRange,
|
||||
) -> dict[str, Any]:
|
||||
return await self.backend.read_run_trace(
|
||||
run_id=run_id, trace_range=trace_range,
|
||||
)
|
||||
@@ -8,7 +8,7 @@ import typer
|
||||
|
||||
from wf_cli.context import config_path_from_context, load_cli_context
|
||||
from wf_cli.io import CliInputError, emit_json, parse_json_input
|
||||
from wf_mcp.workflow_surface import TraceRange
|
||||
from wf_api.backend import TraceRange
|
||||
|
||||
app = typer.Typer(
|
||||
name="run",
|
||||
|
||||
@@ -5,9 +5,10 @@ from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
from wf_api import WorkflowApi
|
||||
from wf_mcp.broker import build_service_from_config, load_broker_config
|
||||
from wf_mcp.broker.service import WfMcpService
|
||||
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
||||
from wf_mcp.broker.service.workflow_api_backend import WfMcpWorkflowApiBackend
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -22,7 +23,7 @@ class CliContext:
|
||||
|
||||
config_path: Path
|
||||
service: WfMcpService
|
||||
handlers: WorkflowSurfaceHandlers
|
||||
handlers: WorkflowApi
|
||||
|
||||
|
||||
def config_path_from_context(ctx: typer.Context) -> str:
|
||||
@@ -40,5 +41,5 @@ def load_cli_context(config_path: str | Path) -> CliContext:
|
||||
return CliContext(
|
||||
config_path=resolved_config_path,
|
||||
service=service,
|
||||
handlers=WorkflowSurfaceHandlers(service),
|
||||
handlers=WorkflowApi(WfMcpWorkflowApiBackend(service)),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from wf_artifacts import ArtifactKind
|
||||
from wf_api.backend import TraceRange as ApiTraceRange
|
||||
|
||||
from ...workflow_surface.handlers import WorkflowSurfaceHandlers
|
||||
from ...workflow_surface.models import TraceRange as HandlerTraceRange
|
||||
from .core import WfMcpService
|
||||
|
||||
|
||||
def _to_handler_trace_range(tr: ApiTraceRange) -> HandlerTraceRange:
|
||||
return HandlerTraceRange(start=tr.start, limit=tr.limit)
|
||||
|
||||
|
||||
class WfMcpWorkflowApiBackend:
|
||||
"""Adapt existing WorkflowSurfaceHandlers into WorkflowApiBackend."""
|
||||
|
||||
def __init__(self, service: WfMcpService) -> None:
|
||||
self._handlers = WorkflowSurfaceHandlers(service)
|
||||
|
||||
# -- capabilities --
|
||||
|
||||
async def list_capabilities(
|
||||
self,
|
||||
*,
|
||||
query: str | None = None,
|
||||
source_id: str | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.list_capabilities(
|
||||
query=query, source_id=source_id, cursor=cursor, limit=limit,
|
||||
)
|
||||
|
||||
async def inspect_capability(
|
||||
self,
|
||||
*,
|
||||
qualified_name: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.inspect_capability(qualified_name=qualified_name)
|
||||
|
||||
async def call_capability(
|
||||
self,
|
||||
*,
|
||||
qualified_name: str,
|
||||
payload: dict[str, Any],
|
||||
deployment_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.call_capability(
|
||||
qualified_name=qualified_name, payload=payload, deployment_id=deployment_id,
|
||||
)
|
||||
|
||||
# -- artifacts --
|
||||
|
||||
async def list_artifacts(
|
||||
self,
|
||||
*,
|
||||
query: str | None = None,
|
||||
kind: ArtifactKind | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.list_artifacts(
|
||||
query=query, kind=kind, cursor=cursor, limit=limit,
|
||||
)
|
||||
|
||||
async def inspect_artifact(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.inspect_artifact(
|
||||
artifact_id=artifact_id, version=version,
|
||||
)
|
||||
|
||||
async def save_artifact(
|
||||
self,
|
||||
artifact: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.save_artifact(artifact)
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
plan: dict[str, Any],
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_artifact_from_plan(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
plan=plan,
|
||||
outcomes=outcomes,
|
||||
kind=kind,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
async def create_artifact_from_draft(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
draft: dict[str, Any],
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_artifact_from_draft(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
draft=draft,
|
||||
outcomes=outcomes,
|
||||
kind=kind,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
outcomes: Sequence[str],
|
||||
kind: ArtifactKind = "workflow",
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_artifact_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
outcomes=outcomes,
|
||||
kind=kind,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
async def create_wrapper_from_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
title: str,
|
||||
outcomes: Sequence[str],
|
||||
description: str | None = None,
|
||||
required_capabilities: dict[str, dict[str, Any]] | None = None,
|
||||
source_bindings: dict[str, str] | None = None,
|
||||
created_from_catalog_version: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_wrapper_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
title=title,
|
||||
outcomes=outcomes,
|
||||
description=description,
|
||||
required_capabilities=required_capabilities,
|
||||
source_bindings=source_bindings,
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
|
||||
# -- drafts --
|
||||
|
||||
async def validate_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.validate_draft(draft=draft)
|
||||
|
||||
async def compile_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.compile_draft(draft=draft)
|
||||
|
||||
async def patch_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.patch_draft(draft=draft, patch=patch)
|
||||
|
||||
# -- draft workspaces --
|
||||
|
||||
async def list_draft_workspaces(self) -> dict[str, Any]:
|
||||
return await self._handlers.list_draft_workspaces()
|
||||
|
||||
async def create_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
draft: dict[str, Any],
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_draft_workspace(
|
||||
workspace_id=workspace_id, draft=draft, title=title,
|
||||
)
|
||||
|
||||
async def get_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.get_draft_workspace(
|
||||
workspace_id=workspace_id, include_draft=include_draft,
|
||||
)
|
||||
|
||||
async def delete_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.delete_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def validate_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.validate_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def patch_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.patch_draft_workspace(
|
||||
workspace_id=workspace_id, revision=revision, patch=patch,
|
||||
)
|
||||
|
||||
async def set_draft_name(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.set_draft_name(
|
||||
workspace_id=workspace_id, revision=revision, name=name,
|
||||
)
|
||||
|
||||
async def set_draft_route(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.set_draft_route(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
outcome=outcome,
|
||||
target=target,
|
||||
)
|
||||
|
||||
async def set_step_input_map(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.set_step_input_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
input_map=input_map,
|
||||
)
|
||||
|
||||
async def set_step_output_map(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.set_step_output_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
output_map=output_map,
|
||||
)
|
||||
|
||||
async def create_minimal_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
name: str,
|
||||
capability_name: str,
|
||||
input_schema: dict[str, Any],
|
||||
state_schema: dict[str, Any],
|
||||
output_schema: dict[str, Any],
|
||||
input: Sequence[Any] | None = None,
|
||||
output: Sequence[Any] | None = None,
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_minimal_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
name=name,
|
||||
capability_name=capability_name,
|
||||
input_schema=input_schema,
|
||||
state_schema=state_schema,
|
||||
output_schema=output_schema,
|
||||
input=input,
|
||||
output=output,
|
||||
input_map=input_map,
|
||||
output_map=output_map,
|
||||
error_message_source=error_message_source,
|
||||
title=title,
|
||||
)
|
||||
|
||||
async def create_draft_workspace_from_capability(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
capability_name: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
input_schema: dict[str, Any] | None = None,
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
input: Sequence[Any] | None = None,
|
||||
output: Sequence[Any] | None = None,
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.create_draft_workspace_from_capability(
|
||||
workspace_id=workspace_id,
|
||||
capability_name=capability_name,
|
||||
name=name,
|
||||
title=title,
|
||||
input_schema=input_schema,
|
||||
state_schema=state_schema,
|
||||
output_schema=output_schema,
|
||||
input=input,
|
||||
output=output,
|
||||
input_map=input_map,
|
||||
output_map=output_map,
|
||||
error_message_source=error_message_source,
|
||||
)
|
||||
|
||||
# -- deployments --
|
||||
|
||||
async def list_deployments(self) -> dict[str, Any]:
|
||||
return await self._handlers.list_deployments()
|
||||
|
||||
async def inspect_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.inspect_deployment(deployment_id=deployment_id)
|
||||
|
||||
async def save_deployment(
|
||||
self,
|
||||
deployment: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.save_deployment(deployment)
|
||||
|
||||
async def delete_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.delete_deployment(deployment_id=deployment_id)
|
||||
|
||||
async def validate_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
live_check: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.validate_deployment(
|
||||
deployment_id=deployment_id, live_check=live_check,
|
||||
)
|
||||
|
||||
# -- runs --
|
||||
|
||||
async def run_deployment(
|
||||
self,
|
||||
*,
|
||||
deployment_id: str,
|
||||
workflow_input: dict[str, Any],
|
||||
trace_range: ApiTraceRange | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.run_deployment(
|
||||
deployment_id=deployment_id,
|
||||
workflow_input=workflow_input,
|
||||
trace_range=_to_handler_trace_range(trace_range) if trace_range is not None else None,
|
||||
)
|
||||
|
||||
async def resume_run(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
resume_payload: dict[str, Any],
|
||||
resume_outcome: str = "submitted",
|
||||
trace_range: ApiTraceRange | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.resume_run(
|
||||
run_id=run_id,
|
||||
resume_payload=resume_payload,
|
||||
resume_outcome=resume_outcome,
|
||||
trace_range=_to_handler_trace_range(trace_range) if trace_range is not None else None,
|
||||
)
|
||||
|
||||
async def inspect_run(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.inspect_run(run_id=run_id)
|
||||
|
||||
async def read_run_trace(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
trace_range: ApiTraceRange,
|
||||
) -> dict[str, Any]:
|
||||
return await self._handlers.read_run_trace(
|
||||
run_id=run_id, trace_range=_to_handler_trace_range(trace_range),
|
||||
)
|
||||
@@ -7,9 +7,10 @@ from pydantic import Field
|
||||
|
||||
from wf_artifacts import ArtifactKind
|
||||
from wf_artifacts.models import RequiredCapability
|
||||
from wf_api import WorkflowApi
|
||||
from wf_api.backend import TraceRange as ApiTraceRange
|
||||
from wf_mcp.broker.service import WfMcpService
|
||||
|
||||
from .handlers import WorkflowSurfaceHandlers
|
||||
from wf_mcp.broker.service.workflow_api_backend import WfMcpWorkflowApiBackend
|
||||
from .models import (
|
||||
CallCapabilityResult,
|
||||
CreateArtifactFromWorkspaceRequest,
|
||||
@@ -36,7 +37,10 @@ from .models import (
|
||||
|
||||
def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None:
|
||||
"""Register stable workflow tools on the public MCP server surface."""
|
||||
handlers = WorkflowSurfaceHandlers(service)
|
||||
handlers = WorkflowApi(WfMcpWorkflowApiBackend(service))
|
||||
|
||||
def _to_api_trace_range(tr: TraceRange) -> ApiTraceRange:
|
||||
return ApiTraceRange(start=tr.start, limit=tr.limit)
|
||||
|
||||
@server.tool(
|
||||
name="wf.workflow.list_artifacts",
|
||||
@@ -665,7 +669,7 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
||||
await handlers.run_deployment(
|
||||
deployment_id=deployment_id,
|
||||
workflow_input=workflow_input,
|
||||
trace_range=trace_range,
|
||||
trace_range=_to_api_trace_range(trace_range) if trace_range is not None else None,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -697,7 +701,7 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
||||
run_id=run_id,
|
||||
resume_payload=resume_payload,
|
||||
resume_outcome=resume_outcome,
|
||||
trace_range=trace_range,
|
||||
trace_range=_to_api_trace_range(trace_range) if trace_range is not None else None,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -734,6 +738,6 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
||||
return RunDeploymentResult.model_validate(
|
||||
await handlers.read_run_trace(
|
||||
run_id=run_id,
|
||||
trace_range=trace_range,
|
||||
trace_range=_to_api_trace_range(trace_range),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from wf_api import WorkflowApi
|
||||
from wf_cli.context import load_cli_context
|
||||
|
||||
|
||||
def test_load_cli_context_returns_workflow_api(tmp_path: Path) -> None:
|
||||
"""CliContext.handlers must be WorkflowApi, not WorkflowSurfaceHandlers."""
|
||||
root = tmp_path / "wf_cli_api_check"
|
||||
root.mkdir()
|
||||
config_path = root / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"store_root": ".wf_mcp_store",
|
||||
"connections": [
|
||||
{
|
||||
"id": "demo.personal",
|
||||
"server": "demo",
|
||||
"account": "personal",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
context = load_cli_context(config_path)
|
||||
|
||||
assert isinstance(context.handlers, WorkflowApi)
|
||||
assert hasattr(context.handlers, "backend")
|
||||
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_wf_api_has_no_wf_mcp_imports() -> None:
|
||||
"""wf_api must not import any wf_mcp modules."""
|
||||
wf_api_root = Path(__file__).resolve().parents[2] / "src" / "wf_api"
|
||||
violations: list[str] = []
|
||||
|
||||
for py_file in sorted(wf_api_root.rglob("*.py")):
|
||||
rel = py_file.relative_to(wf_api_root.parent)
|
||||
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
|
||||
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom) and node.module is not None:
|
||||
if node.module.startswith("wf_mcp") or node.module.startswith("wf_mcp."):
|
||||
violations.append(
|
||||
f"{module}:{node.lineno}: from {node.module} import ..."
|
||||
)
|
||||
elif isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
if alias.name.startswith("wf_mcp"):
|
||||
violations.append(f"{module}:{node.lineno}: import {alias.name}")
|
||||
|
||||
assert violations == [], (
|
||||
"wf_api imports wf_mcp — this breaks the dependency direction rule:\n"
|
||||
+ "\n".join(f" {v}" for v in violations)
|
||||
)
|
||||
@@ -30,4 +30,4 @@ def test_load_cli_context_builds_service_and_handlers(tmp_path: Path) -> None:
|
||||
|
||||
assert context.config_path == config_path
|
||||
assert context.service.connections.list_all()[0].id == "demo.personal"
|
||||
assert context.handlers.service is context.service
|
||||
assert context.handlers.backend._handlers.service is context.service # type: ignore[attr-defined]
|
||||
|
||||
Reference in New Issue
Block a user