# 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)