Extension framework optimization

This commit is contained in:
2026-05-04 11:49:39 +02:00
parent 32f4af8359
commit 33fa602fe5
7 changed files with 208 additions and 18 deletions

View File

@@ -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,
)

View File

@@ -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,

View File

@@ -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(