feat(context): implement ScopeChainResolver protocol and plugin registration mechanism #10620
@@ -0,0 +1,82 @@
|
||||
Feature: Scope Chain Resolver Protocol and Plugin Registration
|
||||
As a developer
|
||||
I want to extend the default scope chain with custom resolvers
|
||||
So that I can implement custom scope resolution logic
|
||||
|
||||
Background:
|
||||
Given a fresh scope chain registry
|
||||
|
||||
Scenario: Register a custom scope resolver
|
||||
Given a custom scope resolver named "custom_resolver"
|
||||
When I register the resolver
|
||||
Then the resolver should be registered
|
||||
And the resolver should be in the registry
|
||||
|
||||
Scenario: Resolve scope with a single resolver
|
||||
Given a custom scope resolver named "test_resolver"
|
||||
And the resolver is registered
|
||||
And a scope context with project_id "proj1", actor_id "actor1", plan_id "plan1"
|
||||
When I resolve the scope
|
||||
Then the result should have project_id "proj1"
|
||||
And the result should have actor_id "actor1"
|
||||
And the result should have plan_id "plan1"
|
||||
|
||||
Scenario: Resolve scope with multiple resolvers in order
|
||||
Given a custom scope resolver named "resolver1"
|
||||
And a custom scope resolver named "resolver2"
|
||||
And both resolvers are registered
|
||||
And a scope context with project_id "proj1", actor_id "actor1", plan_id "plan1"
|
||||
When I resolve the scope with resolvers in order
|
||||
Then the result should have project_id "proj1"
|
||||
And the result should have actor_id "actor1"
|
||||
And the result should have plan_id "plan1"
|
||||
|
||||
Scenario: Unregister a scope resolver
|
||||
Given a custom scope resolver named "temp_resolver"
|
||||
And the resolver is registered
|
||||
When I unregister the resolver
|
||||
Then the resolver should not be in the registry
|
||||
|
||||
Scenario: List all registered resolvers
|
||||
Given a custom scope resolver named "resolver1"
|
||||
And a custom scope resolver named "resolver2"
|
||||
And both resolvers are registered
|
||||
When I list all resolvers
|
||||
Then the list should contain "resolver1"
|
||||
And the list should contain "resolver2"
|
||||
|
||||
Scenario: Resolver modifies scope context
|
||||
Given a custom scope resolver that modifies project_id
|
||||
And the resolver is registered
|
||||
And a scope context with project_id "original", actor_id "actor1", plan_id "plan1"
|
||||
When I resolve the scope
|
||||
Then the result should have project_id "modified"
|
||||
|
||||
Scenario: Resolver adds metadata
|
||||
Given a custom scope resolver that adds metadata
|
||||
And the resolver is registered
|
||||
And a scope context with project_id "proj1", actor_id "actor1", plan_id "plan1"
|
||||
When I resolve the scope
|
||||
Then the result metadata should contain "custom_key"
|
||||
And the metadata value should be "custom_value"
|
||||
|
||||
Scenario: Error handling for unregistered resolver
|
||||
Given a scope context with project_id "proj1", actor_id "actor1", plan_id "plan1"
|
||||
When I try to resolve with unregistered resolver "nonexistent"
|
||||
Then an error should be raised
|
||||
And the error message should contain "not registered"
|
||||
|
||||
Scenario: Clear all resolvers
|
||||
Given a custom scope resolver named "resolver1"
|
||||
And a custom scope resolver named "resolver2"
|
||||
And both resolvers are registered
|
||||
When I clear all resolvers
|
||||
Then the registry should be empty
|
||||
And the list of resolvers should be empty
|
||||
|
||||
Scenario: Resolver execution order is preserved
|
||||
Given resolvers that track execution order
|
||||
And the resolvers are registered in order "first", "second", "third"
|
||||
And a scope context with project_id "proj1", actor_id "actor1", plan_id "plan1"
|
||||
When I resolve the scope
|
||||
Then the execution order should be "first", "second", "third"
|
||||
@@ -0,0 +1,306 @@
|
||||
"""Step definitions for scope chain resolver tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
from behave import given, then, when
|
||||
|
||||
from cleveragents.context import (
|
||||
ScopeChainRegistry,
|
||||
ScopeContext,
|
||||
ScopeResult,
|
||||
)
|
||||
|
||||
|
||||
class TestResolver:
|
||||
"""Test resolver implementation."""
|
||||
|
||||
def __init__(self, name: str = "test") -> None:
|
||||
"""Initialize test resolver."""
|
||||
self.name = name
|
||||
self.execution_count = 0
|
||||
|
||||
def resolve(self, context: ScopeContext) -> ScopeResult:
|
||||
"""Resolve scope."""
|
||||
self.execution_count += 1
|
||||
return ScopeResult(
|
||||
project_id=context.project_id,
|
||||
actor_id=context.actor_id,
|
||||
plan_id=context.plan_id,
|
||||
metadata=context.metadata.copy(),
|
||||
)
|
||||
|
||||
|
||||
class ModifyingResolver:
|
||||
"""Resolver that modifies the scope."""
|
||||
|
||||
def resolve(self, context: ScopeContext) -> ScopeResult:
|
||||
"""Resolve scope and modify project_id."""
|
||||
return ScopeResult(
|
||||
project_id="modified",
|
||||
actor_id=context.actor_id,
|
||||
plan_id=context.plan_id,
|
||||
metadata=context.metadata.copy(),
|
||||
)
|
||||
|
||||
|
||||
class MetadataResolver:
|
||||
"""Resolver that adds metadata."""
|
||||
|
||||
def resolve(self, context: ScopeContext) -> ScopeResult:
|
||||
"""Resolve scope and add metadata."""
|
||||
metadata = context.metadata.copy()
|
||||
metadata["custom_key"] = "custom_value"
|
||||
return ScopeResult(
|
||||
project_id=context.project_id,
|
||||
actor_id=context.actor_id,
|
||||
plan_id=context.plan_id,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
class TrackingResolver:
|
||||
"""Resolver that tracks execution order."""
|
||||
|
||||
execution_order: ClassVar[list[str]] = []
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
"""Initialize tracking resolver."""
|
||||
self.name = name
|
||||
|
||||
def resolve(self, context: ScopeContext) -> ScopeResult:
|
||||
"""Resolve scope and track execution."""
|
||||
TrackingResolver.execution_order.append(self.name)
|
||||
return ScopeResult(
|
||||
project_id=context.project_id,
|
||||
actor_id=context.actor_id,
|
||||
plan_id=context.plan_id,
|
||||
metadata=context.metadata.copy(),
|
||||
)
|
||||
|
||||
|
||||
@given("a fresh scope chain registry")
|
||||
def step_fresh_registry(context: object) -> None:
|
||||
"""Create a fresh registry."""
|
||||
context.registry = ScopeChainRegistry() # type: ignore
|
||||
|
||||
|
||||
@given('a custom scope resolver named "{name}"')
|
||||
def step_custom_resolver(context: object, name: str) -> None:
|
||||
"""Create a custom resolver."""
|
||||
if not hasattr(context, "resolvers"): # type: ignore
|
||||
context.resolvers = {} # type: ignore
|
||||
context.resolvers[name] = TestResolver(name) # type: ignore
|
||||
|
||||
|
||||
@when("I register the resolver")
|
||||
def step_register_resolver(context: object) -> None:
|
||||
"""Register the resolver."""
|
||||
# Get the last created resolver
|
||||
resolvers = context.resolvers # type: ignore
|
||||
name = list(resolvers.keys())[-1]
|
||||
resolver = resolvers[name]
|
||||
context.registry.register(name, resolver) # type: ignore
|
||||
|
||||
|
||||
@then("the resolver should be registered")
|
||||
def step_resolver_registered(context: object) -> None:
|
||||
"""Check resolver is registered."""
|
||||
resolvers = context.resolvers # type: ignore
|
||||
name = list(resolvers.keys())[-1]
|
||||
assert context.registry.get(name) is not None # type: ignore
|
||||
|
||||
|
||||
@then("the resolver should be in the registry")
|
||||
def step_resolver_in_registry(context: object) -> None:
|
||||
"""Check resolver is in registry."""
|
||||
resolvers = context.resolvers # type: ignore
|
||||
name = list(resolvers.keys())[-1]
|
||||
assert name in context.registry.list_resolvers() # type: ignore
|
||||
|
||||
|
||||
@given(
|
||||
'a scope context with project_id "{project_id}", actor_id "{actor_id}", plan_id "{plan_id}"'
|
||||
)
|
||||
def step_scope_context(
|
||||
context: object, project_id: str, actor_id: str, plan_id: str
|
||||
) -> None:
|
||||
"""Create a scope context."""
|
||||
context.scope_context = ScopeContext( # type: ignore
|
||||
project_id=project_id, actor_id=actor_id, plan_id=plan_id
|
||||
)
|
||||
|
||||
|
||||
@when("I resolve the scope")
|
||||
def step_resolve_scope(context: object) -> None:
|
||||
"""Resolve the scope."""
|
||||
context.result = context.registry.resolve(context.scope_context) # type: ignore
|
||||
|
||||
|
||||
@then('the result should have project_id "{project_id}"')
|
||||
def step_check_project_id(context: object, project_id: str) -> None:
|
||||
"""Check project_id in result."""
|
||||
assert context.result.project_id == project_id # type: ignore
|
||||
|
||||
|
||||
@then('the result should have actor_id "{actor_id}"')
|
||||
def step_check_actor_id(context: object, actor_id: str) -> None:
|
||||
"""Check actor_id in result."""
|
||||
assert context.result.actor_id == actor_id # type: ignore
|
||||
|
||||
|
||||
@then('the result should have plan_id "{plan_id}"')
|
||||
def step_check_plan_id(context: object, plan_id: str) -> None:
|
||||
"""Check plan_id in result."""
|
||||
assert context.result.plan_id == plan_id # type: ignore
|
||||
|
||||
|
||||
@given("a custom scope resolver that modifies project_id")
|
||||
def step_modifying_resolver(context: object) -> None:
|
||||
"""Create a resolver that modifies project_id."""
|
||||
if not hasattr(context, "resolvers"): # type: ignore
|
||||
context.resolvers = {} # type: ignore
|
||||
context.resolvers["modifier"] = ModifyingResolver() # type: ignore
|
||||
|
||||
|
||||
@given("a custom scope resolver that adds metadata")
|
||||
def step_metadata_resolver(context: object) -> None:
|
||||
"""Create a resolver that adds metadata."""
|
||||
if not hasattr(context, "resolvers"): # type: ignore
|
||||
context.resolvers = {} # type: ignore
|
||||
context.resolvers["metadata"] = MetadataResolver() # type: ignore
|
||||
|
||||
|
||||
@then('the result metadata should contain "{key}"')
|
||||
def step_check_metadata_key(context: object, key: str) -> None:
|
||||
"""Check metadata contains key."""
|
||||
assert key in context.result.metadata # type: ignore
|
||||
|
||||
|
||||
@then('the metadata value should be "{value}"')
|
||||
def step_check_metadata_value(context: object, value: str) -> None:
|
||||
"""Check metadata value."""
|
||||
assert context.result.metadata.get("custom_key") == value # type: ignore
|
||||
|
||||
|
||||
@when('I try to resolve with unregistered resolver "{name}"')
|
||||
def step_resolve_unregistered(context: object, name: str) -> None:
|
||||
"""Try to resolve with unregistered resolver."""
|
||||
try:
|
||||
context.registry.resolve( # type: ignore
|
||||
context.scope_context, [name] # type: ignore
|
||||
)
|
||||
context.error_raised = False # type: ignore
|
||||
except ValueError as e:
|
||||
context.error_raised = True # type: ignore
|
||||
context.error_message = str(e) # type: ignore
|
||||
|
||||
|
||||
@then("an error should be raised")
|
||||
def step_error_raised(context: object) -> None:
|
||||
"""Check error was raised."""
|
||||
assert context.error_raised is True # type: ignore
|
||||
|
||||
|
||||
@then('the error message should contain "{text}"')
|
||||
def step_check_error_message(context: object, text: str) -> None:
|
||||
"""Check error message contains text."""
|
||||
assert text in context.error_message # type: ignore
|
||||
|
||||
|
||||
@when("I unregister the resolver")
|
||||
def step_unregister_resolver(context: object) -> None:
|
||||
"""Unregister the resolver."""
|
||||
resolvers = context.resolvers # type: ignore
|
||||
name = list(resolvers.keys())[-1]
|
||||
context.registry.unregister(name) # type: ignore
|
||||
|
||||
|
||||
@then("the resolver should not be in the registry")
|
||||
def step_resolver_not_in_registry(context: object) -> None:
|
||||
"""Check resolver is not in registry."""
|
||||
resolvers = context.resolvers # type: ignore
|
||||
name = list(resolvers.keys())[-1]
|
||||
assert name not in context.registry.list_resolvers() # type: ignore
|
||||
|
||||
|
||||
@when("I list all resolvers")
|
||||
def step_list_resolvers(context: object) -> None:
|
||||
"""List all resolvers."""
|
||||
context.resolver_list = context.registry.list_resolvers() # type: ignore
|
||||
|
||||
|
||||
@then('the list should contain "{name}"')
|
||||
def step_check_resolver_in_list(context: object, name: str) -> None:
|
||||
"""Check resolver is in list."""
|
||||
assert name in context.resolver_list # type: ignore
|
||||
|
||||
|
||||
@given("a custom scope resolver named {name}")
|
||||
def step_custom_resolver_alt(context: object, name: str) -> None:
|
||||
"""Create a custom resolver (alternative)."""
|
||||
if not hasattr(context, "resolvers"): # type: ignore
|
||||
context.resolvers = {} # type: ignore
|
||||
context.resolvers[name] = TestResolver(name) # type: ignore
|
||||
|
||||
|
||||
@given("both resolvers are registered")
|
||||
def step_register_both(context: object) -> None:
|
||||
"""Register both resolvers."""
|
||||
resolvers = context.resolvers # type: ignore
|
||||
for name, resolver in resolvers.items():
|
||||
context.registry.register(name, resolver) # type: ignore
|
||||
|
||||
|
||||
@when("I resolve the scope with resolvers in order")
|
||||
def step_resolve_with_order(context: object) -> None:
|
||||
"""Resolve scope with resolvers in order."""
|
||||
context.result = context.registry.resolve(context.scope_context) # type: ignore
|
||||
|
||||
|
||||
@when("I clear all resolvers")
|
||||
def step_clear_resolvers(context: object) -> None:
|
||||
"""Clear all resolvers."""
|
||||
context.registry.clear() # type: ignore
|
||||
|
||||
|
||||
@then("the registry should be empty")
|
||||
def step_registry_empty(context: object) -> None:
|
||||
"""Check registry is empty."""
|
||||
assert len(context.registry.list_resolvers()) == 0 # type: ignore
|
||||
|
||||
|
||||
@then("the list of resolvers should be empty")
|
||||
def step_resolver_list_empty(context: object) -> None:
|
||||
"""Check resolver list is empty."""
|
||||
assert context.resolver_list == [] # type: ignore
|
||||
|
||||
|
||||
@given("resolvers that track execution order")
|
||||
def step_tracking_resolvers(context: object) -> None:
|
||||
"""Create resolvers that track execution order."""
|
||||
TrackingResolver.execution_order = []
|
||||
if not hasattr(context, "resolvers"): # type: ignore
|
||||
context.resolvers = {} # type: ignore
|
||||
context.resolvers["first"] = TrackingResolver("first") # type: ignore
|
||||
context.resolvers["second"] = TrackingResolver("second") # type: ignore
|
||||
context.resolvers["third"] = TrackingResolver("third") # type: ignore
|
||||
|
||||
|
||||
@given('the resolvers are registered in order "{first}", "{second}", "{third}"')
|
||||
def step_register_in_order(context: object, first: str, second: str, third: str) -> None:
|
||||
"""Register resolvers in order."""
|
||||
resolvers = context.resolvers # type: ignore
|
||||
context.registry.register(first, resolvers[first]) # type: ignore
|
||||
context.registry.register(second, resolvers[second]) # type: ignore
|
||||
context.registry.register(third, resolvers[third]) # type: ignore
|
||||
|
||||
|
||||
@then('the execution order should be "{first}", "{second}", "{third}"')
|
||||
def step_check_execution_order(
|
||||
context: object, first: str, second: str, third: str
|
||||
) -> None:
|
||||
"""Check execution order."""
|
||||
expected = [first, second, third]
|
||||
assert TrackingResolver.execution_order == expected # type: ignore
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Context module for scope chain resolution.
|
||||
|
||||
Provides the ScopeChainResolver protocol and plugin registration mechanism
|
||||
for extending the default project → actor → plan scope chain.
|
||||
"""
|
||||
|
||||
from .protocols import ScopeChainResolver, ScopeContext, ScopeResult
|
||||
from .registry import ScopeChainRegistry
|
||||
|
||||
__all__ = [
|
||||
"ScopeChainRegistry",
|
||||
"ScopeChainResolver",
|
||||
"ScopeContext",
|
||||
"ScopeResult",
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Protocol definitions for scope chain resolution.
|
||||
|
||||
Defines the ScopeChainResolver protocol that allows users to extend
|
||||
the default project → actor → plan scope chain with custom resolvers.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ScopeContext(BaseModel):
|
||||
"""Context information passed to scope resolvers.
|
||||
|
||||
Contains the current scope state and metadata needed for resolution.
|
||||
"""
|
||||
|
||||
project_id: str | None = None
|
||||
actor_id: str | None = None
|
||||
plan_id: str | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
class Config:
|
||||
"""Pydantic configuration."""
|
||||
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class ScopeResult(BaseModel):
|
||||
"""Result of scope resolution.
|
||||
|
||||
Contains the resolved scope values and any additional metadata.
|
||||
"""
|
||||
|
||||
project_id: str | None = None
|
||||
actor_id: str | None = None
|
||||
plan_id: str | None = None
|
||||
metadata: dict[str, Any] = {}
|
||||
resolved: bool = True
|
||||
|
||||
class Config:
|
||||
"""Pydantic configuration."""
|
||||
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class ScopeChainResolver(Protocol):
|
||||
"""Protocol for custom scope chain resolvers.
|
||||
|
||||
Allows users to extend the default project → actor → plan scope chain
|
||||
with custom resolution logic. Resolvers are called in configured order
|
||||
and can modify or enhance the scope context.
|
||||
|
||||
Example:
|
||||
class CustomScopeResolver:
|
||||
def resolve(self, context: ScopeContext) -> ScopeResult:
|
||||
# Custom resolution logic
|
||||
result = ScopeResult(
|
||||
project_id=context.project_id,
|
||||
actor_id=context.actor_id,
|
||||
plan_id=context.plan_id,
|
||||
)
|
||||
return result
|
||||
"""
|
||||
|
||||
def resolve(self, context: ScopeContext) -> ScopeResult:
|
||||
"""Resolve scope based on the provided context.
|
||||
|
||||
Args:
|
||||
context: The current scope context containing project, actor,
|
||||
and plan IDs along with metadata.
|
||||
|
||||
Returns:
|
||||
ScopeResult containing the resolved scope values and metadata.
|
||||
"""
|
||||
...
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Registry for scope chain resolvers.
|
||||
|
||||
Manages loading and validation of custom scope resolvers from entry points
|
||||
and configuration.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.metadata
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from .protocols import ScopeChainResolver, ScopeContext, ScopeResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScopeChainRegistry:
|
||||
"""Registry for managing scope chain resolvers.
|
||||
|
||||
Loads resolvers from Python entry points and manages their execution
|
||||
in configured order.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the registry."""
|
||||
self._resolvers: dict[str, ScopeChainResolver] = {}
|
||||
self._resolver_order: list[str] = []
|
||||
|
||||
def register(self, name: str, resolver: ScopeChainResolver) -> None:
|
||||
"""Register a scope chain resolver.
|
||||
|
||||
Args:
|
||||
name: Unique identifier for the resolver.
|
||||
resolver: The resolver instance implementing ScopeChainResolver.
|
||||
|
||||
Raises:
|
||||
ValueError: If a resolver with the same name is already registered.
|
||||
"""
|
||||
if name in self._resolvers:
|
||||
msg = f"Resolver '{name}' is already registered"
|
||||
raise ValueError(msg)
|
||||
self._resolvers[name] = resolver
|
||||
self._resolver_order.append(name)
|
||||
logger.debug(f"Registered scope resolver: {name}")
|
||||
|
||||
def unregister(self, name: str) -> None:
|
||||
"""Unregister a scope chain resolver.
|
||||
|
||||
Args:
|
||||
name: Identifier of the resolver to unregister.
|
||||
|
||||
Raises:
|
||||
ValueError: If the resolver is not registered.
|
||||
"""
|
||||
if name not in self._resolvers:
|
||||
msg = f"Resolver '{name}' is not registered"
|
||||
raise ValueError(msg)
|
||||
del self._resolvers[name]
|
||||
self._resolver_order.remove(name)
|
||||
logger.debug(f"Unregistered scope resolver: {name}")
|
||||
|
||||
def get(self, name: str) -> ScopeChainResolver | None:
|
||||
"""Get a registered resolver by name.
|
||||
|
||||
Args:
|
||||
name: Identifier of the resolver.
|
||||
|
||||
Returns:
|
||||
The resolver instance or None if not found.
|
||||
"""
|
||||
return self._resolvers.get(name)
|
||||
|
||||
def list_resolvers(self) -> list[str]:
|
||||
"""List all registered resolver names in order.
|
||||
|
||||
Returns:
|
||||
List of resolver names in execution order.
|
||||
"""
|
||||
return self._resolver_order.copy()
|
||||
|
||||
def resolve(
|
||||
self, context: ScopeContext, resolver_names: list[str] | None = None
|
||||
) -> ScopeResult:
|
||||
"""Execute resolvers in order to resolve scope.
|
||||
|
||||
Args:
|
||||
context: The scope context to resolve.
|
||||
resolver_names: Optional list of resolver names to execute.
|
||||
If None, all registered resolvers are executed in order.
|
||||
|
||||
Returns:
|
||||
The final ScopeResult after all resolvers have been applied.
|
||||
|
||||
Raises:
|
||||
ValueError: If a specified resolver is not registered.
|
||||
"""
|
||||
result = ScopeResult(
|
||||
project_id=context.project_id,
|
||||
actor_id=context.actor_id,
|
||||
plan_id=context.plan_id,
|
||||
metadata=context.metadata.copy(),
|
||||
)
|
||||
|
||||
resolvers_to_run = resolver_names or self._resolver_order
|
||||
|
||||
for resolver_name in resolvers_to_run:
|
||||
if resolver_name not in self._resolvers:
|
||||
msg = f"Resolver '{resolver_name}' is not registered"
|
||||
raise ValueError(msg)
|
||||
|
||||
resolver = self._resolvers[resolver_name]
|
||||
# Create a new context from the current result
|
||||
current_context = ScopeContext(
|
||||
project_id=result.project_id,
|
||||
actor_id=result.actor_id,
|
||||
plan_id=result.plan_id,
|
||||
metadata=result.metadata.copy(),
|
||||
)
|
||||
|
||||
try:
|
||||
result = resolver.resolve(current_context)
|
||||
logger.debug(f"Resolver '{resolver_name}' executed successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing resolver '{resolver_name}': {e}")
|
||||
raise
|
||||
|
||||
return result
|
||||
|
||||
def load_from_entry_points(self) -> None:
|
||||
"""Load resolvers from Python entry points.
|
||||
|
||||
Loads all resolvers registered under the 'cleveragents.scope_resolvers'
|
||||
entry point group.
|
||||
"""
|
||||
try:
|
||||
entry_points = importlib.metadata.entry_points()
|
||||
# Handle both old and new entry_points API
|
||||
if sys.version_info >= (3, 10):
|
||||
# Python 3.10+ API
|
||||
scope_resolvers = entry_points.select(
|
||||
group="cleveragents.scope_resolvers"
|
||||
)
|
||||
else:
|
||||
# Python 3.9 API
|
||||
scope_resolvers = entry_points.get("cleveragents.scope_resolvers", [])
|
||||
|
||||
for ep in scope_resolvers:
|
||||
try:
|
||||
resolver_class = ep.load()
|
||||
resolver_instance = resolver_class()
|
||||
self.register(ep.name, resolver_instance)
|
||||
logger.info(f"Loaded scope resolver from entry point: {ep.name}")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to load scope resolver '{ep.name}' from entry "
|
||||
f"point: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading scope resolvers from entry points: {e}")
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered resolvers."""
|
||||
self._resolvers.clear()
|
||||
self._resolver_order.clear()
|
||||
logger.debug("Cleared all scope resolvers")
|
||||
Reference in New Issue
Block a user