generated from coulomb/repo-seed
Extension framework optimization
This commit is contained in:
@@ -78,21 +78,12 @@ class ExtensionExecutor:
|
||||
f"Extension `{extension_id}` returned {type(result).__name__}, expected ProcessingResult"
|
||||
)
|
||||
|
||||
result = _with_trace(result, ProcessingTrace(event="extension.executed", metadata={"id": extension_id}))
|
||||
result = result.with_trace(
|
||||
ProcessingTrace(event="extension.executed", metadata={"id": extension_id})
|
||||
)
|
||||
callbacks = self.lifecycle.after_success if result.valid else self.lifecycle.after_failure
|
||||
for callback in callbacks:
|
||||
callback(descriptor, request, result)
|
||||
for callback in self.lifecycle.after:
|
||||
callback(descriptor, request, result)
|
||||
return result
|
||||
|
||||
|
||||
def _with_trace(result: ProcessingResult, trace: ProcessingTrace) -> ProcessingResult:
|
||||
return ProcessingResult(
|
||||
output=result.output,
|
||||
diagnostics=result.diagnostics,
|
||||
provenance=result.provenance,
|
||||
dependencies=result.dependencies,
|
||||
trace=[*result.trace, trace],
|
||||
metadata=result.metadata,
|
||||
)
|
||||
|
||||
@@ -80,6 +80,25 @@ class ProcessingContext:
|
||||
}
|
||||
return _drop_empty(data)
|
||||
|
||||
def cache_key_material(self) -> dict[str, Any]:
|
||||
"""Return stable context fields that may affect deterministic output.
|
||||
|
||||
The workspace root, caller name, and backend handles are intentionally
|
||||
excluded: they are execution environment details, not document semantics.
|
||||
Extensions that need them in cache keys should include explicit values in
|
||||
request options or metadata.
|
||||
"""
|
||||
|
||||
return _drop_empty(
|
||||
{
|
||||
"source_path": str(self.source_path) if self.source_path else None,
|
||||
"namespaces": self.namespaces,
|
||||
"variables": self.variables,
|
||||
"policy": self.policy,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProcessingRequest:
|
||||
@@ -98,6 +117,7 @@ class ProcessingRequest:
|
||||
payload = {
|
||||
"operation": self.operation,
|
||||
"input": self.input,
|
||||
"context": self.context.cache_key_material(),
|
||||
"options": self.options,
|
||||
"scope": self.scope,
|
||||
"capabilities": [capability.to_dict() for capability in self.capabilities],
|
||||
@@ -148,6 +168,18 @@ class ProcessingResult:
|
||||
}
|
||||
return _drop_empty(data)
|
||||
|
||||
def with_trace(self, event: ProcessingTrace) -> "ProcessingResult":
|
||||
"""Return a copy with an additional trace event."""
|
||||
|
||||
return ProcessingResult(
|
||||
output=self.output,
|
||||
diagnostics=self.diagnostics,
|
||||
provenance=self.provenance,
|
||||
dependencies=self.dependencies,
|
||||
trace=[*self.trace, event],
|
||||
metadata=self.metadata,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_error(
|
||||
cls,
|
||||
|
||||
@@ -112,6 +112,8 @@ class ExtensionRegistry:
|
||||
|
||||
def __init__(self, descriptors: Iterable[ExtensionDescriptor] | None = None) -> None:
|
||||
self._descriptors: dict[str, ExtensionDescriptor] = {}
|
||||
self._by_kind: dict[str, set[str]] = {}
|
||||
self._by_capability: dict[str, set[str]] = {}
|
||||
for descriptor in descriptors or []:
|
||||
self.register(descriptor)
|
||||
|
||||
@@ -119,6 +121,9 @@ class ExtensionRegistry:
|
||||
if descriptor.id in self._descriptors:
|
||||
raise ExtensionRegistryError(f"Duplicate extension id `{descriptor.id}`")
|
||||
self._descriptors[descriptor.id] = descriptor
|
||||
self._by_kind.setdefault(descriptor.kind, set()).add(descriptor.id)
|
||||
for capability in descriptor.capabilities:
|
||||
self._by_capability.setdefault(capability.id, set()).add(descriptor.id)
|
||||
|
||||
def get(self, extension_id: str) -> ExtensionDescriptor:
|
||||
try:
|
||||
@@ -127,16 +132,16 @@ class ExtensionRegistry:
|
||||
raise ExtensionRegistryError(f"Unknown extension `{extension_id}`") from exc
|
||||
|
||||
def list(self, *, kind: str | None = None) -> list[ExtensionDescriptor]:
|
||||
descriptors = [self._descriptors[key] for key in sorted(self._descriptors)]
|
||||
if kind is None:
|
||||
return descriptors
|
||||
return [descriptor for descriptor in descriptors if descriptor.kind == kind]
|
||||
ids = sorted(self._descriptors)
|
||||
else:
|
||||
ids = sorted(self._by_kind.get(kind, set()))
|
||||
return [self._descriptors[key] for key in ids]
|
||||
|
||||
def require_capability(self, capability_id: str) -> list[ExtensionDescriptor]:
|
||||
return [
|
||||
descriptor
|
||||
for descriptor in self.list()
|
||||
if any(capability.id == capability_id for capability in descriptor.capabilities)
|
||||
self._descriptors[extension_id]
|
||||
for extension_id in sorted(self._by_capability.get(capability_id, set()))
|
||||
]
|
||||
|
||||
def check_dependencies(
|
||||
|
||||
Reference in New Issue
Block a user