feat: add source registry mutations
This commit is contained in:
@@ -26,6 +26,7 @@ from .service import WorkflowApi
|
||||
from .source_admin import WorkflowSourceAdminApi
|
||||
from .source_registry_admin import (
|
||||
WorkflowSourceRegistryApi,
|
||||
WorkflowSourceRegistryMutationProvider,
|
||||
WorkflowSourceRegistryProvider,
|
||||
)
|
||||
from .surface import (
|
||||
@@ -107,6 +108,7 @@ __all__ = [
|
||||
"WorkflowSourceAdminApi",
|
||||
"WorkflowSourceAdminSurface",
|
||||
"WorkflowSourceRegistryApi",
|
||||
"WorkflowSourceRegistryMutationProvider",
|
||||
"WorkflowSourceRegistryProvider",
|
||||
"WorkflowSourceRegistrySurface",
|
||||
"WorkflowSpecProvider",
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence, Set
|
||||
from dataclasses import asdict, is_dataclass
|
||||
from typing import Any, Protocol
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
from wf_platform import page_items
|
||||
|
||||
@@ -15,8 +15,22 @@ class WorkflowSourceRegistryProvider(Protocol):
|
||||
def config_source_ids(self) -> Set[str]: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class WorkflowSourceRegistryMutationProvider(Protocol):
|
||||
"""Write capabilities for source registry mutation operations."""
|
||||
|
||||
def add_registry_entry(self, entry: Mapping[str, Any]) -> Mapping[str, Any] | object: ...
|
||||
def update_registry_entry(
|
||||
self, source_id: str, patch: Mapping[str, Any]
|
||||
) -> Mapping[str, Any] | object: ...
|
||||
def set_registry_entry_enabled(
|
||||
self, source_id: str, enabled: bool
|
||||
) -> Mapping[str, Any] | object: ...
|
||||
def remove_registry_entry(self, source_id: str) -> Mapping[str, Any] | object: ...
|
||||
|
||||
|
||||
class WorkflowSourceRegistryApi:
|
||||
"""Protocol-neutral read-only desired source registry operations.
|
||||
"""Protocol-neutral desired source registry operations.
|
||||
|
||||
This surface is intentionally separate from WorkflowSourceAdminApi.
|
||||
WorkflowSourceAdminApi exposes observed/hydrated runtime source inventory.
|
||||
@@ -28,8 +42,13 @@ class WorkflowSourceRegistryApi:
|
||||
self,
|
||||
*,
|
||||
provider: WorkflowSourceRegistryProvider,
|
||||
mutation_provider: WorkflowSourceRegistryMutationProvider | None = None,
|
||||
) -> None:
|
||||
self._provider = provider
|
||||
self._mutation_provider = mutation_provider
|
||||
|
||||
def _is_shadowed(self, source_id: str) -> bool:
|
||||
return source_id in self._provider.config_source_ids()
|
||||
|
||||
async def list_registry_entries(
|
||||
self,
|
||||
@@ -37,10 +56,9 @@ class WorkflowSourceRegistryApi:
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
shadowed_ids = set(self._provider.config_source_ids())
|
||||
entries = sorted(
|
||||
(
|
||||
_entry_summary(_payload(item), shadowed_ids)
|
||||
_entry_summary(_payload(item), self._provider.config_source_ids())
|
||||
for item in self._provider.list_registry_entries()
|
||||
),
|
||||
key=lambda item: str(item.get("id", "")),
|
||||
@@ -57,16 +75,86 @@ class WorkflowSourceRegistryApi:
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
shadowed_ids = set(self._provider.config_source_ids())
|
||||
for item in self._provider.list_registry_entries():
|
||||
entry = _payload(item)
|
||||
if entry.get("id") == source_id:
|
||||
return {
|
||||
"entry": entry,
|
||||
"shadowed_by_config": source_id in shadowed_ids,
|
||||
"shadowed_by_config": self._is_shadowed(source_id),
|
||||
}
|
||||
raise KeyError(f"unknown registry source {source_id!r}")
|
||||
|
||||
async def add_registry_entry(
|
||||
self,
|
||||
*,
|
||||
entry: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("add_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.add_registry_entry(entry)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
|
||||
async def update_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
patch: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("update_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.update_registry_entry(source_id, patch)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
|
||||
async def enable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("enable_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.set_registry_entry_enabled(source_id, True)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
|
||||
async def disable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("disable_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.set_registry_entry_enabled(source_id, False)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
|
||||
async def remove_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("remove_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.remove_registry_entry(source_id)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"removed": bool(result.get("removed")),
|
||||
"source_id": str(result.get("source_id", source_id)),
|
||||
}
|
||||
|
||||
|
||||
def _payload(value: Mapping[str, Any] | object) -> dict[str, Any]:
|
||||
"""Normalize provider objects without depending on MCP registry types."""
|
||||
@@ -84,7 +172,7 @@ def _payload(value: Mapping[str, Any] | object) -> dict[str, Any]:
|
||||
)
|
||||
|
||||
|
||||
def _entry_summary(entry: dict[str, Any], shadowed_ids: set[str]) -> dict[str, Any]:
|
||||
def _entry_summary(entry: dict[str, Any], shadowed_ids: Set[str]) -> dict[str, Any]:
|
||||
transport = entry.get("transport")
|
||||
transport_kind = transport.get("kind") if isinstance(transport, Mapping) else None
|
||||
return {
|
||||
|
||||
+32
-1
@@ -227,7 +227,7 @@ class WorkflowAdminSurface(Protocol):
|
||||
|
||||
|
||||
class WorkflowSourceRegistrySurface(Protocol):
|
||||
"""Read-only desired source registry methods exposed by platform frontends."""
|
||||
"""Desired source registry methods exposed by platform frontends."""
|
||||
|
||||
async def list_registry_entries(
|
||||
self,
|
||||
@@ -242,6 +242,37 @@ class WorkflowSourceRegistrySurface(Protocol):
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def add_registry_entry(
|
||||
self,
|
||||
*,
|
||||
entry: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def update_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
patch: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def enable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def disable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
async def remove_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
|
||||
__all__ = [
|
||||
"WorkflowAdminSurface",
|
||||
|
||||
Reference in New Issue
Block a user