feat: deliver Python workflow client

This commit is contained in:
lda
2026-08-31 02:49:07 +07:00 Verified
parent d53b96fd7c
commit 5315d4b66e
18 changed files with 717 additions and 19 deletions
+16 -10
View File
@@ -5,25 +5,27 @@ from .service import WorkflowApi
from .stores import WorkflowStores
def require_workflow_stores(context: WorkflowOperationContext) -> WorkflowStores:
def require_workflow_stores(
context: WorkflowOperationContext,
*,
drafts: bool = True,
) -> WorkflowStores:
"""Return required stores or fail before constructing durable frontends.
`WorkflowOperationContext` keeps stores optional for compatibility tests and
lightweight MCP surfaces. Durable API surfaces need all stores up front so a
run cannot start without somewhere to persist artifacts, drafts, and stopped
execution state.
lightweight MCP surfaces. Durable API surfaces need artifact/run stores up
front; a draft store is only required when draft APIs are enabled.
"""
missing = []
if context.artifact_store is None:
missing.append("artifact_store")
if context.draft_workspace_store is None:
if drafts and context.draft_workspace_store is None:
missing.append("draft_workspace_store")
if context.run_store is None:
missing.append("run_store")
if missing:
raise ValueError("durable workflow API requires stores: " + ", ".join(missing))
assert context.artifact_store is not None
assert context.draft_workspace_store is not None
assert context.run_store is not None
return WorkflowStores(
artifact_store=context.artifact_store,
@@ -32,10 +34,14 @@ def require_workflow_stores(context: WorkflowOperationContext) -> WorkflowStores
)
def durable_workflow_api(context: WorkflowOperationContext) -> WorkflowApi:
"""Construct a WorkflowApi only after durable store dependencies exist."""
require_workflow_stores(context)
return WorkflowApi(context)
def durable_workflow_api(
context: WorkflowOperationContext,
*,
drafts: bool = True,
) -> WorkflowApi:
"""Construct a durable API, optionally omitting the draft product surface."""
require_workflow_stores(context, drafts=drafts)
return WorkflowApi(context, drafts=drafts)
__all__ = ["durable_workflow_api", "require_workflow_stores"]
+32 -4
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from typing import Any, Literal, overload
from typing import Any, Literal, cast, overload
from wf_artifacts import ArtifactKind, compile_workflow_draft
from wf_artifacts.drafts.models import DraftStep
@@ -56,6 +56,19 @@ from .operation_context import WorkflowOperationContext
from .runs import TraceRangeLike, WorkflowRunApi
class _DisabledDraftSurface:
"""Placeholder that fails clearly if disabled draft methods are called.
Keeping this tiny seam avoids constructing draft services while preserving
the existing method layout on ``WorkflowApi`` for explicit draft callers.
"""
def __getattr__(self, name: str) -> Any:
raise RuntimeError(
"workflow draft APIs are disabled; compose WorkflowApi with drafts=True"
)
def _authoring_schema(
value: object,
*,
@@ -100,11 +113,26 @@ class WorkflowApi:
callers share one operation surface without importing wf_mcp.
"""
def __init__(self, context: WorkflowOperationContext) -> None:
def __init__(
self,
context: WorkflowOperationContext,
*,
drafts: bool = True,
) -> None:
self.context = context
self.capabilities = WorkflowCapabilityApi(context)
self.drafts = WorkflowDraftApi(context)
self.draft_authoring = WorkflowDraftAuthoringApi(context, self.drafts)
# ``drafts`` keeps server composition explicit: callers that omit draft
# storage must pass False, while the default preserves legacy direct
# WorkflowApi callers that use the draft service for validation only.
self.drafts_enabled = drafts
if self.drafts_enabled:
self.drafts = WorkflowDraftApi(context)
self.draft_authoring = WorkflowDraftAuthoringApi(context, self.drafts)
else:
self.drafts = cast(WorkflowDraftApi, _DisabledDraftSurface())
self.draft_authoring = cast(
WorkflowDraftAuthoringApi, _DisabledDraftSurface()
)
self.artifacts = WorkflowArtifactApi(context)
self.deployments = WorkflowDeploymentApi(context)
self.runs = WorkflowRunApi(context)
+1 -1
View File
@@ -18,7 +18,7 @@ class WorkflowStores:
"""Protocol-neutral persistence dependencies for workflow APIs."""
artifact_store: WorkflowArtifactStore
draft_workspace_store: DraftWorkspaceStore
draft_workspace_store: DraftWorkspaceStore | None
run_store: RunStore