mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-12 11:07:15 +00:00
5bafa8b3a2
PR was blocked by .github/workflows/guard-fork-dependencies.yml: fork PRs cannot modify uv.lock. Reverting: - uv.lock + pyproject.toml black bump (24.10.0 -> 26.3.1) and the 295 files of mechanical Black 26 reformat coupled to it - pyproject.toml diskcache extra change (kept the runtime mitigation in litellm/caching/disk_cache.py via JSONDisk) Kept: - Dockerfile cache narrowing (drops ~660 MB of uv build cache that surfaced cached setuptools as CVE findings) - litellm/caching/disk_cache.py: dc.JSONDisk to neutralize CVE-2025-69872 - ui/litellm-dashboard/package-lock.json + litellm-js/spend-logs/package-lock.json: next/postcss/hono/uuid CVE bumps (these are not blocked by the fork guard) - tests/test_litellm/caching/test_disk_cache.py - tests/code_coverage_tests/liccheck.ini: harmless black authorization Black + gitpython + langchain dep upgrades will need a follow-up from a maintainer pushing a branch in the canonical BerriAI/litellm repo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
156 lines
5.3 KiB
Python
156 lines
5.3 KiB
Python
# conftest.py
|
|
|
|
import asyncio
|
|
import copy
|
|
import inspect
|
|
import os
|
|
import sys
|
|
import warnings
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(
|
|
0, os.path.abspath("../..")
|
|
) # Adds the parent directory to the system path
|
|
|
|
import litellm
|
|
import litellm.proxy.proxy_server
|
|
|
|
|
|
# Top-level assignments of these types are the ones importlib.reload(litellm)
|
|
# would have effectively reset. We snapshot them at conftest import time and
|
|
# deep-copy the snapshot back before every test.
|
|
_SNAPSHOT_TYPES = (list, dict, set, tuple, str, int, float, bool, bytes)
|
|
|
|
|
|
def _snapshot_mutable_state(module):
|
|
"""Capture a per-module snapshot of primitive and collection attributes."""
|
|
snapshot = {}
|
|
for attr in list(vars(module)):
|
|
if attr.startswith("_"):
|
|
continue
|
|
try:
|
|
value = getattr(module, attr)
|
|
except Exception as exc:
|
|
warnings.warn(
|
|
f"conftest: could not read {module.__name__}.{attr} during snapshot: {exc}",
|
|
stacklevel=2,
|
|
)
|
|
continue
|
|
if value is None or isinstance(value, _SNAPSHOT_TYPES):
|
|
try:
|
|
snapshot[attr] = copy.deepcopy(value)
|
|
except Exception as exc:
|
|
warnings.warn(
|
|
f"conftest: could not snapshot {module.__name__}.{attr}: {exc}",
|
|
stacklevel=2,
|
|
)
|
|
return snapshot
|
|
|
|
|
|
def _restore_mutable_state(module, snapshot):
|
|
for attr, default in snapshot.items():
|
|
try:
|
|
setattr(module, attr, copy.deepcopy(default))
|
|
except Exception as exc:
|
|
warnings.warn(
|
|
f"conftest: could not restore {module.__name__}.{attr}: {exc}",
|
|
stacklevel=2,
|
|
)
|
|
|
|
|
|
def _collect_flushable_caches():
|
|
"""Return (module, attr) pairs whose values expose flush_cache()."""
|
|
targets = []
|
|
for module in (litellm, litellm.proxy.proxy_server):
|
|
for attr in list(vars(module)):
|
|
if attr.startswith("_"):
|
|
continue
|
|
try:
|
|
value = getattr(module, attr)
|
|
except Exception:
|
|
continue
|
|
# Only instances — a class reference has an unbound flush_cache
|
|
# that can't be called without a self argument.
|
|
if inspect.isclass(value) or inspect.ismodule(value):
|
|
continue
|
|
if callable(getattr(value, "flush_cache", None)):
|
|
targets.append((module, attr))
|
|
return targets
|
|
|
|
|
|
def _flush_caches(targets):
|
|
for module, attr in targets:
|
|
try:
|
|
value = getattr(module, attr)
|
|
except Exception:
|
|
continue
|
|
flush = getattr(value, "flush_cache", None)
|
|
if callable(flush):
|
|
try:
|
|
flush()
|
|
except Exception as exc:
|
|
warnings.warn(
|
|
f"conftest: flush_cache failed on {module.__name__}.{attr}: {exc}",
|
|
stacklevel=2,
|
|
)
|
|
|
|
|
|
# Snapshot once at conftest import — these are the "clean" module states.
|
|
_LITELLM_STATE = _snapshot_mutable_state(litellm)
|
|
_PROXY_SERVER_STATE = _snapshot_mutable_state(litellm.proxy.proxy_server)
|
|
_FLUSHABLE_CACHES = _collect_flushable_caches()
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
def setup_and_teardown():
|
|
"""Reset mutable module state on litellm and proxy_server before each test.
|
|
|
|
Replaces a previous importlib.reload(litellm) approach that cost ~17s
|
|
per test (re-executing the full litellm __init__ import chain).
|
|
|
|
What IS reset:
|
|
- Top-level module attributes of type list / dict / set / tuple
|
|
/ str / int / float / bool / bytes, and None-valued attributes.
|
|
These cover callback lists, general_settings, master_key,
|
|
premium_user, prisma_client, etc. — anything the old reload() reset
|
|
by re-executing the module body.
|
|
- Any module-level object instance that exposes flush_cache() (the
|
|
DualCache and LLMClientCache family), which handles cache state
|
|
that can't round-trip through deepcopy because of internal locks.
|
|
|
|
What is NOT reset:
|
|
- Class instances without flush_cache() (e.g. ProxyLogging,
|
|
JWTHandler, FastAPI routers, loggers). If a test mutates such an
|
|
instance in-place (setattr on the instance, appending to one of
|
|
its internal lists, etc.), the mutation will leak into later tests.
|
|
Use pytest's monkeypatch.setattr() or a local fixture for those
|
|
cases — don't rely on this autouse fixture to undo them.
|
|
"""
|
|
_restore_mutable_state(litellm, _LITELLM_STATE)
|
|
_restore_mutable_state(litellm.proxy.proxy_server, _PROXY_SERVER_STATE)
|
|
_flush_caches(_FLUSHABLE_CACHES)
|
|
|
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
yield
|
|
finally:
|
|
loop.close()
|
|
asyncio.set_event_loop(None)
|
|
|
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
# Separate tests in 'test_amazing_proxy_custom_logger.py' and other tests
|
|
custom_logger_tests = [
|
|
item for item in items if "custom_logger" in item.parent.name
|
|
]
|
|
other_tests = [item for item in items if "custom_logger" not in item.parent.name]
|
|
|
|
# Sort tests based on their names
|
|
custom_logger_tests.sort(key=lambda x: x.name)
|
|
other_tests.sort(key=lambda x: x.name)
|
|
|
|
# Reorder the items list
|
|
items[:] = custom_logger_tests + other_tests
|