schemarouter 0.2.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Protocol
5
+
6
+ from .errors import RegistrationError
7
+ from .models import EndpointSpec, ToolSpec
8
+
9
+
10
+ class ToolRegistry(Protocol):
11
+ """Structural contract for pluggable tool registries.
12
+
13
+ Read methods must return detached snapshots (or immutable equivalents) so callers cannot
14
+ mutate registry state without going through a versioned write operation.
15
+ """
16
+
17
+ @property
18
+ def version(self) -> int: ...
19
+
20
+ def register(self, tool: ToolSpec, *, replace: bool = False) -> str: ...
21
+
22
+ def get(self, key: str) -> ToolSpec: ...
23
+
24
+ def tools(self) -> tuple[ToolSpec, ...]: ...
25
+
26
+ def keys(self) -> tuple[str, ...]: ...
27
+
28
+ def endpoint(self, tool_key: str, endpoint_name: str) -> EndpointSpec: ...
29
+
30
+
31
+ class InMemoryRegistry:
32
+ """Versioned, collision-safe in-memory tool catalog with snapshot reads."""
33
+
34
+ def __init__(self) -> None:
35
+ self._tools: dict[str, ToolSpec] = {}
36
+ self._version = 0
37
+
38
+ @property
39
+ def version(self) -> int:
40
+ return self._version
41
+
42
+ @staticmethod
43
+ def _snapshot(tool: ToolSpec) -> ToolSpec:
44
+ return tool.model_copy(deep=True)
45
+
46
+ def register(self, tool: ToolSpec, *, replace: bool = False) -> str:
47
+ key = tool.key
48
+ if key in self._tools and not replace:
49
+ raise RegistrationError(f"tool {key!r} is already registered")
50
+ self._tools[key] = self._snapshot(tool)
51
+ self._version += 1
52
+ return key
53
+
54
+ def unregister(self, key: str) -> None:
55
+ if key not in self._tools:
56
+ raise KeyError(key)
57
+ del self._tools[key]
58
+ self._version += 1
59
+
60
+ def get(self, key: str) -> ToolSpec:
61
+ return self._snapshot(self._tools[key])
62
+
63
+ def tools(self) -> tuple[ToolSpec, ...]:
64
+ return tuple(self._snapshot(tool) for tool in self._tools.values())
65
+
66
+ def keys(self) -> tuple[str, ...]:
67
+ return tuple(self._tools)
68
+
69
+ def endpoint(self, tool_key: str, endpoint_name: str) -> EndpointSpec:
70
+ return self.get(tool_key).endpoint(endpoint_name)
71
+
72
+ def update_many(self, tools: Iterable[ToolSpec], *, replace: bool = False) -> None:
73
+ staged = list(tools)
74
+ staged_keys = [tool.key for tool in staged]
75
+ if len(staged_keys) != len(set(staged_keys)):
76
+ raise RegistrationError("duplicate tool keys in batch")
77
+ if not replace:
78
+ collisions = sorted(set(staged_keys) & set(self._tools))
79
+ if collisions:
80
+ raise RegistrationError(f"tools already registered: {', '.join(collisions)}")
81
+ for tool in staged:
82
+ self._tools[tool.key] = self._snapshot(tool)
83
+ if staged:
84
+ self._version += 1
schemarouter/runs.py ADDED
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Any, Literal
5
+
6
+ from pydantic import Field
7
+
8
+ from .models import StrictModel
9
+
10
+
11
+ class RetryPolicy(StrictModel):
12
+ """Deterministic retry policy applied at the trusted executor boundary."""
13
+
14
+ max_attempts: int = Field(default=1, ge=1, le=10)
15
+ initial_backoff_seconds: float = Field(default=0.0, ge=0.0, le=60.0)
16
+ backoff_multiplier: float = Field(default=2.0, ge=1.0, le=10.0)
17
+ max_backoff_seconds: float = Field(default=30.0, ge=0.0, le=300.0)
18
+ retry_non_read_only: bool = False
19
+
20
+
21
+ class RunConfig(StrictModel):
22
+ """Per-run metadata and execution controls."""
23
+
24
+ tags: list[str] = Field(default_factory=list)
25
+ metadata: dict[str, Any] = Field(default_factory=dict)
26
+ max_concurrency: int = Field(default=8, ge=1, le=128)
27
+ include_payloads: bool = False
28
+ retry: RetryPolicy = Field(default_factory=RetryPolicy)
29
+
30
+
31
+ RunEventName = Literal[
32
+ "run.start",
33
+ "plan.end",
34
+ "tool.start",
35
+ "tool.end",
36
+ "tool.error",
37
+ "run.end",
38
+ "run.error",
39
+ ]
40
+
41
+
42
+ class RunEvent(StrictModel):
43
+ """Typed event envelope emitted by SchemaRouter streaming APIs."""
44
+
45
+ event: RunEventName
46
+ run_id: str
47
+ sequence: int = Field(ge=0)
48
+ timestamp: datetime
49
+ tags: list[str] = Field(default_factory=list)
50
+ metadata: dict[str, Any] = Field(default_factory=dict)
51
+ tool: str | None = None
52
+ endpoint: str | None = None
53
+ data: dict[str, Any] = Field(default_factory=dict)
54
+
55
+ @classmethod
56
+ def create(
57
+ cls,
58
+ *,
59
+ event: RunEventName,
60
+ run_id: str,
61
+ sequence: int,
62
+ config: RunConfig,
63
+ tool: str | None = None,
64
+ endpoint: str | None = None,
65
+ data: dict[str, Any] | None = None,
66
+ ) -> RunEvent:
67
+ return cls(
68
+ event=event,
69
+ run_id=run_id,
70
+ sequence=sequence,
71
+ timestamp=datetime.now(timezone.utc),
72
+ tags=list(config.tags),
73
+ metadata=dict(config.metadata),
74
+ tool=tool,
75
+ endpoint=endpoint,
76
+ data=dict(data or {}),
77
+ )