mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
refactor: declare each lazy litellm attribute once and resolve them through one function (#50523)
This commit is contained in:
parent
4ed339b2fc
commit
1f98627d4a
6 changed files with 816 additions and 2659 deletions
|
|
@ -81,7 +81,6 @@ from litellm.constants import (
|
|||
import httpx
|
||||
from openai import DefaultAsyncHttpxClient, DefaultHttpxClient
|
||||
|
||||
# register_async_client_cleanup is lazy-loaded and called on first access
|
||||
|
||||
litellm_mode = os.getenv("LITELLM_MODE", "DEV") # "PRODUCTION", "DEV"
|
||||
|
||||
|
|
@ -98,7 +97,6 @@ failure_callback: List[CALLBACK_TYPES] = []
|
|||
service_callback: List[CALLBACK_TYPES] = []
|
||||
audit_log_callbacks: List[CALLBACK_TYPES] = []
|
||||
batch_line_item_callbacks: Sequence[CALLBACK_TYPES] = ()
|
||||
# logging_callback_manager is lazy-loaded via __getattr__
|
||||
_custom_logger_compatible_callbacks_literal = Literal[
|
||||
"lago",
|
||||
"openmeter",
|
||||
|
|
@ -507,8 +505,6 @@ skill_search_embedding_model: Optional[str] = None
|
|||
public_model_groups_links: dict[str, str | dict[str, object]] = {}
|
||||
#### REQUEST PRIORITIZATION #######
|
||||
priority_reservation: Optional[Dict[str, Union[float, "PriorityReservationDict"]]] = None
|
||||
# priority_reservation_settings is lazy-loaded via __getattr__
|
||||
# Only declare for type checking - at runtime __getattr__ handles it
|
||||
if TYPE_CHECKING:
|
||||
priority_reservation_settings: Optional["PriorityReservationSettings"] = None
|
||||
|
||||
|
|
@ -543,8 +539,6 @@ _google_kms_resource_name: Optional[str] = None
|
|||
_key_management_system: Optional["KeyManagementSystem"] = None
|
||||
# Note: KeyManagementSettings must be eagerly imported because _key_management_settings
|
||||
# is accessed during import time in secret_managers/main.py
|
||||
# We'll import it after the lazy import system is set up
|
||||
# We can't define it here because KeyManagementSettings is lazy-loaded
|
||||
#### PII MASKING ####
|
||||
output_parse_pii: bool = False
|
||||
#############################################
|
||||
|
|
@ -1196,8 +1190,6 @@ model_list = list(
|
|||
|
||||
model_list_set = set(model_list)
|
||||
|
||||
# provider_list is lazy-loaded via __getattr__ to avoid importing LlmProviders at import time
|
||||
|
||||
|
||||
def _build_models_by_provider() -> dict:
|
||||
return {
|
||||
|
|
@ -1378,10 +1370,6 @@ all_embedding_models = (
|
|||
####### VIDEO GENERATION MODELS ###################
|
||||
openai_video_generation_models = ["sora-2"]
|
||||
|
||||
# timeout is lazy-loaded via __getattr__
|
||||
# get_llm_provider is lazy-loaded via __getattr__
|
||||
# remove_index_from_tool_calls is lazy-loaded via __getattr__
|
||||
|
||||
# Import KeyManagementSettings here (before utils import) because _key_management_settings
|
||||
# is accessed during import time in secret_managers/main.py (via dd_tracing -> datadog -> _service_logger -> utils)
|
||||
from litellm.types.secret_managers.main import KeyManagementSettings
|
||||
|
|
@ -1391,8 +1379,6 @@ _key_management_settings: KeyManagementSettings = KeyManagementSettings()
|
|||
# client must be imported immediately as it's used as a decorator at function definition time
|
||||
from .utils import client
|
||||
|
||||
# Note: Most other utils imports are lazy-loaded via __getattr__ to avoid loading utils.py
|
||||
# (which imports tiktoken) at import time
|
||||
|
||||
from .llms.custom_llm import CustomLLM
|
||||
from .llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
|
@ -1416,12 +1402,9 @@ from .llms.bedrock.embed.amazon_titan_v2_transformation import (
|
|||
)
|
||||
from .llms.topaz.common_utils import TopazModelInfo
|
||||
|
||||
# OpenAIOSeriesConfig is lazy loaded - openaiOSeriesConfig will be created on first access
|
||||
# OpenAIGPTConfig, OpenAIGPT5Config, etc. are lazy loaded - instances will be created on first access
|
||||
from .llms.xai.common_utils import XAIModelInfo
|
||||
|
||||
# PublicAI now uses JSON-based configuration (see litellm/llms/openai_like/providers.json)
|
||||
# All remaining configs are now lazy loaded - see _lazy_imports_registry.py
|
||||
|
||||
# Import LlmProviders here (before main import) because it's imported during import time
|
||||
# in multiple places including openai.py (via main import)
|
||||
|
|
@ -1650,8 +1633,6 @@ def set_global_gitlab_config(config: Mapping[str, object]) -> None:
|
|||
global_gitlab_config = config
|
||||
|
||||
|
||||
# Lazy loading system for heavy modules to reduce initial import time and memory usage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import guardrails as guardrails
|
||||
from litellm.types.utils import ModelInfo as _ModelInfoType
|
||||
|
|
@ -1659,7 +1640,6 @@ if TYPE_CHECKING:
|
|||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
|
||||
from litellm.caching.caching import Cache
|
||||
|
||||
# Type stubs for lazy-loaded configs to help mypy
|
||||
from .llms.bedrock.chat.converse_transformation import (
|
||||
AmazonConverseConfig as AmazonConverseConfig,
|
||||
)
|
||||
|
|
@ -2088,7 +2068,6 @@ if TYPE_CHECKING:
|
|||
)
|
||||
from .llms.gdc.chat.transformation import GDCGeminiConfig as GDCGeminiConfig
|
||||
|
||||
# Type stubs for lazy-loaded config instances
|
||||
openaiOSeriesConfig: OpenAIOSeriesConfig
|
||||
openAIGPTConfig: OpenAIGPTConfig
|
||||
openAIGPTAudioConfig: OpenAIGPTAudioConfig
|
||||
|
|
@ -2146,7 +2125,6 @@ if TYPE_CHECKING:
|
|||
VertexGeminiConfig as _VertexGeminiConfig,
|
||||
)
|
||||
|
||||
# Type stubs for lazy-loaded config classes (to help mypy understand types)
|
||||
VLLMConfig: Type[_VLLMConfig]
|
||||
DeepSeekChatConfig: Type[_DeepSeekChatConfig]
|
||||
TencentChatConfig: Type[_TencentChatConfig]
|
||||
|
|
@ -2423,63 +2401,34 @@ if TYPE_CHECKING:
|
|||
module_level_aclient: AsyncHTTPHandler
|
||||
module_level_client: HTTPHandler
|
||||
|
||||
# Bedrock tool name mappings instance (lazy-loaded)
|
||||
from litellm.caching.caching import InMemoryCache
|
||||
|
||||
bedrock_tool_name_mappings: InMemoryCache
|
||||
|
||||
# Azure exception class (lazy-loaded)
|
||||
from litellm.llms.azure.common_utils import AzureOpenAIError
|
||||
|
||||
# Secret manager types (lazy-loaded)
|
||||
from litellm.types.secret_managers.main import (
|
||||
KeyManagementSystem,
|
||||
KeyManagementSettings, # Not lazy-loaded - needed for _key_management_settings initialization
|
||||
)
|
||||
|
||||
# Custom logger class (lazy-loaded)
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
|
||||
# Datadog LLM observability params (lazy-loaded)
|
||||
from litellm.types.integrations.datadog_llm_obs import DatadogLLMObsInitParams
|
||||
|
||||
# Logging callback manager class and instance (lazy-loaded)
|
||||
from litellm.litellm_core_utils.logging_callback_manager import (
|
||||
LoggingCallbackManager,
|
||||
)
|
||||
|
||||
logging_callback_manager: LoggingCallbackManager
|
||||
|
||||
# provider_list is lazy-loaded
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
provider_list: List[Union[LlmProviders, str]]
|
||||
|
||||
# Note: AmazonConverseConfig and OpenAILikeChatConfig are imported above in TYPE_CHECKING block
|
||||
|
||||
|
||||
# Track if async client cleanup has been registered (for lazy loading)
|
||||
_async_client_cleanup_registered = False
|
||||
|
||||
_AGENT_EXPORTS: Final = frozenset(
|
||||
{
|
||||
"agent",
|
||||
"aagent",
|
||||
"agent_session",
|
||||
"aagent_session",
|
||||
"agent_resume",
|
||||
"aagent_resume",
|
||||
"agent_capabilities",
|
||||
"Harness",
|
||||
"ClaudeCodeOptions",
|
||||
"CodexOptions",
|
||||
"OpenCodeOptions",
|
||||
"PiOptions",
|
||||
"DeepAgentsOptions",
|
||||
"ToolLoopOptions",
|
||||
}
|
||||
)
|
||||
|
||||
# Eager loading for backwards compatibility with VCR and other HTTP recording tools
|
||||
# When LITELLM_DISABLE_LAZY_LOADING is set, lazy-loaded attributes are loaded at import time
|
||||
# For now, this only affects encoding (tiktoken) as it was the only reported issue
|
||||
|
|
@ -2492,9 +2441,7 @@ if os.getenv("LITELLM_DISABLE_LAZY_LOADING", "").lower() in ("1", "true", "yes",
|
|||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Lazy import handler with cached registry for improved performance."""
|
||||
global _async_client_cleanup_registered
|
||||
# Register async client cleanup on first access (only once)
|
||||
if not _async_client_cleanup_registered:
|
||||
from litellm.llms.custom_httpx.async_client_cleanup import (
|
||||
register_async_client_cleanup,
|
||||
|
|
@ -2503,205 +2450,9 @@ def __getattr__(name: str) -> Any:
|
|||
register_async_client_cleanup()
|
||||
_async_client_cleanup_registered = True
|
||||
|
||||
# Use cached registry from _lazy_imports instead of importing tuples every time
|
||||
from ._lazy_imports import get_lazy_import_registry
|
||||
from ._lazy_imports import resolve_lazy_attribute
|
||||
|
||||
registry: Final = get_lazy_import_registry()
|
||||
return resolve_lazy_attribute(__name__, name)
|
||||
|
||||
# Check if name is in registry and call the cached handler function
|
||||
if name in registry:
|
||||
handler_func: Final = registry[name]
|
||||
return handler_func(name)
|
||||
|
||||
if name in ("proxy", "guardrails"):
|
||||
import importlib
|
||||
|
||||
return importlib.import_module(f"litellm.{name}")
|
||||
|
||||
if name == "harness" or name in _AGENT_EXPORTS:
|
||||
import importlib
|
||||
|
||||
harness_module = importlib.import_module("litellm.harness")
|
||||
return harness_module if name == "harness" else getattr(harness_module, name)
|
||||
|
||||
# Lazy load encoding from main.py to avoid heavy tiktoken import
|
||||
if name == "encoding":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "encoding" not in _globals:
|
||||
from .main import encoding as _encoding
|
||||
|
||||
_globals["encoding"] = _encoding
|
||||
return _globals["encoding"]
|
||||
|
||||
# Lazy load bedrock_tool_name_mappings instance
|
||||
if name == "bedrock_tool_name_mappings":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "bedrock_tool_name_mappings" not in _globals:
|
||||
from .llms.bedrock.chat.invoke_handler import (
|
||||
bedrock_tool_name_mappings as _bedrock_tool_name_mappings,
|
||||
)
|
||||
|
||||
_globals["bedrock_tool_name_mappings"] = _bedrock_tool_name_mappings
|
||||
return _globals["bedrock_tool_name_mappings"]
|
||||
|
||||
# Lazy load AzureOpenAIError exception class
|
||||
if name == "AzureOpenAIError":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "AzureOpenAIError" not in _globals:
|
||||
from .llms.azure.common_utils import AzureOpenAIError as _AzureOpenAIError
|
||||
|
||||
_globals["AzureOpenAIError"] = _AzureOpenAIError
|
||||
return _globals["AzureOpenAIError"]
|
||||
|
||||
# Lazy load openaiOSeriesConfig instance
|
||||
if name == "openaiOSeriesConfig":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
if "openaiOSeriesConfig" not in _globals:
|
||||
# Import the config class and instantiate it
|
||||
config_class = __getattr__("OpenAIOSeriesConfig")
|
||||
_globals["openaiOSeriesConfig"] = config_class()
|
||||
return _globals["openaiOSeriesConfig"]
|
||||
|
||||
# Lazy load other config instances
|
||||
_config_instances: Final = {
|
||||
"openAIGPTConfig": "OpenAIGPTConfig",
|
||||
"openAIGPTAudioConfig": "OpenAIGPTAudioConfig",
|
||||
"openAIGPT5Config": "OpenAIGPT5Config",
|
||||
"nvidiaNimConfig": "NvidiaNimConfig",
|
||||
"nvidiaNimEmbeddingConfig": "NvidiaNimEmbeddingConfig",
|
||||
}
|
||||
if name in _config_instances:
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
if name not in _globals:
|
||||
# Import the config class and instantiate it
|
||||
config_class = __getattr__(_config_instances[name])
|
||||
_globals[name] = config_class()
|
||||
return _globals[name]
|
||||
|
||||
# Handle OpenAIO1Config alias
|
||||
if name == "OpenAIO1Config":
|
||||
return __getattr__("OpenAIOSeriesConfig")
|
||||
|
||||
# Lazy load provider_list
|
||||
if name == "provider_list":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "provider_list" not in _globals:
|
||||
# LlmProviders is eagerly imported above, so we can import it directly
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
_globals["provider_list"] = list(LlmProviders)
|
||||
return _globals["provider_list"]
|
||||
|
||||
# Lazy load priority_reservation_settings instance
|
||||
if name == "priority_reservation_settings":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "priority_reservation_settings" not in _globals:
|
||||
# Import the class and instantiate it
|
||||
PriorityReservationSettings: Final = __getattr__("PriorityReservationSettings")
|
||||
_globals["priority_reservation_settings"] = PriorityReservationSettings()
|
||||
return _globals["priority_reservation_settings"]
|
||||
|
||||
# Lazy load logging_callback_manager instance
|
||||
if name == "logging_callback_manager":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "logging_callback_manager" not in _globals:
|
||||
# Import the class and instantiate it
|
||||
LoggingCallbackManager: Final = __getattr__("LoggingCallbackManager")
|
||||
_globals["logging_callback_manager"] = LoggingCallbackManager()
|
||||
return _globals["logging_callback_manager"]
|
||||
|
||||
# Lazy load _service_logger module
|
||||
if name == "_service_logger":
|
||||
from ._lazy_imports import get_litellm_globals
|
||||
|
||||
_globals = get_litellm_globals()
|
||||
# Check if already cached
|
||||
if "_service_logger" not in _globals:
|
||||
# Import the module lazily
|
||||
import litellm._service_logger
|
||||
|
||||
_globals["_service_logger"] = litellm._service_logger
|
||||
return _globals["_service_logger"]
|
||||
|
||||
# Lazy load evals module functions
|
||||
if name in [
|
||||
"acreate_eval",
|
||||
"alist_evals",
|
||||
"aget_eval",
|
||||
"aupdate_eval",
|
||||
"adelete_eval",
|
||||
"acancel_eval",
|
||||
"create_eval",
|
||||
"list_evals",
|
||||
"get_eval",
|
||||
"update_eval",
|
||||
"delete_eval",
|
||||
"cancel_eval",
|
||||
"acreate_run",
|
||||
"alist_runs",
|
||||
"aget_run",
|
||||
"acancel_run",
|
||||
"adelete_run",
|
||||
"create_run",
|
||||
"list_runs",
|
||||
"get_run",
|
||||
"cancel_run",
|
||||
"delete_run",
|
||||
]:
|
||||
from litellm.evals.main import (
|
||||
acreate_eval,
|
||||
alist_evals,
|
||||
aget_eval,
|
||||
aupdate_eval,
|
||||
adelete_eval,
|
||||
acancel_eval,
|
||||
create_eval,
|
||||
list_evals,
|
||||
get_eval,
|
||||
update_eval,
|
||||
delete_eval,
|
||||
cancel_eval,
|
||||
acreate_run,
|
||||
alist_runs,
|
||||
aget_run,
|
||||
acancel_run,
|
||||
adelete_run,
|
||||
create_run,
|
||||
list_runs,
|
||||
get_run,
|
||||
cancel_run,
|
||||
delete_run,
|
||||
)
|
||||
from types import MappingProxyType
|
||||
|
||||
evals_exports: Final[Mapping[str, object]] = MappingProxyType(locals())
|
||||
return evals_exports[name]
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
# ALL_LITELLM_RESPONSE_TYPES is lazy-loaded via __getattr__ to avoid loading utils at import time
|
||||
|
||||
mark_litellm_import_complete()
|
||||
|
|
|
|||
|
|
@ -1,60 +1,14 @@
|
|||
"""
|
||||
Lazy Import System
|
||||
|
||||
This module implements lazy loading for LiteLLM attributes. Instead of importing
|
||||
everything when the module loads, we only import things when they're actually used.
|
||||
|
||||
How it works:
|
||||
1. When someone accesses `litellm.some_attribute`, Python calls __getattr__ in __init__.py
|
||||
2. __getattr__ looks up the attribute name in a registry
|
||||
3. The registry points to a handler function (like _lazy_import_utils)
|
||||
4. The handler function imports the module and returns the attribute
|
||||
5. The result is cached so we don't import it again
|
||||
|
||||
This makes importing litellm much faster because we don't load heavy dependencies
|
||||
until they're actually needed.
|
||||
"""
|
||||
"""Resolve the attributes of `litellm` and `litellm.utils` that are imported on first access."""
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
from collections.abc import Callable, Mapping
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
from types import MappingProxyType, ModuleType
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
from typing_extensions import ReadOnly, TypedDict
|
||||
|
||||
# Import all the data structures that define what can be lazy-loaded
|
||||
# These are just lists of names and maps of where to find them
|
||||
from ._lazy_imports_registry import (
|
||||
# Import maps
|
||||
_BEDROCK_TYPES_IMPORT_MAP,
|
||||
_CACHING_IMPORT_MAP,
|
||||
_COST_CALCULATOR_IMPORT_MAP,
|
||||
_DOTPROMPT_IMPORT_MAP,
|
||||
_LITELLM_LOGGING_IMPORT_MAP,
|
||||
_LLM_CONFIGS_IMPORT_MAP,
|
||||
_LLM_PROVIDER_LOGIC_IMPORT_MAP,
|
||||
_TOKEN_COUNTER_IMPORT_MAP,
|
||||
_TYPES_IMPORT_MAP,
|
||||
_TYPES_UTILS_IMPORT_MAP,
|
||||
_UTILS_IMPORT_MAP,
|
||||
_UTILS_MODULE_IMPORT_MAP,
|
||||
# Name tuples
|
||||
BEDROCK_TYPES_NAMES,
|
||||
CACHING_NAMES,
|
||||
COST_CALCULATOR_NAMES,
|
||||
DOTPROMPT_NAMES,
|
||||
HTTP_HANDLER_NAMES,
|
||||
LITELLM_LOGGING_NAMES,
|
||||
LLM_CLIENT_CACHE_NAMES,
|
||||
LLM_CONFIG_NAMES,
|
||||
LLM_PROVIDER_LOGIC_NAMES,
|
||||
TOKEN_COUNTER_NAMES,
|
||||
TYPES_NAMES,
|
||||
TYPES_UTILS_NAMES,
|
||||
UTILS_MODULE_NAMES,
|
||||
UTILS_NAMES,
|
||||
)
|
||||
from ._lazy_imports_registry import LITELLM_ATTRIBUTES, UTILS_ATTRIBUTES
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import httpx
|
||||
|
|
@ -62,35 +16,6 @@ if TYPE_CHECKING:
|
|||
from litellm.litellm_core_utils.tokenizer import Encoding as Tokenizer
|
||||
|
||||
|
||||
def get_litellm_globals() -> dict[str, object]:
|
||||
"""
|
||||
Get the globals dictionary of the litellm module.
|
||||
|
||||
This is where we cache imported attributes so we don't import them twice.
|
||||
When you do `litellm.some_function`, it gets stored in this dictionary.
|
||||
"""
|
||||
return sys.modules["litellm"].__dict__
|
||||
|
||||
|
||||
def _get_utils_globals() -> dict[str, object]:
|
||||
"""
|
||||
Get the globals dictionary of the utils module.
|
||||
|
||||
This is where we cache imported attributes so we don't import them twice.
|
||||
When you do `litellm.utils.some_function`, it gets stored in this dictionary.
|
||||
"""
|
||||
return sys.modules["litellm.utils"].__dict__
|
||||
|
||||
|
||||
def _get_module_level_client_timeout(litellm_globals: Mapping[str, Any]) -> "float | httpx.Timeout | None":
|
||||
"""Read the configured `litellm.request_timeout` used for the module level http clients."""
|
||||
return litellm_globals.get("request_timeout")
|
||||
|
||||
|
||||
# These are special lazy loaders for things that are used internally
|
||||
# They're separate from the main lazy import system because they have specific use cases
|
||||
|
||||
|
||||
def get_default_encoding() -> "Tokenizer":
|
||||
from litellm.rust_bridge.tokenizer import get_encoding
|
||||
|
||||
|
|
@ -100,138 +25,43 @@ def get_default_encoding() -> "Tokenizer":
|
|||
_get_default_encoding = get_default_encoding
|
||||
|
||||
|
||||
# Lazy loader for get_modified_max_tokens to avoid importing token_counter at module import time
|
||||
_get_modified_max_tokens_func: "Callable[..., int | None] | None" = None
|
||||
|
||||
|
||||
def _get_modified_max_tokens() -> "Callable[..., int | None]":
|
||||
"""
|
||||
Lazily load and cache the get_modified_max_tokens function.
|
||||
from litellm.litellm_core_utils.token_counter import get_modified_max_tokens
|
||||
|
||||
This avoids importing `litellm.litellm_core_utils.token_counter` at `litellm` import time.
|
||||
The function is cached after the first import.
|
||||
|
||||
This is used internally by utils.py functions that need the token counter but shouldn't
|
||||
trigger its import during module load.
|
||||
"""
|
||||
global _get_modified_max_tokens_func
|
||||
if _get_modified_max_tokens_func is None:
|
||||
from litellm.litellm_core_utils.token_counter import (
|
||||
get_modified_max_tokens as _get_modified_max_tokens_imported,
|
||||
)
|
||||
|
||||
_get_modified_max_tokens_func = _get_modified_max_tokens_imported
|
||||
return _get_modified_max_tokens_func
|
||||
|
||||
|
||||
# Lazy loader for token_counter to avoid importing token_counter module at module import time
|
||||
_token_counter_new_func: "Callable[..., int] | None" = None
|
||||
_messages_reach_token_count_func: "Callable[..., bool] | None" = None
|
||||
return get_modified_max_tokens
|
||||
|
||||
|
||||
def get_token_counter_new() -> "Callable[..., int]":
|
||||
"""
|
||||
Lazily load and cache the token_counter function (aliased as token_counter_new).
|
||||
from litellm.litellm_core_utils.token_counter import token_counter
|
||||
|
||||
This avoids importing `litellm.litellm_core_utils.token_counter` at `litellm` import time.
|
||||
The function is cached after the first import.
|
||||
|
||||
This is used internally by utils.py functions that need the token counter but shouldn't
|
||||
trigger its import during module load.
|
||||
"""
|
||||
global _token_counter_new_func
|
||||
if _token_counter_new_func is None:
|
||||
from litellm.litellm_core_utils.token_counter import (
|
||||
token_counter as _token_counter_imported,
|
||||
)
|
||||
|
||||
_token_counter_new_func = _token_counter_imported
|
||||
return _token_counter_new_func
|
||||
return token_counter
|
||||
|
||||
|
||||
_get_token_counter_new = get_token_counter_new
|
||||
|
||||
|
||||
def get_messages_reach_token_count() -> "Callable[..., bool]":
|
||||
"""Lazily load ``messages_reach_token_count`` for the same reason as ``get_token_counter_new``."""
|
||||
global _messages_reach_token_count_func
|
||||
if _messages_reach_token_count_func is None:
|
||||
from litellm.litellm_core_utils.token_counter import (
|
||||
messages_reach_token_count as _messages_reach_token_count_imported,
|
||||
)
|
||||
from litellm.litellm_core_utils.token_counter import messages_reach_token_count
|
||||
|
||||
_messages_reach_token_count_func = _messages_reach_token_count_imported
|
||||
return _messages_reach_token_count_func
|
||||
return messages_reach_token_count
|
||||
|
||||
|
||||
_get_messages_reach_token_count = get_messages_reach_token_count
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MAIN LAZY IMPORT SYSTEM
|
||||
# ============================================================================
|
||||
|
||||
# This registry maps attribute names (like "ModelResponse") to handler functions
|
||||
# It's built once the first time someone accesses a lazy-loaded attribute
|
||||
# Example: {"ModelResponse": _lazy_import_utils, "Cache": _lazy_import_caching, ...}
|
||||
_LAZY_IMPORT_REGISTRY: dict[str, Callable[[str], object]] | None = None
|
||||
_NOT_LOADED: Final = object()
|
||||
|
||||
|
||||
def get_lazy_import_registry() -> dict[str, Callable[[str], object]]:
|
||||
"""
|
||||
Build the registry that maps attribute names to their handler functions.
|
||||
|
||||
This is called once, the first time someone accesses a lazy-loaded attribute.
|
||||
After that, we just look up the handler function in this dictionary.
|
||||
|
||||
Returns:
|
||||
Dictionary like {"ModelResponse": _lazy_import_utils, ...}
|
||||
"""
|
||||
global _LAZY_IMPORT_REGISTRY
|
||||
if _LAZY_IMPORT_REGISTRY is None:
|
||||
# Build the registry by going through each category and mapping
|
||||
# all the names in that category to their handler function
|
||||
_LAZY_IMPORT_REGISTRY = {}
|
||||
# For each category, map all its names to the handler function
|
||||
# Example: All names in UTILS_NAMES get mapped to _lazy_import_utils
|
||||
for name in COST_CALCULATOR_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_cost_calculator
|
||||
for name in LITELLM_LOGGING_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_litellm_logging
|
||||
for name in UTILS_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_utils
|
||||
for name in TOKEN_COUNTER_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_token_counter
|
||||
for name in LLM_CLIENT_CACHE_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_llm_client_cache
|
||||
for name in BEDROCK_TYPES_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_bedrock_types
|
||||
for name in TYPES_UTILS_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_types_utils
|
||||
for name in CACHING_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_caching
|
||||
for name in HTTP_HANDLER_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_http_handlers
|
||||
for name in DOTPROMPT_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_dotprompt
|
||||
for name in LLM_CONFIG_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_llm_configs
|
||||
for name in TYPES_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_types
|
||||
for name in LLM_PROVIDER_LOGIC_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_llm_provider_logic
|
||||
for name in UTILS_MODULE_NAMES:
|
||||
_LAZY_IMPORT_REGISTRY[name] = _lazy_import_utils_module
|
||||
|
||||
return _LAZY_IMPORT_REGISTRY
|
||||
def _cached(namespace: str, name: str) -> object:
|
||||
return vars(sys.modules[namespace]).get(name, _NOT_LOADED)
|
||||
|
||||
|
||||
_get_lazy_import_registry = get_lazy_import_registry
|
||||
def _cache(namespace: str, name: str, value: object) -> object:
|
||||
setattr(sys.modules[namespace], name, value)
|
||||
return value
|
||||
|
||||
|
||||
class _AttributeView(TypedDict):
|
||||
"""Holds one module attribute so the lazily fetched value is read back as ``object``."""
|
||||
|
||||
value: ReadOnly[object]
|
||||
|
||||
|
||||
|
|
@ -240,241 +70,88 @@ def _module_attribute(module: ModuleType, attr_name: str) -> object:
|
|||
return attribute["value"]
|
||||
|
||||
|
||||
def _generic_lazy_import(name: str, import_map: Mapping[str, tuple[str, str]], category: str) -> object:
|
||||
"""
|
||||
Generic function that handles lazy importing for most attributes.
|
||||
def _import(namespace: str, name: str, location: tuple[str, str]) -> object:
|
||||
cached: Final = _cached(namespace, name)
|
||||
if cached is not _NOT_LOADED:
|
||||
return cached
|
||||
module_path, attr_name = location
|
||||
module: Final[ModuleType] = importlib.import_module(module_path, package="litellm")
|
||||
return _cache(namespace, name, _module_attribute(module, attr_name))
|
||||
|
||||
This is the workhorse function - it does the actual importing and caching.
|
||||
Most handler functions just call this with their specific import map.
|
||||
|
||||
Steps:
|
||||
1. Check if the name exists in the import map (if not, raise error)
|
||||
2. Check if we've already imported it (if yes, return cached value)
|
||||
3. Look up where to find it (module_path and attr_name from the map)
|
||||
4. Import the module (Python caches this automatically)
|
||||
5. Get the attribute from the module
|
||||
6. Cache it in _globals so we don't import again
|
||||
7. Return it
|
||||
def _instance_of(class_name: str) -> Callable[[], object]:
|
||||
def create() -> object:
|
||||
cls: Final = resolve_lazy_attribute("litellm", class_name)
|
||||
assert isinstance(cls, type)
|
||||
return cls()
|
||||
|
||||
Args:
|
||||
name: The attribute name someone is trying to access (e.g., "ModelResponse")
|
||||
import_map: Dictionary telling us where to find each attribute
|
||||
Format: {"ModelResponse": (".utils", "ModelResponse")}
|
||||
category: Just for error messages (e.g., "Utils", "Cost calculator")
|
||||
"""
|
||||
# Step 1: Make sure this attribute exists in our map
|
||||
if name not in import_map:
|
||||
raise AttributeError(f"{category} lazy import: unknown attribute {name!r}")
|
||||
return create
|
||||
|
||||
# Step 2: Get the cache (where we store imported things)
|
||||
_globals: Final = get_litellm_globals()
|
||||
|
||||
# Step 3: If we've already imported it, just return the cached version
|
||||
if name in _globals:
|
||||
return _globals[name]
|
||||
def _submodule(module_path: str) -> Callable[[], object]:
|
||||
return lambda: importlib.import_module(module_path)
|
||||
|
||||
# Step 4: Look up where to find this attribute
|
||||
# The map tells us: (module_path, attribute_name)
|
||||
# Example: (".utils", "ModelResponse") means "look in .utils module, get ModelResponse"
|
||||
module_path, attr_name = import_map[name]
|
||||
|
||||
# Step 5: Import the module
|
||||
# Python automatically caches modules in sys.modules, so calling this twice is fast
|
||||
# If module_path starts with ".", it's a relative import (needs package="litellm")
|
||||
# Otherwise it's an absolute import (like "litellm.caching.caching")
|
||||
if module_path.startswith("."):
|
||||
module = importlib.import_module(module_path, package="litellm")
|
||||
else:
|
||||
module = importlib.import_module(module_path)
|
||||
def _request_timeout() -> "float | httpx.Timeout | None":
|
||||
import httpx
|
||||
|
||||
# Step 6: Get the actual attribute from the module
|
||||
# Example: getattr(utils_module, "ModelResponse") returns the ModelResponse class
|
||||
value: Final = _module_attribute(module, attr_name)
|
||||
timeout: Final = _cached("litellm", "request_timeout")
|
||||
return timeout if isinstance(timeout, (int, float, httpx.Timeout)) else None
|
||||
|
||||
# Step 7: Cache it so we don't have to import again next time
|
||||
_globals[name] = value
|
||||
|
||||
# Step 8: Return it
|
||||
return value
|
||||
def _module_level_aclient() -> object:
|
||||
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
|
||||
|
||||
return get_async_httpx_client(
|
||||
llm_provider="litellm_module_level_client",
|
||||
params={"timeout": _request_timeout(), "client_alias": "module level aclient"},
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# HANDLER FUNCTIONS
|
||||
# ============================================================================
|
||||
# These functions are called when someone accesses a lazy-loaded attribute.
|
||||
# Most of them just call _generic_lazy_import with their specific import map.
|
||||
# The registry (above) maps attribute names to these handler functions.
|
||||
|
||||
def _module_level_client() -> object:
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
||||
def _lazy_import_utils(name: str) -> object:
|
||||
"""Handler for utils module attributes (ModelResponse, token_counter, etc.)"""
|
||||
return _generic_lazy_import(name, _UTILS_IMPORT_MAP, "Utils")
|
||||
return HTTPHandler(timeout=_request_timeout())
|
||||
|
||||
|
||||
def _lazy_import_cost_calculator(name: str) -> object:
|
||||
"""Handler for cost calculator functions (completion_cost, cost_per_token, etc.)"""
|
||||
return _generic_lazy_import(name, _COST_CALCULATOR_IMPORT_MAP, "Cost calculator")
|
||||
def _provider_list() -> object:
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
return list(LlmProviders)
|
||||
|
||||
def _lazy_import_token_counter(name: str) -> object:
|
||||
"""Handler for token counter utilities"""
|
||||
return _generic_lazy_import(name, _TOKEN_COUNTER_IMPORT_MAP, "Token counter")
|
||||
|
||||
_LITELLM_FACTORIES: Final[Mapping[str, Callable[[], object]]] = MappingProxyType(
|
||||
{
|
||||
"in_memory_llm_clients_cache": _instance_of("LLMClientCache"),
|
||||
"module_level_aclient": _module_level_aclient,
|
||||
"module_level_client": _module_level_client,
|
||||
"openaiOSeriesConfig": _instance_of("OpenAIOSeriesConfig"),
|
||||
"openAIGPTConfig": _instance_of("OpenAIGPTConfig"),
|
||||
"openAIGPTAudioConfig": _instance_of("OpenAIGPTAudioConfig"),
|
||||
"openAIGPT5Config": _instance_of("OpenAIGPT5Config"),
|
||||
"nvidiaNimConfig": _instance_of("NvidiaNimConfig"),
|
||||
"nvidiaNimEmbeddingConfig": _instance_of("NvidiaNimEmbeddingConfig"),
|
||||
"priority_reservation_settings": _instance_of("PriorityReservationSettings"),
|
||||
"logging_callback_manager": _instance_of("LoggingCallbackManager"),
|
||||
"provider_list": _provider_list,
|
||||
"proxy": _submodule("litellm.proxy"),
|
||||
"guardrails": _submodule("litellm.guardrails"),
|
||||
"harness": _submodule("litellm.harness"),
|
||||
"_service_logger": _submodule("litellm._service_logger"),
|
||||
}
|
||||
)
|
||||
|
||||
def _lazy_import_bedrock_types(name: str) -> object:
|
||||
"""Handler for Bedrock type aliases"""
|
||||
return _generic_lazy_import(name, _BEDROCK_TYPES_IMPORT_MAP, "Bedrock types")
|
||||
|
||||
|
||||
def _lazy_import_types_utils(name: str) -> object:
|
||||
"""Handler for types from litellm.types.utils (BudgetConfig, ImageObject, etc.)"""
|
||||
return _generic_lazy_import(name, _TYPES_UTILS_IMPORT_MAP, "Types utils")
|
||||
|
||||
|
||||
def _lazy_import_caching(name: str) -> object:
|
||||
"""Handler for caching classes (Cache, DualCache, RedisCache, etc.)"""
|
||||
return _generic_lazy_import(name, _CACHING_IMPORT_MAP, "Caching")
|
||||
|
||||
|
||||
def _lazy_import_dotprompt(name: str) -> object:
|
||||
"""Handler for dotprompt integration globals"""
|
||||
return _generic_lazy_import(name, _DOTPROMPT_IMPORT_MAP, "Dotprompt")
|
||||
|
||||
|
||||
def _lazy_import_types(name: str) -> object:
|
||||
"""Handler for type classes (GuardrailItem, etc.)"""
|
||||
return _generic_lazy_import(name, _TYPES_IMPORT_MAP, "Types")
|
||||
|
||||
|
||||
def _lazy_import_llm_configs(name: str) -> object:
|
||||
"""Handler for LLM config classes (AnthropicConfig, OpenAILikeChatConfig, etc.)"""
|
||||
return _generic_lazy_import(name, _LLM_CONFIGS_IMPORT_MAP, "LLM config")
|
||||
|
||||
|
||||
def _lazy_import_litellm_logging(name: str) -> object:
|
||||
"""Handler for litellm_logging module (Logging, modify_integration)"""
|
||||
return _generic_lazy_import(name, _LITELLM_LOGGING_IMPORT_MAP, "Litellm logging")
|
||||
|
||||
|
||||
def _lazy_import_llm_provider_logic(name: str) -> object:
|
||||
"""Handler for LLM provider logic functions (get_llm_provider, etc.)"""
|
||||
return _generic_lazy_import(name, _LLM_PROVIDER_LOGIC_IMPORT_MAP, "LLM provider logic")
|
||||
|
||||
|
||||
def _lazy_import_utils_module(name: str) -> object:
|
||||
"""
|
||||
Handler for utils module lazy imports.
|
||||
|
||||
This uses a custom implementation because utils module needs to use
|
||||
_get_utils_globals() instead of get_litellm_globals() for caching.
|
||||
"""
|
||||
# Check if this attribute exists in our map
|
||||
if name not in _UTILS_MODULE_IMPORT_MAP:
|
||||
raise AttributeError(f"Utils module lazy import: unknown attribute {name!r}")
|
||||
|
||||
# Get the cache (where we store imported things) - use utils globals
|
||||
_globals: Final = _get_utils_globals()
|
||||
|
||||
# If we've already imported it, just return the cached version
|
||||
if name in _globals:
|
||||
return _globals[name]
|
||||
|
||||
# Look up where to find this attribute
|
||||
module_path, attr_name = _UTILS_MODULE_IMPORT_MAP[name]
|
||||
|
||||
# Import the module
|
||||
if module_path.startswith("."):
|
||||
module = importlib.import_module(module_path, package="litellm")
|
||||
else:
|
||||
module = importlib.import_module(module_path)
|
||||
|
||||
# Get the actual attribute from the module
|
||||
value: Final = _module_attribute(module, attr_name)
|
||||
|
||||
# Cache it so we don't have to import again next time
|
||||
_globals[name] = value
|
||||
|
||||
# Return it
|
||||
return value
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SPECIAL HANDLERS
|
||||
# ============================================================================
|
||||
# These handlers have custom logic that doesn't fit the generic pattern
|
||||
|
||||
|
||||
def _lazy_import_llm_client_cache(name: str) -> object:
|
||||
"""
|
||||
Handler for LLM client cache - has special logic for singleton instance.
|
||||
|
||||
This one is different because:
|
||||
- "LLMClientCache" is the class itself
|
||||
- "in_memory_llm_clients_cache" is a singleton instance of that class
|
||||
So we need custom logic to handle both cases.
|
||||
"""
|
||||
_globals: Final = get_litellm_globals()
|
||||
|
||||
# If already cached, return it
|
||||
if name in _globals:
|
||||
return _globals[name]
|
||||
|
||||
# Import the class
|
||||
from litellm.caching.llm_caching_handler import LLMClientCache
|
||||
|
||||
# If they want the class itself, return it
|
||||
if name == "LLMClientCache":
|
||||
_globals["LLMClientCache"] = LLMClientCache
|
||||
return LLMClientCache
|
||||
|
||||
# If they want the singleton instance, create it (only once)
|
||||
if name == "in_memory_llm_clients_cache":
|
||||
instance: Final = LLMClientCache()
|
||||
_globals["in_memory_llm_clients_cache"] = instance
|
||||
return instance
|
||||
|
||||
raise AttributeError(f"LLM client cache lazy import: unknown attribute {name!r}")
|
||||
|
||||
|
||||
def _lazy_import_http_handlers(name: str) -> object:
|
||||
"""
|
||||
Handler for HTTP clients - has special logic for creating client instances.
|
||||
|
||||
This one is different because:
|
||||
- These aren't just imports, they're actual client instances that need to be created
|
||||
- They need configuration (timeout, etc.) from the module globals
|
||||
- They use factory functions instead of direct instantiation
|
||||
"""
|
||||
_globals: Final = get_litellm_globals()
|
||||
|
||||
if name == "module_level_aclient":
|
||||
# Create an async HTTP client using the factory function
|
||||
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
|
||||
|
||||
# Get timeout from module config (if set)
|
||||
async_timeout: Final = _get_module_level_client_timeout(_globals)
|
||||
params: Final = {"timeout": async_timeout, "client_alias": "module level aclient"}
|
||||
|
||||
# Create the client instance
|
||||
provider_id: Final = "litellm_module_level_client"
|
||||
async_client: Final = get_async_httpx_client(
|
||||
llm_provider=provider_id,
|
||||
params=params,
|
||||
)
|
||||
|
||||
# Cache it so we don't create it again
|
||||
_globals["module_level_aclient"] = async_client
|
||||
return async_client
|
||||
|
||||
if name == "module_level_client":
|
||||
# Create a sync HTTP client
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
||||
sync_timeout: Final = _get_module_level_client_timeout(_globals)
|
||||
sync_client: Final = HTTPHandler(timeout=sync_timeout)
|
||||
|
||||
# Cache it
|
||||
_globals["module_level_client"] = sync_client
|
||||
return sync_client
|
||||
|
||||
raise AttributeError(f"HTTP handlers lazy import: unknown attribute {name!r}")
|
||||
def resolve_lazy_attribute(module_name: str, name: str) -> object:
|
||||
"""Import, cache and return `name`, the way `module_name.__getattr__` would."""
|
||||
litellm_location: Final = LITELLM_ATTRIBUTES.get(name)
|
||||
if litellm_location is not None:
|
||||
return _import("litellm", name, litellm_location)
|
||||
utils_location: Final = UTILS_ATTRIBUTES.get(name)
|
||||
if utils_location is not None:
|
||||
return _import("litellm.utils", name, utils_location)
|
||||
factory: Final = _LITELLM_FACTORIES.get(name) if module_name == "litellm" else None
|
||||
if factory is None:
|
||||
raise AttributeError(f"module {module_name!r} has no attribute {name!r}")
|
||||
cached: Final = _cached("litellm", name)
|
||||
return cached if cached is not _NOT_LOADED else _cache("litellm", name, factory())
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12025,15 +12025,6 @@ def should_run_mock_completion(
|
|||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Lazy import handler for utils module with cached registry for improved performance."""
|
||||
# Use cached registry from _lazy_imports instead of importing tuples every time
|
||||
from litellm._lazy_imports import get_lazy_import_registry
|
||||
from litellm._lazy_imports import resolve_lazy_attribute
|
||||
|
||||
registry: Final = get_lazy_import_registry()
|
||||
|
||||
# Check if name is in registry and call the cached handler function
|
||||
if name in registry:
|
||||
handler_func: Final = registry[name]
|
||||
return handler_func(name)
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
return resolve_lazy_attribute(__name__, name)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"""Simple tests for lazy import functionality."""
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -7,38 +6,9 @@ from typing import Final
|
|||
|
||||
import pytest
|
||||
|
||||
|
||||
import litellm
|
||||
from litellm._lazy_imports import (
|
||||
COST_CALCULATOR_NAMES,
|
||||
LITELLM_LOGGING_NAMES,
|
||||
UTILS_NAMES,
|
||||
TOKEN_COUNTER_NAMES,
|
||||
CACHING_NAMES,
|
||||
BEDROCK_TYPES_NAMES,
|
||||
TYPES_UTILS_NAMES,
|
||||
LLM_CLIENT_CACHE_NAMES,
|
||||
HTTP_HANDLER_NAMES,
|
||||
_lazy_import_cost_calculator,
|
||||
_lazy_import_litellm_logging,
|
||||
_lazy_import_utils,
|
||||
_lazy_import_token_counter,
|
||||
_lazy_import_bedrock_types,
|
||||
_lazy_import_types_utils,
|
||||
_lazy_import_caching,
|
||||
_lazy_import_llm_client_cache,
|
||||
_lazy_import_http_handlers,
|
||||
DOTPROMPT_NAMES,
|
||||
_lazy_import_dotprompt,
|
||||
LLM_CONFIG_NAMES,
|
||||
_lazy_import_llm_configs,
|
||||
TYPES_NAMES,
|
||||
_lazy_import_types,
|
||||
LLM_PROVIDER_LOGIC_NAMES,
|
||||
_lazy_import_llm_provider_logic,
|
||||
UTILS_MODULE_NAMES,
|
||||
_lazy_import_utils_module,
|
||||
)
|
||||
import litellm.utils
|
||||
from litellm._lazy_imports_registry import LITELLM_ATTRIBUTES, UTILS_ATTRIBUTES
|
||||
from tests.test_litellm_rust.support.child_interpreter import run_child_interpreter
|
||||
|
||||
|
||||
|
|
@ -58,314 +28,75 @@ def test_import_litellm_does_not_load_fastapi_or_bpe_table():
|
|||
assert result.stdout.strip() == ""
|
||||
|
||||
|
||||
def _clear_names_from_globals(names: tuple):
|
||||
"""Clear all names from litellm globals."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
for name in names:
|
||||
if name in litellm_globals:
|
||||
del litellm_globals[name]
|
||||
|
||||
|
||||
def _clear_names_from_utils_globals(names: tuple):
|
||||
"""Clear all names from litellm.utils globals."""
|
||||
# Get the actual globals dict, not a copy
|
||||
utils_globals = sys.modules["litellm.utils"].__dict__
|
||||
for name in names:
|
||||
if name in utils_globals:
|
||||
del utils_globals[name]
|
||||
|
||||
|
||||
def _verify_only_requested_name_imported(name: str, all_names: tuple):
|
||||
"""Verify that only the requested name is in globals, not the others."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
for other_name in all_names:
|
||||
if other_name != name:
|
||||
assert (
|
||||
other_name not in litellm_globals
|
||||
), f"{other_name} should not be imported when importing {name}"
|
||||
|
||||
|
||||
def _verify_only_requested_name_imported_in_utils(name: str, all_names: tuple):
|
||||
"""Verify that only the requested name is in utils globals, not the others."""
|
||||
# Get the actual globals dict, not a copy
|
||||
utils_globals = sys.modules["litellm.utils"].__dict__
|
||||
for other_name in all_names:
|
||||
if other_name != name:
|
||||
assert (
|
||||
other_name not in utils_globals
|
||||
), f"{other_name} should not be imported when importing {name}"
|
||||
|
||||
|
||||
def test_cost_calculator_lazy_imports():
|
||||
"""Test that all cost calculator functions can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
# Test each name individually - only that name should be imported
|
||||
for name in COST_CALCULATOR_NAMES:
|
||||
# Clear all names before importing just one
|
||||
_clear_names_from_globals(COST_CALCULATOR_NAMES)
|
||||
|
||||
func = _lazy_import_cost_calculator(name)
|
||||
assert func is not None
|
||||
assert callable(func)
|
||||
assert name in litellm_globals
|
||||
|
||||
# Verify only the requested name is in globals, not the others
|
||||
_verify_only_requested_name_imported(name, COST_CALCULATOR_NAMES)
|
||||
|
||||
|
||||
def test_litellm_logging_lazy_imports():
|
||||
"""Test that all litellm_logging items can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
# Test each name individually - only that name should be imported
|
||||
for name in LITELLM_LOGGING_NAMES:
|
||||
# Clear all names before importing just one
|
||||
_clear_names_from_globals(LITELLM_LOGGING_NAMES)
|
||||
|
||||
item = _lazy_import_litellm_logging(name)
|
||||
assert item is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
# Verify only the requested name is in globals, not the others
|
||||
_verify_only_requested_name_imported(name, LITELLM_LOGGING_NAMES)
|
||||
|
||||
|
||||
def test_utils_lazy_imports():
|
||||
"""Test that all utils functions can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
# Test each name individually - only that name should be imported
|
||||
for name in UTILS_NAMES:
|
||||
# Clear all names before importing just one
|
||||
_clear_names_from_globals(UTILS_NAMES)
|
||||
|
||||
attr = _lazy_import_utils(name)
|
||||
assert attr is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
# Verify only the requested name is in globals, not the others
|
||||
_verify_only_requested_name_imported(name, UTILS_NAMES)
|
||||
|
||||
|
||||
def test_caching_lazy_imports():
|
||||
"""Test that all caching classes can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
# Test each name individually - only that name should be imported
|
||||
for name in CACHING_NAMES:
|
||||
# Clear all names before importing just one
|
||||
_clear_names_from_globals(CACHING_NAMES)
|
||||
|
||||
cls = _lazy_import_caching(name)
|
||||
assert cls is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
# Verify only the requested name is in globals, not the others
|
||||
_verify_only_requested_name_imported(name, CACHING_NAMES)
|
||||
|
||||
|
||||
def test_token_counter_lazy_imports():
|
||||
"""Test that token counter utilities can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in TOKEN_COUNTER_NAMES:
|
||||
_clear_names_from_globals(TOKEN_COUNTER_NAMES)
|
||||
|
||||
func = _lazy_import_token_counter(name)
|
||||
assert func is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
_verify_only_requested_name_imported(name, TOKEN_COUNTER_NAMES)
|
||||
|
||||
|
||||
def test_bedrock_types_lazy_imports():
|
||||
"""Test that Bedrock type aliases can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in BEDROCK_TYPES_NAMES:
|
||||
_clear_names_from_globals(BEDROCK_TYPES_NAMES)
|
||||
|
||||
alias = _lazy_import_bedrock_types(name)
|
||||
assert alias is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
_verify_only_requested_name_imported(name, BEDROCK_TYPES_NAMES)
|
||||
|
||||
|
||||
def test_types_utils_lazy_imports():
|
||||
"""Test that common types.utils symbols can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in TYPES_UTILS_NAMES:
|
||||
_clear_names_from_globals(TYPES_UTILS_NAMES)
|
||||
|
||||
obj = _lazy_import_types_utils(name)
|
||||
assert obj is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
_verify_only_requested_name_imported(name, TYPES_UTILS_NAMES)
|
||||
|
||||
|
||||
def test_llm_client_cache_lazy_imports():
|
||||
"""Test that LLM client cache class and singleton can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in LLM_CLIENT_CACHE_NAMES:
|
||||
_clear_names_from_globals(LLM_CLIENT_CACHE_NAMES)
|
||||
|
||||
obj = _lazy_import_llm_client_cache(name)
|
||||
assert obj is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
_verify_only_requested_name_imported(name, LLM_CLIENT_CACHE_NAMES)
|
||||
|
||||
|
||||
def test_http_handler_lazy_imports():
|
||||
"""Test that HTTP handler singletons can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in HTTP_HANDLER_NAMES:
|
||||
_clear_names_from_globals(HTTP_HANDLER_NAMES)
|
||||
|
||||
handler = _lazy_import_http_handlers(name)
|
||||
assert handler is not None
|
||||
assert name in litellm_globals
|
||||
|
||||
_verify_only_requested_name_imported(name, HTTP_HANDLER_NAMES)
|
||||
|
||||
|
||||
def test_dotprompt_lazy_imports():
|
||||
"""Test that dotprompt globals can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in DOTPROMPT_NAMES:
|
||||
_clear_names_from_globals(DOTPROMPT_NAMES)
|
||||
|
||||
obj = _lazy_import_dotprompt(name)
|
||||
assert name in litellm_globals
|
||||
|
||||
# Only the setter must be callable; others may be None by default
|
||||
if name == "set_global_prompt_directory":
|
||||
assert callable(obj), f"{name} should be callable"
|
||||
|
||||
_verify_only_requested_name_imported(name, DOTPROMPT_NAMES)
|
||||
|
||||
|
||||
def test_unknown_attribute_raises_error():
|
||||
"""Test that unknown attributes raise AttributeError."""
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_cost_calculator("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_litellm_logging("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_utils("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_caching("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_token_counter("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_llm_client_cache("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_bedrock_types("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_types_utils("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_llm_configs("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_types("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_llm_provider_logic("unknown")
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_lazy_import_utils_module("unknown")
|
||||
|
||||
|
||||
def test_llm_config_lazy_imports():
|
||||
"""Test that LLM config classes can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in LLM_CONFIG_NAMES:
|
||||
_clear_names_from_globals(LLM_CONFIG_NAMES)
|
||||
|
||||
obj = _lazy_import_llm_configs(name)
|
||||
assert obj is not None
|
||||
assert name in litellm_globals
|
||||
# Config classes should be classes/types
|
||||
assert isinstance(obj, type), f"{name} should be a class"
|
||||
|
||||
_verify_only_requested_name_imported(name, LLM_CONFIG_NAMES)
|
||||
|
||||
|
||||
def test_types_lazy_imports():
|
||||
"""Test that type classes can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in TYPES_NAMES:
|
||||
_clear_names_from_globals(TYPES_NAMES)
|
||||
|
||||
obj = _lazy_import_types(name)
|
||||
assert obj is not None
|
||||
assert name in litellm_globals
|
||||
# Type classes should be classes/types
|
||||
assert isinstance(obj, type), f"{name} should be a class"
|
||||
|
||||
_verify_only_requested_name_imported(name, TYPES_NAMES)
|
||||
|
||||
|
||||
def test_llm_provider_logic_lazy_imports():
|
||||
"""Test that LLM provider logic functions can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
litellm_globals = sys.modules["litellm"].__dict__
|
||||
|
||||
for name in LLM_PROVIDER_LOGIC_NAMES:
|
||||
_clear_names_from_globals(LLM_PROVIDER_LOGIC_NAMES)
|
||||
|
||||
func = _lazy_import_llm_provider_logic(name)
|
||||
assert func is not None
|
||||
assert callable(func)
|
||||
assert name in litellm_globals
|
||||
|
||||
_verify_only_requested_name_imported(name, LLM_PROVIDER_LOGIC_NAMES)
|
||||
|
||||
|
||||
def test_utils_module_lazy_imports():
|
||||
"""Test that utils module attributes can be lazy imported."""
|
||||
# Get the actual globals dict, not a copy
|
||||
utils_globals = sys.modules["litellm.utils"].__dict__
|
||||
|
||||
for name in UTILS_MODULE_NAMES:
|
||||
_clear_names_from_utils_globals(UTILS_MODULE_NAMES)
|
||||
|
||||
obj = _lazy_import_utils_module(name)
|
||||
assert obj is not None
|
||||
assert name in utils_globals
|
||||
|
||||
_verify_only_requested_name_imported_in_utils(name, UTILS_MODULE_NAMES)
|
||||
def _declared(location: tuple[str, str]) -> object:
|
||||
module_path, attr_name = location
|
||||
return getattr(importlib.import_module(module_path, package="litellm"), attr_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", sorted(LITELLM_ATTRIBUTES))
|
||||
def test_litellm_attribute_is_the_declared_object(name: str):
|
||||
assert getattr(litellm, name) is _declared(LITELLM_ATTRIBUTES[name])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", sorted(UTILS_ATTRIBUTES))
|
||||
def test_utils_attribute_is_the_same_object_from_litellm_and_litellm_utils(name: str):
|
||||
expected: Final = _declared(UTILS_ATTRIBUTES[name])
|
||||
assert getattr(litellm.utils, name) is expected
|
||||
assert getattr(litellm, name) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("instance_name", "class_name"),
|
||||
[
|
||||
("in_memory_llm_clients_cache", "LLMClientCache"),
|
||||
("openaiOSeriesConfig", "OpenAIOSeriesConfig"),
|
||||
("openAIGPTConfig", "OpenAIGPTConfig"),
|
||||
("openAIGPTAudioConfig", "OpenAIGPTAudioConfig"),
|
||||
("openAIGPT5Config", "OpenAIGPT5Config"),
|
||||
("nvidiaNimConfig", "NvidiaNimConfig"),
|
||||
("nvidiaNimEmbeddingConfig", "NvidiaNimEmbeddingConfig"),
|
||||
("priority_reservation_settings", "PriorityReservationSettings"),
|
||||
("logging_callback_manager", "LoggingCallbackManager"),
|
||||
],
|
||||
)
|
||||
def test_lazy_instance_is_a_single_instance_of_its_class(instance_name: str, class_name: str):
|
||||
instance: Final = getattr(litellm, instance_name)
|
||||
assert type(instance) is getattr(litellm, class_name)
|
||||
assert getattr(litellm, instance_name) is instance
|
||||
|
||||
|
||||
def test_module_level_http_clients_use_request_timeout(monkeypatch: pytest.MonkeyPatch):
|
||||
client_names: Final = ("module_level_client", "module_level_aclient")
|
||||
monkeypatch.setattr(litellm, "request_timeout", 7)
|
||||
for client_name in client_names:
|
||||
monkeypatch.delitem(vars(litellm), client_name, raising=False)
|
||||
try:
|
||||
assert litellm.module_level_client.timeout == 7
|
||||
assert litellm.module_level_aclient.timeout == 7
|
||||
assert litellm.module_level_client is litellm.module_level_client
|
||||
finally:
|
||||
for client_name in client_names:
|
||||
vars(litellm).pop(client_name, None)
|
||||
|
||||
|
||||
def test_provider_list_lists_every_provider():
|
||||
assert litellm.provider_list == list(litellm.LlmProviders)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["proxy", "guardrails", "harness", "_service_logger"])
|
||||
def test_submodule_attribute_is_the_submodule(name: str):
|
||||
assert getattr(litellm, name) is importlib.import_module(f"litellm.{name}")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("module", [litellm, litellm.utils])
|
||||
def test_unknown_attribute_raises_attribute_error(module: object):
|
||||
with pytest.raises(AttributeError, match="not_a_litellm_attribute"):
|
||||
getattr(module, "not_a_litellm_attribute")
|
||||
assert not hasattr(module, "not_a_litellm_attribute")
|
||||
|
||||
|
||||
def test_litellm_instances_are_not_attributes_of_litellm_utils():
|
||||
assert not hasattr(litellm.utils, "module_level_client")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
|||
|
|
@ -1185,7 +1185,6 @@ ALIAS_CASES: Final = (
|
|||
("litellm", "", "_turn_on_debug", "turn_on_debug", False),
|
||||
("litellm", "", "_turn_on_json", "turn_on_json", False),
|
||||
("litellm._lazy_imports", "", "_get_default_encoding", "get_default_encoding", False),
|
||||
("litellm._lazy_imports", "", "_get_lazy_import_registry", "get_lazy_import_registry", False),
|
||||
("litellm._lazy_imports", "", "_get_messages_reach_token_count", "get_messages_reach_token_count", False),
|
||||
("litellm._lazy_imports", "", "_get_token_counter_new", "get_token_counter_new", False),
|
||||
("litellm._logging", "", "_is_debugging_on", "is_debugging_on", False),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue