mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
* test: drop the cwd-relative sys.path.insert calls from the test suite
TQ003 stands at 1,077 across 1,058 files, and 1,015 of them are the same shape:
sys.path.insert(0, os.path.abspath("../..")) and its deeper siblings. The
argument resolves against the working directory rather than the file, so from
the repo root, where every job runs pytest, it inserts the directory two levels
above the checkout. It has never pointed at litellm. The package is installed
into the environment anyway, which is what actually makes the import work, and
what the rule's message has said all along.
Removing them leaves 1,634 imports of sys and os with no remaining reference,
and those go too, except where another test module imports the name back out of
the file. The rest of TQ003 is 62 call sites that resolve against __file__ or a
variable, which are a different question and are left alone.
Collection is identical either way: 45,871 tests and the same 51 pre-existing
collection errors before and after, and ruff reports no new undefined name.
* test: drop the duplicate imports the sys.path sweep exposed to F811
* test(pre-call-utils): restore the os import the new bedrock tests need
219 lines
6.3 KiB
Python
219 lines
6.3 KiB
Python
# conftest.py
|
|
|
|
import asyncio
|
|
import importlib
|
|
import os
|
|
import socket
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Iterator, Tuple
|
|
|
|
import pytest
|
|
import uvicorn
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
import litellm # noqa: E402,F401
|
|
|
|
from tests._vcr_conftest_common import ( # noqa: E402,F401
|
|
VerboseReporterState,
|
|
_pin_multipart_boundary,
|
|
apply_vcr_auto_marker_to_items,
|
|
emit_cassette_cache_session_banner,
|
|
emit_vcr_classification_summary,
|
|
emit_vcr_diagnostic_log,
|
|
install_live_call_probe,
|
|
record_vcr_outcome,
|
|
register_persister_if_enabled,
|
|
reset_vcr_diag_dir,
|
|
vcr_config_dict,
|
|
)
|
|
|
|
_verbose_state = VerboseReporterState()
|
|
|
|
PROXY_CONFIG_PATH = Path(__file__).parent / "google_genai_proxy_test_config.yaml"
|
|
PROXY_MASTER_KEY = "sk-1234"
|
|
PROXY_START_TIMEOUT_S = 30.0
|
|
|
|
|
|
def _start_proxy_server(
|
|
config_path: str,
|
|
) -> Tuple[str, uvicorn.Server, threading.Thread, socket.socket]:
|
|
from litellm.proxy.proxy_server import (
|
|
app as proxy_app,
|
|
cleanup_router_config_variables,
|
|
initialize,
|
|
)
|
|
|
|
cleanup_router_config_variables()
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
sock.bind(("127.0.0.1", 0))
|
|
host, port = sock.getsockname()
|
|
|
|
config = uvicorn.Config(proxy_app, host=host, port=port, log_level="warning")
|
|
server = uvicorn.Server(config)
|
|
|
|
def _run() -> None:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
loop.run_until_complete(initialize(config=config_path, debug=True))
|
|
loop.run_until_complete(server.serve(sockets=[sock]))
|
|
|
|
thread = threading.Thread(target=_run, daemon=True)
|
|
thread.start()
|
|
|
|
start_time = time.time()
|
|
while not server.started:
|
|
if not thread.is_alive():
|
|
raise RuntimeError("LiteLLM proxy failed to start")
|
|
if time.time() - start_time > PROXY_START_TIMEOUT_S:
|
|
raise TimeoutError("LiteLLM proxy did not start in time")
|
|
time.sleep(0.05)
|
|
|
|
return f"http://{host}:{port}", server, thread, sock
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def google_genai_proxy_url() -> Iterator[str]:
|
|
from base_google_genai_proxy_sdk_test import has_vertex_credentials
|
|
from base_google_test import load_vertex_ai_credentials
|
|
|
|
saved_env = {
|
|
key: os.environ.get(key)
|
|
for key in (
|
|
"DATABASE_URL",
|
|
"DIRECT_URL",
|
|
"LITELLM_MASTER_KEY",
|
|
"STORE_MODEL_IN_DB",
|
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
)
|
|
}
|
|
temp_credentials_path: str | None = None
|
|
os.environ.pop("DATABASE_URL", None)
|
|
os.environ.pop("DIRECT_URL", None)
|
|
os.environ["LITELLM_MASTER_KEY"] = PROXY_MASTER_KEY
|
|
os.environ["STORE_MODEL_IN_DB"] = "False"
|
|
|
|
if has_vertex_credentials():
|
|
credentials_file = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "")
|
|
if not (credentials_file and os.path.isfile(credentials_file)):
|
|
vertex_credentials_path = load_vertex_ai_credentials(
|
|
model="vertex_ai/gemini-2.5-flash-lite"
|
|
)
|
|
if vertex_credentials_path:
|
|
temp_credentials_path = vertex_credentials_path
|
|
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = vertex_credentials_path
|
|
|
|
server_url, server, thread, sock = _start_proxy_server(str(PROXY_CONFIG_PATH))
|
|
try:
|
|
yield server_url
|
|
finally:
|
|
server.should_exit = True
|
|
thread.join(timeout=10)
|
|
sock.close()
|
|
if temp_credentials_path:
|
|
try:
|
|
os.unlink(temp_credentials_path)
|
|
except OSError:
|
|
pass
|
|
for key, value in saved_env.items():
|
|
if value is None:
|
|
os.environ.pop(key, None)
|
|
else:
|
|
os.environ[key] = value
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def event_loop():
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
yield loop
|
|
loop.close()
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
def setup_and_teardown(request):
|
|
"""
|
|
This fixture reloads litellm before every function. To speed up testing by removing callbacks being chained.
|
|
"""
|
|
|
|
|
|
if "google_genai_proxy_url" not in request.fixturenames:
|
|
importlib.reload(litellm)
|
|
|
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
print(litellm)
|
|
yield
|
|
|
|
# Teardown code (executes after the yield point)
|
|
loop.close() # Close the loop created earlier
|
|
asyncio.set_event_loop(None) # Remove the reference to the loop
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def vcr_config():
|
|
return vcr_config_dict()
|
|
|
|
|
|
def pytest_recording_configure(config, vcr):
|
|
register_persister_if_enabled(vcr)
|
|
|
|
|
|
@pytest.hookimpl(hookwrapper=True)
|
|
def pytest_runtest_makereport(item, call):
|
|
outcome = yield
|
|
rep = outcome.get_result()
|
|
setattr(item, f"rep_{rep.when}", rep)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _vcr_outcome_gate(request, vcr):
|
|
install_live_call_probe(request, vcr)
|
|
yield
|
|
record_vcr_outcome(request, vcr)
|
|
|
|
|
|
def pytest_configure(config):
|
|
_verbose_state.remember_pluginmanager(config)
|
|
reset_vcr_diag_dir()
|
|
|
|
|
|
def pytest_runtest_logreport(report):
|
|
_verbose_state.maybe_emit_verdict(report)
|
|
|
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
apply_vcr_auto_marker_to_items(
|
|
items,
|
|
skip_nodeid_suffixes=(
|
|
"test_proxy_genai_sdk_non_streaming",
|
|
"test_proxy_genai_sdk_streaming_completes_without_errors",
|
|
"test_proxy_genai_sdk_streaming_dict_style",
|
|
),
|
|
)
|
|
|
|
# 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
|
|
|
|
|
|
def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
|
emit_cassette_cache_session_banner(terminalreporter)
|
|
emit_vcr_classification_summary(terminalreporter)
|
|
emit_vcr_diagnostic_log(terminalreporter)
|