Source code for toolregistry.class_tool_integration

"""Integration for registering class-based tools as tools.

This module provides functionality to scan a Python class and register its
methods as tools. If the class only contains static methods, they are registered
directly. If there are instance methods or other non-static attributes, the class
will be instantiated and its callable public methods will be registered.

Example:
    >>> from toolregistry import ToolRegistry
    >>> registry = ToolRegistry()
    >>> registry.register_class_tool(MyClass)
    >>> registry.get_available_tools()
    ['MyClass.method1', 'MyClass.method2', ...]
"""

from typing import Type, Union

from .tool_registry import ToolRegistry


from typing import Type, Optional, Union, Protocol, Callable


def _is_all_static_methods(cls: Type) -> bool:
    """
    Determines if all the methods of a given class are static methods.

    Args:
        cls (Type): The class to check.

    Returns:
        bool: True if all non-private methods of the class are static methods; otherwise, False.
    """
    for name, member in cls.__dict__.items():
        if not name.startswith("_") and not isinstance(member, staticmethod):
            return False
    return True


def _determine_namespace(
    cls_or_inst: Union[Type, object], with_ns: Union[str, bool]
) -> Optional[str]:
    """
    Determines the namespace to use based on the class or instance and the `with_ns` parameter.

    Args:
        cls_or_inst (Union[Type, object]): The class or instance to derive the namespace from.
        with_ns (Union[str, bool]): Either a string representing the namespace,
                                    True for using the class or instance name,
                                    or False for no namespace.

    Returns:
        Optional[str]: The derived namespace, or None if `with_ns` is False.
    """
    if isinstance(with_ns, str):
        return with_ns
    elif with_ns:
        if isinstance(cls_or_inst, type):
            return cls_or_inst.__name__
        else:
            return type(cls_or_inst).__name__
    else:
        return None


def _register_static_methods(
    cls: Type, registry: ToolRegistry, namespace: Optional[str]
) -> None:
    """
    Registers all static methods of a class into the provided registry.

    Args:
        cls (Type): The class whose static methods will be registered.
        registry (ToolRegistry): The registry object to register the methods into.
                             It is expected to have a `register` method.
        namespace (Optional[str]): The namespace under which the static methods will be registered.
    """
    for name, member in cls.__dict__.items():
        if not name.startswith("_") and isinstance(member, staticmethod):
            registry.register(member.__func__, namespace=namespace)


def _register_instance_methods(
    instance: object, registry: ToolRegistry, namespace: Optional[str]
) -> None:
    """
    Registers all instance methods (excluding private methods and classmethods) of an object into the registry.

    Args:
        instance (object): The object whose instance methods will be registered.
        registry (ToolRegistry): The registry object to register the methods into.
                             It is expected to have a `register` method.
        namespace (Optional[str]): The namespace under which the instance methods will be registered.
    """
    for name in dir(instance):
        if name.startswith("_"):
            continue
        # Exclude classmethods
        member = type(instance).__dict__.get(name, None)
        if isinstance(member, classmethod):
            continue
        attr = getattr(instance, name)
        if callable(attr):
            registry.register(attr, namespace=namespace)


[docs] class ClassToolIntegration:
[docs] def __init__(self, registry: ToolRegistry) -> None: """Initialize with a ToolRegistry instance. Args: registry (ToolRegistry): The tool registry to register methods with. """ self.registry = registry
[docs] def register_class_methods( self, cls_or_instance: Union[Type, object], with_namespace: Union[bool, str] = False, ) -> None: """Register all methods from a class or instance as tools. If a class is provided: - If all public methods are static, they are registered directly. - Otherwise, the class is instantiated and its public callable methods are registered. If an instance is provided: - Its public callable methods are registered directly. Args: cls_or_instance (Union[Type, object]): The class or instance to scan for methods. with_namespace (Union[bool, str]): Whether to prefix tool names with a namespace. - If False, no namespace is used. - If True, the namespace is derived from the class name. - If a string is provided, it is used as the namespace. Defaults to False. """ namespace = _determine_namespace(cls_or_instance, with_namespace) if isinstance(cls_or_instance, type): if _is_all_static_methods(cls_or_instance): _register_static_methods(cls_or_instance, self.registry, namespace) else: try: instance = cls_or_instance() except TypeError as e: raise TypeError( f"Cannot instantiate class {cls_or_instance.__name__} without arguments. " "Please provide an instance of the class." ) from e _register_instance_methods(instance, self.registry, namespace) else: _register_instance_methods(cls_or_instance, self.registry, namespace)
[docs] async def register_class_methods_async( self, cls_or_instance: Union[Type, object], with_namespace: Union[bool, str] = False, ) -> None: """Async implementation to register tools from a class. Currently, this is implemented synchronously. Args: cls_or_instance (Union[Type, object]): The class or instance to scan for methods. with_namespace (Union[bool, str]): Whether to prefix tool names with a namespace. - If False, no namespace is used. - If True, the namespace is derived from the class name. - If a string is provided, it is used as the namespace. Defaults to False. """ self.register_class_methods(cls_or_instance, with_namespace)