[Refactor] litellm/init.py: lazy load http handlers (#17997)

This commit is contained in:
Alexsander Hamir 2025-12-15 11:50:22 -08:00 • committed by GitHub
parent 93b1da7911
commit 0629dcfdd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 8 deletions

View file

@ -26,7 +26,6 @@ from typing import (
)
from litellm.types.integrations.datadog_llm_obs import DatadogLLMObsInitParams
from litellm.types.integrations.datadog import DatadogInitParams
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
from litellm.caching.caching import Cache, DualCache, RedisCache, InMemoryCache
from litellm.caching.llm_caching_handler import LLMClientCache
from litellm.types.llms.bedrock import COHERE_EMBEDDING_INPUT_TYPES
@ -422,10 +421,6 @@ disable_aiohttp_trust_env: bool = (
force_ipv4: bool = (
False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6.
)
module_level_aclient = AsyncHTTPHandler(
timeout=request_timeout, client_alias="module level aclient"
)
module_level_client = HTTPHandler(timeout=request_timeout)
#### RETRIES ####
num_retries: Optional[int] = None # per model endpoint
@ -1520,6 +1515,7 @@ def set_global_gitlab_config(config: Dict[str, Any]) -> None:
if TYPE_CHECKING:
from litellm.types.utils import ModelInfo as _ModelInfoType
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
# Cost calculator functions
cost_per_token: Callable[..., Tuple[float, float]]
@ -1560,13 +1556,18 @@ if TYPE_CHECKING:
# Response types - truly lazy loaded only (not in main.py or elsewhere)
ModelResponseListIterator: Type[Any]
# HTTP handler singletons (created lazily via __getattr__ at runtime)
module_level_aclient: AsyncHTTPHandler
module_level_client: HTTPHandler
def __getattr__(name: str) -> Any:
"""Lazy import handler for cost_calculator and litellm_logging functions."""
"""Lazy import handler"""
from ._lazy_imports import (
COST_CALCULATOR_NAMES,
LITELLM_LOGGING_NAMES,
UTILS_NAMES,
HTTP_HANDLER_NAMES,
)
# Lazy load cost_calculator functions
@ -1584,6 +1585,12 @@ def __getattr__(name: str) -> Any:
from ._lazy_imports import _lazy_import_utils
return _lazy_import_utils(name)
# Lazy-load HTTP handler singletons used across the codebase
if name in HTTP_HANDLER_NAMES:
from ._lazy_imports import _lazy_import_http_handlers
return _lazy_import_http_handlers(name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View file

@ -1,4 +1,4 @@
from typing import Any
from typing import Any, cast
import sys
def _get_litellm_globals() -> dict:
@ -34,6 +34,12 @@ UTILS_NAMES = (
"ModelResponseListIterator", "get_valid_models",
)
# HTTP handler names that support lazy loading via _lazy_import_http_handlers
HTTP_HANDLER_NAMES = (
"module_level_aclient",
"module_level_client",
)
# Lazy import for utils module - imports only the requested item by name.
# Note: PLR0915 (too many statements) is suppressed because the many if statements
# are intentional - each attribute is imported individually only when requested,
@ -278,4 +284,36 @@ def _lazy_import_litellm_logging(name: str) -> Any:
_globals["modify_integration"] = _modify_integration
return _modify_integration
raise AttributeError(f"Litellm logging lazy import: unknown attribute {name!r}")
raise AttributeError(f"Litellm logging lazy import: unknown attribute {name!r}")
def _lazy_import_http_handlers(name: str) -> Any:
"""Lazy import and instantiate module-level HTTP handlers."""
_globals = _get_litellm_globals()
if name == "module_level_aclient":
# Use shared async client factory instead of directly instantiating AsyncHTTPHandler
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
timeout = _globals.get("request_timeout")
params = {"timeout": timeout, "client_alias": "module level aclient"}
# llm_provider is only used for cache keying; use a string identifier but
# cast to Any so static type checkers don't complain about the literal.
provider_id = cast(Any, "litellm_module_level_client")
async_client = get_async_httpx_client(
llm_provider=provider_id,
params=params,
)
_globals["module_level_aclient"] = async_client
return async_client
if name == "module_level_client":
# Import handler type locally to avoid heavy imports at module load time
from litellm.llms.custom_httpx.http_handler import HTTPHandler
timeout = _globals.get("request_timeout")
sync_client = HTTPHandler(timeout=timeout)
_globals["module_level_client"] = sync_client
return sync_client
raise AttributeError(f"HTTP handlers lazy import: unknown attribute {name!r}")

View file

@ -12,9 +12,11 @@ from litellm._lazy_imports import (
COST_CALCULATOR_NAMES,
LITELLM_LOGGING_NAMES,
UTILS_NAMES,
HTTP_HANDLER_NAMES,
_lazy_import_cost_calculator,
_lazy_import_litellm_logging,
_lazy_import_utils,
_lazy_import_http_handlers,
)
@ -78,6 +80,18 @@ def test_utils_lazy_imports():
_verify_only_requested_name_imported(name, UTILS_NAMES)
def test_http_handler_lazy_imports():
"""Test that HTTP handler singletons can be lazy imported."""
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.__dict__
_verify_only_requested_name_imported(name, HTTP_HANDLER_NAMES)
def test_unknown_attribute_raises_error():
"""Test that unknown attributes raise AttributeError."""
with pytest.raises(AttributeError):