from importlib.metadata import version
import os
import warnings
from typing import Any, Optional, List
from .execute_function import ToolUniverse
from .base_tool import BaseTool
from .default_config import default_tool_files
from .tool_registry import (
register_tool,
get_tool_registry,
get_tool_class_lazy,
auto_discover_tools
)
_LIGHT_IMPORT = os.getenv("TOOLUNIVERSE_LIGHT_IMPORT", "false").lower() in (
"true",
"1",
"yes",
)
# Version information - read from package metadata or pyproject.toml
__version__ = version("tooluniverse")
# Check if lazy loading is enabled
LAZY_LOADING_ENABLED = os.getenv('TOOLUNIVERSE_LAZY_LOADING', 'true').lower() in (
'true', '1', 'yes'
)
# Import MCP functionality
if not _LIGHT_IMPORT:
try:
from .mcp_integration import _patch_tooluniverse
# Automatically patch ToolUniverse with MCP methods
_patch_tooluniverse()
except ImportError:
# MCP functionality not available
pass
# Import SMCP with graceful fallback and consistent signatures for type checking
try:
from .smcp import SMCP, create_smcp_server
_SMCP_AVAILABLE = True
except ImportError:
_SMCP_AVAILABLE = False
class SMCP: # type: ignore[no-redef]
def __init__(self, *args: Any, **kwargs: Any) -> None:
raise ImportError(
"SMCP requires FastMCP. Install with: pip install fastmcp"
)
def create_smcp_server(
name: str = "SMCP Server",
tool_categories: Optional[List[str]] = None,
search_enabled: bool = True,
**kwargs: Any,
) -> SMCP:
raise ImportError("SMCP requires FastMCP. Install with: pip install fastmcp")
[docs]
def __getattr__(name: str) -> Any:
"""
Dynamic dispatch for tool classes.
This replaces the manual _LazyImportProxy list.
"""
# 1. Try to get it from the tool registry (lazy or eager)
# The registry knows about all tools via AST discovery or manual registration
tool_class = get_tool_class_lazy(name)
if tool_class:
return tool_class
# 2. If not found, raise AttributeError standard behavior
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
[docs]
def __dir__() -> List[str]:
"""
Dynamic directory listing.
Includes standard globals plus all available tools.
"""
# Standard globals
global_names = list(globals().keys())
# Available tools (triggers discovery if not already done)
# auto_discover_tools(lazy=True) ensures we have the mapping
tool_registry = auto_discover_tools(lazy=True)
tool_names = list(tool_registry.keys())
return sorted(list(set(global_names + tool_names)))
# If lazy loading is disabled, we should eagerly load everything now
# just to be safe and replicate old behavior, although __getattr__ works fine too.
# But for compatibility with `from tooluniverse import *` or inspection tools that
# don't use __dir__, eager loading might be desired if LAZY_LOADING_ENABLED is False.
if not _LIGHT_IMPORT and not LAZY_LOADING_ENABLED:
# Trigger full discovery (imports all modules)
auto_discover_tools(lazy=False)
# Note: We don't inject them into globals() here because __getattr__ handles access.
# But if users expect them to be in globals() for some reason, they might be disappointed.
# However, PEP 562 __getattr__ handles instance access perfectly.
# 'from tooluniverse import ToolName' works.
pass
__all__ = [
"__version__",
"ToolUniverse",
"BaseTool",
"register_tool",
"get_tool_registry",
"SMCP",
"create_smcp_server",
"default_tool_files",
]
# Add tools to __all__ so `from tooluniverse import *` works
# This requires discovering tools first
if not _LIGHT_IMPORT:
try:
# Just get the names without importing modules if possible (lazy)
_registry = auto_discover_tools(lazy=True)
__all__.extend(list(_registry.keys()))
except Exception:
pass