feat: lazy load GuardrailItem following existing patterns (#18072)

- Add TYPES_NAMES tuple and _lazy_import_types function in _lazy_imports.py
- Remove direct import of GuardrailItem from __init__.py
- Add lazy loading handler in __getattr__ to dispatch type imports
- Add type stub for GuardrailItem in TYPE_CHECKING block
- Add test_types_lazy_imports test to verify lazy loading works
- Use from __future__ import annotations for forward reference support
- Follows same pattern as DOTPROMPT_NAMES and LLM_CONFIG_NAMES for consistency
This commit is contained in:
Alexsander Hamir
2025-12-16 11:48:36 -08:00
committed by GitHub
parent d918c7b8a3
commit 80d445a96f
3 changed files with 49 additions and 1 deletions
+10 -1
View File
@@ -1,4 +1,6 @@
### Hide pydantic namespace conflict warnings globally ###
from __future__ import annotations
import warnings
warnings.filterwarnings("ignore", message=".*conflict with protected namespace.*")
@@ -72,7 +74,6 @@ from litellm.constants import (
DEFAULT_SOFT_BUDGET,
DEFAULT_ALLOWED_FAILS,
)
from litellm.types.guardrails import GuardrailItem
from litellm.types.secret_managers.main import (
KeyManagementSystem,
KeyManagementSettings,
@@ -1506,6 +1507,7 @@ if TYPE_CHECKING:
PriorityReservationDict,
StandardKeyGenerationConfig,
)
from litellm.types.guardrails import GuardrailItem
# Cost calculator functions
cost_per_token: Callable[..., Tuple[float, float]]
@@ -1568,6 +1570,7 @@ def __getattr__(name: str) -> Any:
HTTP_HANDLER_NAMES,
DOTPROMPT_NAMES,
LLM_CONFIG_NAMES,
TYPES_NAMES,
)
# Lazy load cost_calculator functions
@@ -1628,6 +1631,12 @@ def __getattr__(name: str) -> Any:
return _lazy_import_llm_configs(name)
# Lazy load types
if name in TYPES_NAMES:
from ._lazy_imports import _lazy_import_types
return _lazy_import_types(name)
# Lazy load encoding from main.py to avoid heavy tiktoken import
if name == "encoding":
from .main import encoding as _encoding
+20
View File
@@ -159,6 +159,11 @@ LLM_CONFIG_NAMES = (
"AmazonConverseConfig",
)
# Types that support lazy loading via _lazy_import_types
TYPES_NAMES = (
"GuardrailItem",
)
# 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,
@@ -619,6 +624,21 @@ def _lazy_import_dotprompt(name: str) -> Any:
raise AttributeError(f"Dotprompt lazy import: unknown attribute {name!r}")
def _lazy_import_types(name: str) -> Any:
"""Lazy import for type classes."""
_globals = _get_litellm_globals()
if name == "GuardrailItem":
from litellm.types.guardrails import (
GuardrailItem as _GuardrailItem,
)
_globals["GuardrailItem"] = _GuardrailItem
return _GuardrailItem
raise AttributeError(f"Types lazy import: unknown attribute {name!r}")
def _lazy_import_llm_configs(name: str) -> Any:
"""Lazy import for LLM config classes."""
_globals = _get_litellm_globals()
+19
View File
@@ -31,6 +31,8 @@ from litellm._lazy_imports import (
_lazy_import_dotprompt,
LLM_CONFIG_NAMES,
_lazy_import_llm_configs,
TYPES_NAMES,
_lazy_import_types,
)
@@ -213,6 +215,9 @@ def test_unknown_attribute_raises_error():
with pytest.raises(AttributeError):
_lazy_import_llm_configs("unknown")
with pytest.raises(AttributeError):
_lazy_import_types("unknown")
def test_llm_config_lazy_imports():
"""Test that LLM config classes can be lazy imported."""
@@ -227,3 +232,17 @@ def test_llm_config_lazy_imports():
_verify_only_requested_name_imported(name, LLM_CONFIG_NAMES)
def test_types_lazy_imports():
"""Test that type classes can be lazy imported."""
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.__dict__
# Type classes should be classes/types
assert isinstance(obj, type), f"{name} should be a class"
_verify_only_requested_name_imported(name, TYPES_NAMES)