litellm/tests/local_testing/test_acompletion_fallbacks.py
ryan-crabbe-berri e9d40a8f73 test: enforce F811 so a duplicate definition cannot silently replace the first
A name bound twice keeps only the second binding. In `tests/` that is nearly
always a repeated import, harmless but misleading, and the same rule is what
catches the cases that are not harmless: a local that shadows an import the
module still calls, and a second `def test_x` that quietly replaces the first.

311 of the 344 sites were repeated imports and came out with ruff's own fix.
The remaining 33 needed a decision. Four modules imported a name they never
used because a local definition below already shadowed it. Two comprehensions
bound `call` over `unittest.mock.call`, which those modules import and use.
One test rebound the two module handles its nested reload closure had captured.
One class attribute shadowed an unused `status` import.

The load-test fixtures move to a conftest, which is how pytest is meant to share
them, so the test module no longer imports three fixture names it never calls.
The nine `prisma_client` parameters keep a narrow `noqa`: pytest resolves that
fixture by name before the body runs, so the parameter never shadows anything.
2026-08-21 12:06:19 -07:00

102 lines
2.9 KiB
Python

import asyncio
import os
import sys
import time
import traceback
import pytest
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
import concurrent
from dotenv import load_dotenv
import litellm
@pytest.mark.asyncio
async def test_acompletion_fallbacks_basic():
response = await litellm.acompletion(
model="openai/unknown-model",
messages=[{"role": "user", "content": "Hello, world!"}],
fallbacks=["openai/gpt-4o-mini"],
)
print(response)
assert response is not None
@pytest.mark.asyncio
async def test_acompletion_fallbacks_bad_models():
"""
Test that the acompletion call times out after 10 seconds - if no fallbacks work
"""
try:
# Wrap the acompletion call with asyncio.wait_for to enforce a timeout
response = await asyncio.wait_for(
litellm.acompletion(
model="openai/unknown-model",
messages=[{"role": "user", "content": "Hello, world!"}],
fallbacks=["openai/bad-model", "openai/unknown-model"],
),
timeout=5.0, # Timeout after 5 seconds
)
assert response is not None
except asyncio.TimeoutError:
pytest.fail("Test timed out - possible infinite loop in fallbacks")
except Exception as e:
print(e)
pass
@pytest.mark.asyncio
async def test_acompletion_fallbacks_with_dict_config():
"""
Test fallbacks with dictionary configuration that includes model-specific settings
"""
response = await litellm.acompletion(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Hello, world!"}],
api_key="very-bad-api-key",
fallbacks=[{"api_key": os.getenv("OPENAI_API_KEY")}],
)
assert response is not None
@pytest.mark.asyncio
async def test_acompletion_fallbacks_empty_list():
"""
Test behavior when fallbacks list is empty
"""
try:
response = await litellm.acompletion(
model="openai/unknown-model",
messages=[{"role": "user", "content": "Hello, world!"}],
fallbacks=[],
)
except Exception as e:
assert isinstance(e, litellm.NotFoundError)
@pytest.mark.asyncio
async def test_acompletion_fallbacks_none_response():
"""
Test handling when a fallback model returns None
Should continue to next fallback rather than returning None
"""
response = await litellm.acompletion(
model="openai/unknown-model",
messages=[{"role": "user", "content": "Hello, world!"}],
fallbacks=["gpt-3.5-turbo"], # replace with a model you know works
)
assert response is not None
async def test_completion_fallbacks_sync():
response = litellm.completion(
model="openai/unknown-model",
messages=[{"role": "user", "content": "Hello, world!"}],
fallbacks=["openai/gpt-4o-mini"],
)
print(response)
assert response is not None