feat: type artifact operation results
This commit is contained in:
@@ -71,9 +71,9 @@
|
||||
- A 2026-07-30 spike confirmed that `fastapi-jsonrpc` already exports a
|
||||
complete OpenRPC document for all 70 registered methods. Request payloads
|
||||
retain useful Pydantic schemas, so OpenRPC is a viable transport input.
|
||||
- The first typed-result slice now gives `workflow.health` and all deployment
|
||||
and run operations named transport-neutral result schemas: 11 of 70
|
||||
methods. The remaining 59 success results still collapse to generic objects
|
||||
- Typed-result slices now give `workflow.health` and all artifact, deployment,
|
||||
and run operations named transport-neutral result schemas: 16 of 70
|
||||
methods. The remaining 54 success results still collapse to generic objects
|
||||
because their Python API and JSON-RPC handlers return `dict[str, Any]`.
|
||||
Continue introducing operation result DTOs before adopting generated
|
||||
TypeScript contracts.
|
||||
|
||||
+57
-31
@@ -24,9 +24,21 @@ from .artifact_refs import artifact_capability_id
|
||||
from .capability_requirements import observed_node_specs
|
||||
from .drafts import WorkflowDraftApi
|
||||
from .listing import matches_query, paged_list_payload
|
||||
from .models import RawWorkflowPlan
|
||||
from .models import (
|
||||
DeleteArtifactResult,
|
||||
JsonProjector,
|
||||
ListArtifactsResult,
|
||||
RawWorkflowPlan,
|
||||
SaveArtifactResult,
|
||||
WorkflowArtifactPayload,
|
||||
)
|
||||
from .operation_context import WorkflowOperationContext
|
||||
|
||||
_PROJECT_ARTIFACT = JsonProjector(WorkflowArtifactPayload)
|
||||
_PROJECT_ARTIFACT_LIST = JsonProjector(ListArtifactsResult)
|
||||
_PROJECT_ARTIFACT_SAVE = JsonProjector(SaveArtifactResult)
|
||||
_PROJECT_ARTIFACT_DELETE = JsonProjector(DeleteArtifactResult)
|
||||
|
||||
|
||||
class WorkflowArtifactApi:
|
||||
"""Saved workflow artifact operations.
|
||||
@@ -51,14 +63,16 @@ class WorkflowArtifactApi:
|
||||
kind: ArtifactKind | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
) -> ListArtifactsResult:
|
||||
"""Return compact paged saved artifact summaries.
|
||||
|
||||
Saved artifacts can contain full raw workflow plans, so list results
|
||||
deliberately stay summary-only. Use inspect/run tools for detail.
|
||||
"""
|
||||
if self.context.artifact_store is None:
|
||||
return paged_list_payload("nodes", [], cursor=cursor, limit=limit)
|
||||
return _PROJECT_ARTIFACT_LIST(
|
||||
paged_list_payload("nodes", [], cursor=cursor, limit=limit)
|
||||
)
|
||||
entries = [
|
||||
artifact_catalog_entry(artifact).model_dump(mode="json")
|
||||
for artifact in self.context.artifact_store.list_artifacts()
|
||||
@@ -77,9 +91,13 @@ class WorkflowArtifactApi:
|
||||
)
|
||||
]
|
||||
entries.sort(key=lambda entry: str(entry.get("name", "")))
|
||||
return paged_list_payload("nodes", entries, cursor=cursor, limit=limit)
|
||||
return _PROJECT_ARTIFACT_LIST(
|
||||
paged_list_payload("nodes", entries, cursor=cursor, limit=limit)
|
||||
)
|
||||
|
||||
async def save_artifact(self, artifact: dict[str, Any]) -> dict[str, Any]:
|
||||
async def save_artifact(
|
||||
self, artifact: dict[str, Any]
|
||||
) -> SaveArtifactResult:
|
||||
workflow_artifact = WorkflowArtifact.model_validate(artifact)
|
||||
self._artifact_store().save_artifact(workflow_artifact)
|
||||
self.context.events.record_workflow_event(
|
||||
@@ -90,11 +108,13 @@ class WorkflowArtifactApi:
|
||||
"version": workflow_artifact.version,
|
||||
},
|
||||
)
|
||||
return {
|
||||
"artifact_id": workflow_artifact.id,
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
}
|
||||
return _PROJECT_ARTIFACT_SAVE(
|
||||
{
|
||||
"artifact_id": workflow_artifact.id,
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
}
|
||||
)
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
self,
|
||||
@@ -109,7 +129,7 @@ class WorkflowArtifactApi:
|
||||
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]:
|
||||
) -> SaveArtifactResult:
|
||||
typed_plan = (
|
||||
plan
|
||||
if isinstance(plan, RawWorkflowPlan)
|
||||
@@ -141,11 +161,13 @@ class WorkflowArtifactApi:
|
||||
"created_from_plan": True,
|
||||
},
|
||||
)
|
||||
return {
|
||||
"artifact_id": workflow_artifact.id,
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
}
|
||||
return _PROJECT_ARTIFACT_SAVE(
|
||||
{
|
||||
"artifact_id": workflow_artifact.id,
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
}
|
||||
)
|
||||
|
||||
async def create_artifact_from_draft(
|
||||
self,
|
||||
@@ -274,35 +296,39 @@ class WorkflowArtifactApi:
|
||||
|
||||
async def inspect_artifact(
|
||||
self, *, artifact_id: str, version: int
|
||||
) -> dict[str, Any]:
|
||||
) -> WorkflowArtifactPayload:
|
||||
artifact = self._artifact_store().get_artifact(artifact_id, version)
|
||||
return artifact.model_dump(mode="json")
|
||||
return _PROJECT_ARTIFACT(artifact.model_dump(mode="json"))
|
||||
|
||||
async def delete_artifact(
|
||||
self, *, artifact_id: str, version: int
|
||||
) -> dict[str, Any]:
|
||||
) -> DeleteArtifactResult:
|
||||
store = self._artifact_store()
|
||||
blockers = store.deployments_for_artifact(artifact_id, version)
|
||||
blocker_ids = [deployment.id for deployment in blockers]
|
||||
if blocker_ids:
|
||||
return {
|
||||
"artifact_id": artifact_id,
|
||||
"version": version,
|
||||
"deleted": False,
|
||||
"blocked_by_deployments": blocker_ids,
|
||||
}
|
||||
return _PROJECT_ARTIFACT_DELETE(
|
||||
{
|
||||
"artifact_id": artifact_id,
|
||||
"version": version,
|
||||
"deleted": False,
|
||||
"blocked_by_deployments": blocker_ids,
|
||||
}
|
||||
)
|
||||
store.delete_artifact(artifact_id, version)
|
||||
self.context.events.record_workflow_event(
|
||||
"workflow_artifact_deleted",
|
||||
capability_id=f"{artifact_id}@{version}",
|
||||
payload={"artifact_id": artifact_id, "version": version},
|
||||
)
|
||||
return {
|
||||
"artifact_id": artifact_id,
|
||||
"version": version,
|
||||
"deleted": True,
|
||||
"blocked_by_deployments": [],
|
||||
}
|
||||
return _PROJECT_ARTIFACT_DELETE(
|
||||
{
|
||||
"artifact_id": artifact_id,
|
||||
"version": version,
|
||||
"deleted": True,
|
||||
"blocked_by_deployments": [],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _suggested_self_bindings(
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
"""Transport-neutral workflow API models."""
|
||||
|
||||
from .artifacts import (
|
||||
ArtifactCatalogEntryPayload,
|
||||
ArtifactKindPayload,
|
||||
CapabilityKindPayload,
|
||||
CapabilityRefPayload,
|
||||
DeleteArtifactResult,
|
||||
ListArtifactsResult,
|
||||
RequiredCapabilityPayload,
|
||||
SaveArtifactResult,
|
||||
WorkflowArtifactPayload,
|
||||
)
|
||||
from .common import (
|
||||
ArtifactVersionPayload,
|
||||
DependencyDiagnosticPayload,
|
||||
@@ -10,6 +21,7 @@ from .common import (
|
||||
JsonSchema,
|
||||
NextActionPatchExamplePayload,
|
||||
NextActionsPayload,
|
||||
PageMetadataPayload,
|
||||
RawWorkflowPlan,
|
||||
TraceRange,
|
||||
)
|
||||
@@ -37,6 +49,11 @@ from .runs import (
|
||||
|
||||
__all__ = [
|
||||
"ArtifactVersionPayload",
|
||||
"ArtifactCatalogEntryPayload",
|
||||
"ArtifactKindPayload",
|
||||
"CapabilityKindPayload",
|
||||
"CapabilityRefPayload",
|
||||
"DeleteArtifactResult",
|
||||
"DeleteDeploymentResult",
|
||||
"DependencyDiagnosticPayload",
|
||||
"DeploymentSummary",
|
||||
@@ -48,20 +65,25 @@ __all__ = [
|
||||
"JsonProjector",
|
||||
"JsonSchema",
|
||||
"ListDeploymentsResult",
|
||||
"ListArtifactsResult",
|
||||
"ListRunsResult",
|
||||
"NextActionPatchExamplePayload",
|
||||
"NextActionsPayload",
|
||||
"PageMetadataPayload",
|
||||
"RawWorkflowPlan",
|
||||
"ResumeReadiness",
|
||||
"RunResult",
|
||||
"RunStatus",
|
||||
"RunSummary",
|
||||
"RunTraceResult",
|
||||
"RequiredCapabilityPayload",
|
||||
"SaveArtifactResult",
|
||||
"SaveDeploymentResult",
|
||||
"SourceBindingPayload",
|
||||
"TraceRange",
|
||||
"TraceEntryPayload",
|
||||
"ValidateDeploymentResult",
|
||||
"WorkflowDeploymentPayload",
|
||||
"WorkflowArtifactPayload",
|
||||
"WorkflowRefPayload",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
from .common import (
|
||||
DependencyDiagnosticPayload,
|
||||
JsonObject,
|
||||
PageMetadataPayload,
|
||||
)
|
||||
|
||||
type ArtifactKindPayload = Literal["workflow", "wrapper"]
|
||||
type CapabilityKindPayload = Literal[
|
||||
"tool",
|
||||
"resource",
|
||||
"prompt",
|
||||
"node_spec",
|
||||
"reducer",
|
||||
"workflow",
|
||||
]
|
||||
|
||||
|
||||
class CapabilityRefPayload(TypedDict):
|
||||
source: str
|
||||
capability_key: str
|
||||
|
||||
|
||||
class RequiredCapabilityPayload(TypedDict):
|
||||
"""Saved dependency contract for one artifact capability reference."""
|
||||
|
||||
ref: CapabilityRefPayload
|
||||
kind: CapabilityKindPayload
|
||||
input_schema_hash: str | None
|
||||
input_schema_snapshot: JsonObject | None
|
||||
output_schema_hash: str | None
|
||||
output_schema_snapshot: JsonObject | None
|
||||
observed_concrete_source: str | None
|
||||
observed_at_epoch_ms: int | None
|
||||
|
||||
|
||||
class ArtifactCatalogEntryPayload(TypedDict):
|
||||
"""Compact artifact row returned by discovery operations."""
|
||||
|
||||
name: str
|
||||
artifact_id: str
|
||||
version: int
|
||||
kind: str
|
||||
display_name: str
|
||||
description: str | None
|
||||
outcomes: list[str]
|
||||
input_schema: JsonObject
|
||||
output_schema: JsonObject
|
||||
required_sources: list[str]
|
||||
diagnostics: list[DependencyDiagnosticPayload]
|
||||
|
||||
|
||||
class ListArtifactsResult(PageMetadataPayload):
|
||||
nodes: list[ArtifactCatalogEntryPayload]
|
||||
|
||||
|
||||
class WorkflowArtifactPayload(TypedDict):
|
||||
"""Normalized immutable artifact returned by inspect operations."""
|
||||
|
||||
id: str
|
||||
version: int
|
||||
title: str
|
||||
kind: ArtifactKindPayload
|
||||
description: str | None
|
||||
input_schema: JsonObject
|
||||
output_schema: JsonObject
|
||||
outcomes: list[str]
|
||||
plan: JsonObject
|
||||
required_capabilities: list[RequiredCapabilityPayload]
|
||||
workflow_dependencies: dict[str, int]
|
||||
created_from_catalog_version: str | None
|
||||
|
||||
|
||||
class SaveArtifactResult(TypedDict):
|
||||
artifact_id: str
|
||||
version: int
|
||||
saved: bool
|
||||
|
||||
|
||||
class DeleteArtifactResult(TypedDict):
|
||||
artifact_id: str
|
||||
version: int
|
||||
deleted: bool
|
||||
blocked_by_deployments: list[str]
|
||||
@@ -73,6 +73,13 @@ class GuidedResultPayload(TypedDict):
|
||||
next_actions: NextActionsPayload
|
||||
|
||||
|
||||
class PageMetadataPayload(TypedDict):
|
||||
"""Cursor metadata shared by compact workflow discovery responses."""
|
||||
|
||||
next_cursor: str | None
|
||||
total: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TraceRange:
|
||||
"""Caller-bounded debug trace slice for durable deployment runs."""
|
||||
|
||||
@@ -14,14 +14,18 @@ from .draft_authoring import RouteSource, WorkflowDraftAuthoringApi
|
||||
from .draft_updates import CapabilityStepUpdate
|
||||
from .drafts import WorkflowDraftApi
|
||||
from .models import (
|
||||
DeleteArtifactResult,
|
||||
DeleteDeploymentResult,
|
||||
ListArtifactsResult,
|
||||
ListDeploymentsResult,
|
||||
ListRunsResult,
|
||||
RawWorkflowPlan,
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
SaveArtifactResult,
|
||||
SaveDeploymentResult,
|
||||
ValidateDeploymentResult,
|
||||
WorkflowArtifactPayload,
|
||||
WorkflowDeploymentPayload,
|
||||
)
|
||||
from .operation_context import WorkflowOperationContext
|
||||
@@ -91,7 +95,7 @@ class WorkflowApi:
|
||||
kind: ArtifactKind | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
) -> ListArtifactsResult:
|
||||
return await self.artifacts.list_artifacts(
|
||||
query=query,
|
||||
kind=kind,
|
||||
@@ -104,7 +108,7 @@ class WorkflowApi:
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]:
|
||||
) -> WorkflowArtifactPayload:
|
||||
return await self.artifacts.inspect_artifact(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
@@ -115,7 +119,7 @@ class WorkflowApi:
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]:
|
||||
) -> DeleteArtifactResult:
|
||||
return await self.artifacts.delete_artifact(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
@@ -124,7 +128,7 @@ class WorkflowApi:
|
||||
async def save_artifact(
|
||||
self,
|
||||
artifact: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> SaveArtifactResult:
|
||||
return await self.artifacts.save_artifact(artifact)
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
@@ -140,7 +144,7 @@ class WorkflowApi:
|
||||
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]:
|
||||
) -> SaveArtifactResult:
|
||||
return await self.artifacts.create_artifact_from_plan(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
|
||||
+13
-4
@@ -10,13 +10,17 @@ from wf_core.models.steps import InputBinding, OutputBinding
|
||||
from .draft_authoring import RouteSource
|
||||
from .draft_updates import CapabilityStepUpdate
|
||||
from .models import (
|
||||
DeleteArtifactResult,
|
||||
DeleteDeploymentResult,
|
||||
ListArtifactsResult,
|
||||
ListDeploymentsResult,
|
||||
ListRunsResult,
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
SaveArtifactResult,
|
||||
SaveDeploymentResult,
|
||||
ValidateDeploymentResult,
|
||||
WorkflowArtifactPayload,
|
||||
WorkflowDeploymentPayload,
|
||||
)
|
||||
from .runs import TraceRangeLike
|
||||
@@ -355,21 +359,26 @@ class WorkflowArtifactSurface(Protocol):
|
||||
kind: ArtifactKind | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> ListArtifactsResult: ...
|
||||
|
||||
async def inspect_artifact(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> WorkflowArtifactPayload: ...
|
||||
|
||||
async def save_artifact(
|
||||
self,
|
||||
artifact: dict[str, Any],
|
||||
) -> SaveArtifactResult: ...
|
||||
|
||||
async def delete_artifact(
|
||||
self,
|
||||
*,
|
||||
artifact_id: str,
|
||||
version: int,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DeleteArtifactResult: ...
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
self,
|
||||
@@ -384,7 +393,7 @@ class WorkflowArtifactSurface(Protocol):
|
||||
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]: ...
|
||||
) -> SaveArtifactResult: ...
|
||||
|
||||
|
||||
class WorkflowDeploymentSurface(Protocol):
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
from wf_api.models import (
|
||||
DeleteArtifactResult,
|
||||
ListArtifactsResult,
|
||||
SaveArtifactResult,
|
||||
WorkflowArtifactPayload,
|
||||
)
|
||||
|
||||
from .base import RpcCaller
|
||||
|
||||
@@ -16,36 +23,48 @@ class RpcArtifactClientMixin:
|
||||
kind: Literal["workflow", "wrapper"] | None = None,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.artifacts.list",
|
||||
{
|
||||
"query": query,
|
||||
"kind": kind,
|
||||
"cursor": cursor,
|
||||
"limit": limit,
|
||||
},
|
||||
) -> ListArtifactsResult:
|
||||
return cast(
|
||||
ListArtifactsResult,
|
||||
await self._call(
|
||||
"workflow.artifacts.list",
|
||||
{
|
||||
"query": query,
|
||||
"kind": kind,
|
||||
"cursor": cursor,
|
||||
"limit": limit,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
async def inspect_artifact(
|
||||
self: RpcCaller, *, artifact_id: str, version: int
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.artifacts.inspect",
|
||||
{"artifact_id": artifact_id, "version": version},
|
||||
) -> WorkflowArtifactPayload:
|
||||
return cast(
|
||||
WorkflowArtifactPayload,
|
||||
await self._call(
|
||||
"workflow.artifacts.inspect",
|
||||
{"artifact_id": artifact_id, "version": version},
|
||||
),
|
||||
)
|
||||
|
||||
async def save_artifact(
|
||||
self: RpcCaller, artifact: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
return await self._call("workflow.artifacts.save", {"artifact": artifact})
|
||||
) -> SaveArtifactResult:
|
||||
return cast(
|
||||
SaveArtifactResult,
|
||||
await self._call("workflow.artifacts.save", {"artifact": artifact}),
|
||||
)
|
||||
|
||||
async def delete_artifact(
|
||||
self: RpcCaller, *, artifact_id: str, version: int
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.artifacts.delete",
|
||||
{"artifact_id": artifact_id, "version": version},
|
||||
) -> DeleteArtifactResult:
|
||||
return cast(
|
||||
DeleteArtifactResult,
|
||||
await self._call(
|
||||
"workflow.artifacts.delete",
|
||||
{"artifact_id": artifact_id, "version": version},
|
||||
),
|
||||
)
|
||||
|
||||
async def create_artifact_from_plan(
|
||||
@@ -61,19 +80,22 @@ class RpcArtifactClientMixin:
|
||||
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._call(
|
||||
"workflow.artifacts.create_from_plan",
|
||||
{
|
||||
"artifact_id": artifact_id,
|
||||
"version": version,
|
||||
"title": title,
|
||||
"plan": plan,
|
||||
"outcomes": list(outcomes),
|
||||
"kind": kind,
|
||||
"description": description,
|
||||
"required_capabilities": required_capabilities,
|
||||
"source_bindings": source_bindings,
|
||||
"created_from_catalog_version": created_from_catalog_version,
|
||||
},
|
||||
) -> SaveArtifactResult:
|
||||
return cast(
|
||||
SaveArtifactResult,
|
||||
await self._call(
|
||||
"workflow.artifacts.create_from_plan",
|
||||
{
|
||||
"artifact_id": artifact_id,
|
||||
"version": version,
|
||||
"title": title,
|
||||
"plan": plan,
|
||||
"outcomes": list(outcomes),
|
||||
"kind": kind,
|
||||
"description": description,
|
||||
"required_capabilities": required_capabilities,
|
||||
"source_bindings": source_bindings,
|
||||
"created_from_catalog_version": created_from_catalog_version,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
from __future__ import annotations
|
||||
"""Artifact JSON-RPC method registration.
|
||||
|
||||
from typing import Any
|
||||
Return annotations stay eagerly evaluated because fastapi-jsonrpc captures them
|
||||
while registering nested handlers for response validation and OpenRPC output.
|
||||
"""
|
||||
|
||||
import fastapi_jsonrpc as jsonrpc
|
||||
|
||||
from wf_api.models import (
|
||||
DeleteArtifactResult,
|
||||
ListArtifactsResult,
|
||||
SaveArtifactResult,
|
||||
WorkflowArtifactPayload,
|
||||
)
|
||||
from wf_server import WorkflowServer
|
||||
|
||||
from ..errors import WorkflowRpcError, raise_workflow_rpc_error
|
||||
@@ -29,7 +37,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_artifacts_create_from_plan(
|
||||
params: CreateArtifactFromPlanParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> SaveArtifactResult:
|
||||
try:
|
||||
return await server.api.create_artifact_from_plan(
|
||||
artifact_id=params.artifact_id,
|
||||
@@ -49,7 +57,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.artifacts.save", errors=[WorkflowRpcError])
|
||||
async def workflow_artifacts_save(
|
||||
params: SaveArtifactParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> SaveArtifactResult:
|
||||
try:
|
||||
return await server.api.save_artifact(params.artifact)
|
||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||
@@ -58,7 +66,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.artifacts.list", errors=[WorkflowRpcError])
|
||||
async def workflow_artifacts_list(
|
||||
params: ListArtifactsParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> ListArtifactsResult:
|
||||
try:
|
||||
return await server.api.list_artifacts(
|
||||
query=params.query,
|
||||
@@ -72,7 +80,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.artifacts.inspect", errors=[WorkflowRpcError])
|
||||
async def workflow_artifacts_inspect(
|
||||
params: InspectArtifactParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> WorkflowArtifactPayload:
|
||||
try:
|
||||
return await server.api.inspect_artifact(
|
||||
artifact_id=params.artifact_id,
|
||||
@@ -84,7 +92,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.artifacts.delete", errors=[WorkflowRpcError])
|
||||
async def workflow_artifacts_delete(
|
||||
params: DeleteArtifactParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DeleteArtifactResult:
|
||||
try:
|
||||
return await server.api.delete_artifact(
|
||||
artifact_id=params.artifact_id,
|
||||
|
||||
@@ -46,6 +46,59 @@ def test_openrpc_exposes_typed_health_result(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "component_name", "properties"),
|
||||
[
|
||||
(
|
||||
"workflow.artifacts.create_from_plan",
|
||||
"SaveArtifactResult",
|
||||
{"artifact_id", "version", "saved"},
|
||||
),
|
||||
(
|
||||
"workflow.artifacts.save",
|
||||
"SaveArtifactResult",
|
||||
{"artifact_id", "version", "saved"},
|
||||
),
|
||||
(
|
||||
"workflow.artifacts.list",
|
||||
"ListArtifactsResult",
|
||||
{"nodes", "next_cursor", "total"},
|
||||
),
|
||||
(
|
||||
"workflow.artifacts.inspect",
|
||||
"WorkflowArtifactPayload",
|
||||
{
|
||||
"id",
|
||||
"version",
|
||||
"title",
|
||||
"kind",
|
||||
"input_schema",
|
||||
"output_schema",
|
||||
"outcomes",
|
||||
"plan",
|
||||
},
|
||||
),
|
||||
(
|
||||
"workflow.artifacts.delete",
|
||||
"DeleteArtifactResult",
|
||||
{"artifact_id", "version", "deleted", "blocked_by_deployments"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_openrpc_exposes_typed_artifact_results(
|
||||
openrpc_document: dict[str, Any],
|
||||
method_name: str,
|
||||
component_name: str,
|
||||
properties: set[str],
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name=method_name,
|
||||
component_name=component_name,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "component_name", "properties"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user