mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix: address cross-version CI failures
This commit is contained in:
parent
c8f6531be2
commit
77d6aedf0a
25 changed files with 152 additions and 108 deletions
8
.github/workflows/_test-unit-base.yml
vendored
8
.github/workflows/_test-unit-base.yml
vendored
|
|
@ -102,12 +102,10 @@ jobs:
|
|||
timeout-minutes: 5
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
~/.cache/uv
|
||||
.venv
|
||||
key: ${{ runner.os }}-uv-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-uv-downloads-py${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-uv-py${{ matrix.python-version }}-
|
||||
${{ runner.os }}-uv-downloads-py${{ matrix.python-version }}-
|
||||
|
||||
- name: Cache the Rust build
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"limit": 40
|
||||
},
|
||||
"reportDeprecated": {
|
||||
"limit": 211
|
||||
"limit": 210
|
||||
},
|
||||
"reportDuplicateImport": {
|
||||
"limit": 19
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ datasource client {
|
|||
|
||||
generator client {
|
||||
provider = "prisma-client-py"
|
||||
recursive_type_depth = -1
|
||||
binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x", "linux-musl", "linux-musl-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5149,14 +5149,8 @@ def get_api_key(llm_provider: str, dynamic_api_key: str | None):
|
|||
return api_key
|
||||
|
||||
|
||||
def get_utc_datetime():
|
||||
import datetime as dt
|
||||
from datetime import datetime
|
||||
|
||||
if hasattr(dt, "UTC"):
|
||||
return datetime.now(dt.UTC)
|
||||
else:
|
||||
return datetime.utcnow()
|
||||
def get_utc_datetime() -> datetime.datetime:
|
||||
return datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
|
||||
def get_max_tokens(model: str) -> int | None:
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ dev = [
|
|||
"basedpyright==1.39.7",
|
||||
"keyring==25.7.0",
|
||||
"pytest==9.0.3",
|
||||
"tomli==2.4.1; python_version < '3.11'",
|
||||
"pytest-mock==3.15.1",
|
||||
"pytest-asyncio==1.3.0",
|
||||
"pytest-postgresql==7.0.2",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"limit": 809
|
||||
},
|
||||
"ANN201": {
|
||||
"limit": 2001
|
||||
"limit": 2000
|
||||
},
|
||||
"ANN202": {
|
||||
"limit": 835
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
"limit": 2
|
||||
},
|
||||
"DTZ003": {
|
||||
"limit": 26
|
||||
"limit": 25
|
||||
},
|
||||
"DTZ005": {
|
||||
"limit": 233
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ seen the red and accepted it.
|
|||
Usage:
|
||||
python scripts/budget_ratchet_check.py [--base REF] [budget.json ...]
|
||||
|
||||
Stdlib only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -33,11 +32,15 @@ import argparse
|
|||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from types import MappingProxyType
|
||||
from typing import NamedTuple
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_BASE = "origin/litellm_internal_staging"
|
||||
DEFAULT_BUDGETS: tuple[str, ...] = (
|
||||
|
|
|
|||
|
|
@ -18,13 +18,17 @@ import json
|
|||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from collections import defaultdict
|
||||
from difflib import SequenceMatcher
|
||||
from pathlib import Path
|
||||
from typing import Final, NamedTuple
|
||||
from textwrap import dedent
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
MUTMUT_INVOCATION = ["uv", "run", "--no-sync", "--with", "mutmut==3.5.0", "mutmut"]
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@
|
|||
"limit": 117
|
||||
},
|
||||
"TQ008": {
|
||||
"limit": 11135
|
||||
"limit": 11103
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ from pathlib import Path
|
|||
import re
|
||||
import sys
|
||||
import time
|
||||
import tomllib
|
||||
from typing import Callable, Dict, Final, List, Optional, Protocol, Set, Tuple
|
||||
|
||||
from packaging.requirements import Requirement
|
||||
import requests
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
DEFAULT_TRANSITIVE_PIN_PACKAGES = (
|
||||
"aiofiles",
|
||||
"anyio",
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ def _closed_port() -> int:
|
|||
pytest.param(lambda c: c.async_get_ttl("lit4930"), id="async_get_ttl"),
|
||||
],
|
||||
)
|
||||
async def test_circuit_breaker_opens_when_method_swallows_redis_failure(redis_no_ping, call_method):
|
||||
async def test_circuit_breaker_opens_when_method_swallows_redis_failure(call_method):
|
||||
"""A guarded method that swallows its own Redis error must still count as a failure.
|
||||
|
||||
These methods catch connection errors and return a default so callers degrade instead
|
||||
|
|
@ -495,7 +495,7 @@ async def test_circuit_breaker_opens_when_method_swallows_redis_failure(redis_no
|
|||
"""
|
||||
from litellm.constants import REDIS_CIRCUIT_BREAKER_FAILURE_THRESHOLD
|
||||
|
||||
cache = RedisCache(host="127.0.0.1", port=_closed_port(), socket_timeout=0.5)
|
||||
cache = await asyncio.to_thread(RedisCache, host="127.0.0.1", port=_closed_port(), socket_timeout=0.5)
|
||||
|
||||
for _ in range(REDIS_CIRCUIT_BREAKER_FAILURE_THRESHOLD):
|
||||
await call_method(cache)
|
||||
|
|
@ -505,7 +505,7 @@ async def test_circuit_breaker_opens_when_method_swallows_redis_failure(redis_no
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_circuit_breaker_success_still_resets_the_failure_streak(redis_no_ping):
|
||||
async def test_circuit_breaker_success_still_resets_the_failure_streak():
|
||||
"""A reachable Redis must keep the breaker closed, however many earlier calls failed.
|
||||
|
||||
The guard now records success only when nothing failed while the method ran, so this
|
||||
|
|
@ -514,7 +514,7 @@ async def test_circuit_breaker_success_still_resets_the_failure_streak(redis_no_
|
|||
"""
|
||||
from litellm.constants import REDIS_CIRCUIT_BREAKER_FAILURE_THRESHOLD
|
||||
|
||||
cache = RedisCache(host="127.0.0.1", port=_closed_port(), socket_timeout=0.5)
|
||||
cache = await asyncio.to_thread(RedisCache, host="127.0.0.1", port=_closed_port(), socket_timeout=0.5)
|
||||
|
||||
for _ in range(REDIS_CIRCUIT_BREAKER_FAILURE_THRESHOLD - 1):
|
||||
await cache.async_get_cache("lit4930")
|
||||
|
|
@ -532,7 +532,7 @@ async def test_circuit_breaker_success_still_resets_the_failure_streak(redis_no_
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_circuit_breaker_covers_lua_script_execution(redis_no_ping):
|
||||
async def test_circuit_breaker_covers_lua_script_execution():
|
||||
"""Lua script execution must feed the breaker like every other Redis call.
|
||||
|
||||
The v3 rate limiter issues all of its Redis traffic through async_register_script, so
|
||||
|
|
@ -544,7 +544,7 @@ async def test_circuit_breaker_covers_lua_script_execution(redis_no_ping):
|
|||
|
||||
from litellm.constants import REDIS_CIRCUIT_BREAKER_FAILURE_THRESHOLD
|
||||
|
||||
cache = RedisCache(host="127.0.0.1", port=_closed_port(), socket_timeout=0.5)
|
||||
cache = await asyncio.to_thread(RedisCache, host="127.0.0.1", port=_closed_port(), socket_timeout=0.5)
|
||||
run_script = cache.async_register_script("return 1")
|
||||
|
||||
for _ in range(REDIS_CIRCUIT_BREAKER_FAILURE_THRESHOLD):
|
||||
|
|
|
|||
|
|
@ -258,11 +258,9 @@ async def test_s3_cache_async_set_cache_pipeline(mock_s3_dependencies):
|
|||
|
||||
# Verify each call
|
||||
calls = cache.s3_client.put_object.call_args_list
|
||||
for i, (key, value) in enumerate(cache_list):
|
||||
call_args = calls[i][1]
|
||||
assert call_args["Bucket"] == "test-bucket"
|
||||
assert call_args["Key"] == key
|
||||
assert call_args["Body"] == json.dumps(value)
|
||||
assert {(call.kwargs["Bucket"], call.kwargs["Key"], call.kwargs["Body"]) for call in calls} == {
|
||||
("test-bucket", key, json.dumps(value)) for key, value in cache_list
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -285,10 +283,12 @@ async def test_s3_cache_concurrent_async_operations(mock_s3_dependencies):
|
|||
|
||||
# Verify each call had correct parameters
|
||||
calls = cache.s3_client.put_object.call_args_list
|
||||
for i, call in enumerate(calls):
|
||||
call_args = call[1]
|
||||
assert call_args["Bucket"] == "test-bucket"
|
||||
assert f"concurrent_key_{i}" == call_args["Key"]
|
||||
assert {call.kwargs["Key"] for call in calls} == {f"concurrent_key_{i}" for i in range(5)}
|
||||
for call in calls:
|
||||
assert call.kwargs["Bucket"] == "test-bucket"
|
||||
payload = json.loads(call.kwargs["Body"])
|
||||
assert call.kwargs["Key"] == f"concurrent_key_{payload['id']}"
|
||||
assert payload["data"] == f"test_data_{payload['id']}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ Covers:
|
|||
"""
|
||||
|
||||
import time
|
||||
from importlib import import_module
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -107,16 +108,16 @@ class TestResponsesStreamingIteratorMaxDuration:
|
|||
|
||||
def test_should_not_raise_when_duration_is_none(self):
|
||||
it = self._make_base_iterator()
|
||||
with patch(
|
||||
"litellm.responses.streaming_iterator.LITELLM_MAX_STREAMING_DURATION_SECONDS",
|
||||
with patch.object(
|
||||
import_module("litellm.responses.streaming_iterator"), "LITELLM_MAX_STREAMING_DURATION_SECONDS",
|
||||
None,
|
||||
):
|
||||
it._check_max_streaming_duration()
|
||||
|
||||
def test_should_not_raise_when_under_limit(self):
|
||||
it = self._make_base_iterator()
|
||||
with patch(
|
||||
"litellm.responses.streaming_iterator.LITELLM_MAX_STREAMING_DURATION_SECONDS",
|
||||
with patch.object(
|
||||
import_module("litellm.responses.streaming_iterator"), "LITELLM_MAX_STREAMING_DURATION_SECONDS",
|
||||
60.0,
|
||||
):
|
||||
it._check_max_streaming_duration()
|
||||
|
|
@ -124,8 +125,8 @@ class TestResponsesStreamingIteratorMaxDuration:
|
|||
def test_should_raise_timeout_when_exceeded(self):
|
||||
it = self._make_base_iterator()
|
||||
it._stream_created_time = time.time() - 20
|
||||
with patch(
|
||||
"litellm.responses.streaming_iterator.LITELLM_MAX_STREAMING_DURATION_SECONDS",
|
||||
with patch.object(
|
||||
import_module("litellm.responses.streaming_iterator"), "LITELLM_MAX_STREAMING_DURATION_SECONDS",
|
||||
10.0,
|
||||
):
|
||||
with pytest.raises(litellm.Timeout, match="max streaming duration"):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from importlib import import_module
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -163,8 +164,8 @@ async def test_anthropic_messages_with_mcp_forwards_the_callers_mcp_credentials(
|
|||
).LiteLLM_Proxy_MCP_Handler,
|
||||
"_process_mcp_tools_without_openai_transform",
|
||||
new=process,
|
||||
), patch(
|
||||
"litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._execute_tool_calls",
|
||||
), patch.object(
|
||||
import_module("litellm.responses.mcp.litellm_proxy_mcp_handler").LiteLLM_Proxy_MCP_Handler, "_execute_tool_calls",
|
||||
new=execute,
|
||||
), patch(
|
||||
"litellm.anthropic_messages", new=AsyncMock(side_effect=responses)
|
||||
|
|
@ -218,11 +219,11 @@ async def test_anthropic_messages_with_mcp_stops_when_every_tool_call_is_skipped
|
|||
|
||||
with patch.object(
|
||||
MCPRequestContext, "resolve", return_value=MCPRequestContext(user_api_key_auth="auth")
|
||||
), patch(
|
||||
"litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._process_mcp_tools_without_openai_transform",
|
||||
), patch.object(
|
||||
import_module("litellm.responses.mcp.litellm_proxy_mcp_handler").LiteLLM_Proxy_MCP_Handler, "_process_mcp_tools_without_openai_transform",
|
||||
new=AsyncMock(return_value=([], {})),
|
||||
), patch(
|
||||
"litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._execute_tool_calls",
|
||||
), patch.object(
|
||||
import_module("litellm.responses.mcp.litellm_proxy_mcp_handler").LiteLLM_Proxy_MCP_Handler, "_execute_tool_calls",
|
||||
new=AsyncMock(return_value=[]),
|
||||
), patch(
|
||||
"litellm.anthropic_messages", new=anthropic_messages_mock
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Coverage:
|
|||
|
||||
import base64
|
||||
from typing import Any, Dict, List, Optional
|
||||
from importlib import import_module
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -223,28 +224,28 @@ class TestFileSearchGuardInResponsesMain:
|
|||
expected = {"ok": True}
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.responses.main.litellm.get_llm_provider",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main").litellm, "get_llm_provider",
|
||||
return_value=("claude-sonnet-4-5", "anthropic", None, None),
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.update_responses_input_with_model_file_ids",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main"), "update_responses_input_with_model_file_ids",
|
||||
return_value="hello",
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.update_responses_tools_with_model_file_ids",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main"), "update_responses_tools_with_model_file_ids",
|
||||
return_value=tools,
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main").ProviderConfigManager, "get_provider_responses_api_config",
|
||||
return_value=None,
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.ResponsesAPIRequestUtils.get_requested_response_api_optional_param",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main").ResponsesAPIRequestUtils, "get_requested_response_api_optional_param",
|
||||
return_value={},
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.run_async_function", return_value=expected
|
||||
patch.object(
|
||||
import_module("litellm.responses.main"), "run_async_function", return_value=expected
|
||||
) as run_async_mock,
|
||||
):
|
||||
result = responses(
|
||||
|
|
@ -274,28 +275,28 @@ class TestFileSearchGuardInResponsesMain:
|
|||
mock_config.supports_native_file_search.return_value = False
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.responses.main.litellm.get_llm_provider",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main").litellm, "get_llm_provider",
|
||||
return_value=("claude-sonnet-4-5", "anthropic", None, None),
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.update_responses_input_with_model_file_ids",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main"), "update_responses_input_with_model_file_ids",
|
||||
return_value="hello",
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.update_responses_tools_with_model_file_ids",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main"), "update_responses_tools_with_model_file_ids",
|
||||
return_value=tools,
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main").ProviderConfigManager, "get_provider_responses_api_config",
|
||||
return_value=mock_config,
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.ResponsesAPIRequestUtils.get_requested_response_api_optional_param",
|
||||
patch.object(
|
||||
import_module("litellm.responses.main").ResponsesAPIRequestUtils, "get_requested_response_api_optional_param",
|
||||
return_value={},
|
||||
),
|
||||
patch(
|
||||
"litellm.responses.main.run_async_function", return_value=expected
|
||||
patch.object(
|
||||
import_module("litellm.responses.main"), "run_async_function", return_value=expected
|
||||
) as run_async_mock,
|
||||
):
|
||||
result = responses(
|
||||
|
|
@ -758,8 +759,8 @@ class TestEmulatedFileSearchHandler:
|
|||
mock_search_response.data = [search_result]
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.responses.file_search.emulated_handler._call_aresponses",
|
||||
patch.object(
|
||||
import_module("litellm.responses.file_search.emulated_handler"), "_call_aresponses",
|
||||
new=AsyncMock(side_effect=[first_resp, final_resp]),
|
||||
),
|
||||
patch(
|
||||
|
|
@ -821,8 +822,8 @@ class TestEmulatedFileSearchHandler:
|
|||
mock_search_response.data = [search_result]
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.responses.file_search.emulated_handler._call_aresponses",
|
||||
patch.object(
|
||||
import_module("litellm.responses.file_search.emulated_handler"), "_call_aresponses",
|
||||
new=AsyncMock(side_effect=[first_resp_plural, final_resp]),
|
||||
),
|
||||
patch(
|
||||
|
|
@ -855,8 +856,8 @@ class TestEmulatedFileSearchHandler:
|
|||
text="I already know the answer."
|
||||
)
|
||||
|
||||
with patch(
|
||||
"litellm.responses.file_search.emulated_handler._call_aresponses",
|
||||
with patch.object(
|
||||
import_module("litellm.responses.file_search.emulated_handler"), "_call_aresponses",
|
||||
new=AsyncMock(return_value=direct_resp),
|
||||
):
|
||||
result = await aresponses_with_emulated_file_search(
|
||||
|
|
@ -905,8 +906,8 @@ class TestEmulatedFileSearchHandler:
|
|||
mock_search_response.data = [search_result]
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.responses.file_search.emulated_handler._call_aresponses",
|
||||
patch.object(
|
||||
import_module("litellm.responses.file_search.emulated_handler"), "_call_aresponses",
|
||||
new=AsyncMock(side_effect=[first_resp, final_resp]),
|
||||
) as mock_call,
|
||||
patch(
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ def test_supported_hooks_limited_to_pre_and_post():
|
|||
|
||||
|
||||
def test_during_call_mode_rejected_at_init():
|
||||
with pytest.raises(ValueError, match='Event hook GuardrailEventHooks\\.during_call is not in the'):
|
||||
with pytest.raises(ValueError, match="during_call is not in the supported event hooks"):
|
||||
StraikerGuardrail(api_key="k", event_hook="during_call")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import subprocess
|
|||
import types
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock, MagicMock, mock_open, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, create_autospec, mock_open, patch
|
||||
|
||||
import click
|
||||
import httpx
|
||||
|
|
@ -696,6 +697,18 @@ def test_restructure_always_happens(monkeypatch):
|
|||
assert ui_path == packaged_ui_path
|
||||
|
||||
|
||||
def _mock_scheduled_proxy_config() -> MagicMock:
|
||||
config: Final = proxy_server_module.ProxyConfig()
|
||||
return MagicMock(
|
||||
spec=proxy_server_module.ProxyConfig,
|
||||
check_periodic_reloads=create_autospec(config.check_periodic_reloads),
|
||||
get_credentials=create_autospec(config.get_credentials),
|
||||
add_deployment=create_autospec(config.add_deployment),
|
||||
reload_search_tools_from_db=create_autospec(config.reload_search_tools_from_db),
|
||||
reload_mcp_servers_from_db=create_autospec(config.reload_mcp_servers_from_db),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_initialize_scheduled_jobs_credentials(monkeypatch):
|
||||
"""
|
||||
|
|
@ -711,7 +724,7 @@ async def test_initialize_scheduled_jobs_credentials(monkeypatch):
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config),
|
||||
|
|
@ -771,7 +784,7 @@ async def test_periodic_reload_job_scheduled_without_store_model_in_db(monkeypat
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
scheduler = AsyncIOScheduler()
|
||||
|
||||
try:
|
||||
|
|
@ -812,7 +825,7 @@ async def test_initialize_scheduled_jobs_uses_configured_config_reload_interval(
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
mock_scheduler = MagicMock()
|
||||
|
||||
configured_interval = 47
|
||||
|
|
@ -861,7 +874,7 @@ async def test_initialize_scheduled_jobs_rejects_non_positive_config_reload_inte
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
mock_scheduler = MagicMock()
|
||||
|
||||
with (
|
||||
|
|
@ -908,7 +921,7 @@ async def test_initialize_scheduled_jobs_hydrates_mcp_when_store_model_in_db_fal
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config),
|
||||
|
|
@ -7258,7 +7271,7 @@ async def test_batch_cost_poller_is_confirmed_before_serving(monkeypatch):
|
|||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", AsyncMock()),
|
||||
patch("litellm.proxy.proxy_server.proxy_config", _mock_scheduled_proxy_config()),
|
||||
patch("litellm.proxy.proxy_server.store_model_in_db", False),
|
||||
patch("litellm.proxy.proxy_server.llm_router", MagicMock()),
|
||||
patch("litellm.proxy.proxy_server.PROXY_BATCH_POLLING_ENABLED", True),
|
||||
|
|
@ -7300,7 +7313,7 @@ async def test_store_model_in_db_db_override_when_config_false():
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config),
|
||||
|
|
@ -7343,7 +7356,7 @@ async def test_store_model_in_db_db_check_skipped_when_already_true(monkeypatch)
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config),
|
||||
|
|
@ -7386,7 +7399,7 @@ async def test_store_model_in_db_db_failure_graceful(monkeypatch):
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config),
|
||||
|
|
@ -11640,7 +11653,7 @@ async def _run_scheduled_background_jobs():
|
|||
mock_proxy_logging = MagicMock(spec=ProxyLogging)
|
||||
mock_proxy_logging.slack_alerting_instance = MagicMock()
|
||||
mock_proxy_logging.db_spend_update_writer = MagicMock()
|
||||
mock_proxy_config = AsyncMock()
|
||||
mock_proxy_config = _mock_scheduled_proxy_config()
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ in expected_responses_api_request/.
|
|||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
from importlib import import_module
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import httpx
|
||||
|
|
@ -405,8 +406,8 @@ async def test_aresponses_websocket_strips_responses_routing_prefix_from_openai_
|
|||
|
||||
from litellm.responses.main import _aresponses_websocket
|
||||
|
||||
with patch(
|
||||
"litellm.responses.main.base_llm_http_handler.async_responses_websocket",
|
||||
with patch.object(
|
||||
import_module("litellm.responses.main").base_llm_http_handler, "async_responses_websocket",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_ws:
|
||||
await _aresponses_websocket(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Pydantic ValidationError (previously typed as Optional[str]).
|
|||
"""
|
||||
|
||||
import json
|
||||
from importlib import import_module
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -259,8 +260,8 @@ def test_handle_logging_failed_response_maps_rate_limit_to_429():
|
|||
{"type": "tokens", "code": "rate_limit_exceeded", "message": "throttled"}
|
||||
)
|
||||
with (
|
||||
patch("litellm.responses.streaming_iterator.run_async_function") as mock_run_async,
|
||||
patch("litellm.responses.streaming_iterator.executor"),
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "run_async_function") as mock_run_async,
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "executor"),
|
||||
):
|
||||
iterator._handle_logging_failed_response()
|
||||
logged_exception = mock_run_async.call_args.kwargs["exception"]
|
||||
|
|
@ -276,8 +277,8 @@ def test_handle_logging_failed_response_maps_type_field_to_400():
|
|||
{"type": "invalid_request_error", "code": "invalid_prompt", "message": "bad prompt"}
|
||||
)
|
||||
with (
|
||||
patch("litellm.responses.streaming_iterator.run_async_function") as mock_run_async,
|
||||
patch("litellm.responses.streaming_iterator.executor"),
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "run_async_function") as mock_run_async,
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "executor"),
|
||||
):
|
||||
iterator._handle_logging_failed_response()
|
||||
logged_exception = mock_run_async.call_args.kwargs["exception"]
|
||||
|
|
@ -296,8 +297,8 @@ def test_handle_logging_failed_response_records_usage_and_cost():
|
|||
iterator.completed_response = chunk
|
||||
iterator.logging_obj._response_cost_calculator.return_value = 0.0042
|
||||
with (
|
||||
patch("litellm.responses.streaming_iterator.run_async_function"),
|
||||
patch("litellm.responses.streaming_iterator.executor"),
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "run_async_function"),
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "executor"),
|
||||
):
|
||||
iterator._handle_logging_failed_response()
|
||||
combined_usage = iterator.logging_obj.model_call_details["combined_usage_object"]
|
||||
|
|
@ -315,8 +316,8 @@ def test_handle_logging_failed_response_without_usage_skips_recording():
|
|||
{"type": "server_error", "code": "server_error", "message": "boom"}
|
||||
)
|
||||
with (
|
||||
patch("litellm.responses.streaming_iterator.run_async_function"),
|
||||
patch("litellm.responses.streaming_iterator.executor"),
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "run_async_function"),
|
||||
patch.object(import_module("litellm.responses.streaming_iterator"), "executor"),
|
||||
):
|
||||
iterator._handle_logging_failed_response()
|
||||
assert "combined_usage_object" not in iterator.logging_obj.model_call_details
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Tests for litellm/router_strategy/auto_router/litellm_encoder.py"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Final
|
||||
|
||||
import pytest
|
||||
|
|
@ -7,6 +8,9 @@ import pytest
|
|||
|
||||
import litellm
|
||||
from litellm.constants import DEFAULT_AUTO_ROUTER_MAX_INPUT_CHARS
|
||||
if sys.version_info >= (3, 14):
|
||||
pytest.skip("The semantic-router extra excludes Python 3.14", allow_module_level=True)
|
||||
|
||||
from litellm.router_strategy.auto_router.litellm_encoder import LiteLLMRouterEncoder
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import json
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from typing_extensions import get_args, get_type_hints
|
||||
|
||||
import litellm
|
||||
from litellm.types.utils import ModelInfoBase
|
||||
|
|
@ -50,8 +50,8 @@ def _load_cost_map() -> dict:
|
|||
|
||||
|
||||
def test_realtime_is_a_valid_mode_literal():
|
||||
hints = typing.get_type_hints(ModelInfoBase, include_extras=False)
|
||||
assert "realtime" in typing.get_args(hints["mode"])
|
||||
hints = get_type_hints(ModelInfoBase, include_extras=False)
|
||||
assert "realtime" in get_args(hints["mode"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", REALTIME_ONLY_GPT_MODELS)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from fastapi.testclient import TestClient
|
|||
|
||||
|
||||
import urllib.parse
|
||||
from importlib import import_module
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import litellm
|
||||
|
|
@ -2515,8 +2516,8 @@ def test_completion_forwards_store_and_prompt_cache_key_to_mcp_gateway():
|
|||
prompt_cache_key are named params, so they no longer travel via **kwargs and
|
||||
must be forwarded explicitly like safety_identifier and service_tier.
|
||||
"""
|
||||
with patch(
|
||||
"litellm.responses.mcp.chat_completions_handler.acompletion_with_mcp"
|
||||
with patch.object(
|
||||
import_module("litellm.responses.mcp.chat_completions_handler"), "acompletion_with_mcp"
|
||||
) as mock_mcp:
|
||||
result = litellm.completion(
|
||||
model="openai/gpt-4o",
|
||||
|
|
|
|||
|
|
@ -4,11 +4,15 @@ import re
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
_MODULE_PATH = _REPO_ROOT / "scripts" / "ruff_strict_gate.py"
|
||||
_spec = importlib.util.spec_from_file_location("ruff_strict_gate", _MODULE_PATH)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import asyncio
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Final
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
|
@ -53,6 +54,15 @@ from litellm.utils import (
|
|||
# Adds the parent directory to the system path
|
||||
|
||||
|
||||
def test_get_utc_datetime_returns_current_aware_utc_time() -> None:
|
||||
before: Final = datetime.now(timezone.utc)
|
||||
result: Final = litellm.utils.get_utc_datetime()
|
||||
after: Final = datetime.now(timezone.utc)
|
||||
|
||||
assert result.utcoffset() == timedelta(0)
|
||||
assert before <= result <= after
|
||||
|
||||
|
||||
def test_usage_openai_cache_write_tokens_populates_both_names():
|
||||
"""OpenAI reports cache-write tokens as prompt_tokens_details.cache_write_tokens.
|
||||
The Usage constructor must expose it under both cache_write_tokens (canonical,
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -4453,6 +4453,7 @@ dev = [
|
|||
{ name = "responses" },
|
||||
{ name = "respx" },
|
||||
{ name = "ruff" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "types-boto3", extra = ["bedrock", "bedrock-agent", "bedrock-runtime", "kms", "s3", "sagemaker-runtime", "sts"] },
|
||||
{ name = "types-pyyaml" },
|
||||
{ name = "types-redis" },
|
||||
|
|
@ -4638,6 +4639,7 @@ dev = [
|
|||
{ name = "responses", specifier = "==0.26.0" },
|
||||
{ name = "respx", specifier = "==0.22.0" },
|
||||
{ name = "ruff", specifier = "==0.15.3" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'", specifier = "==2.4.1" },
|
||||
{ name = "types-boto3", extras = ["bedrock", "bedrock-agent", "bedrock-runtime", "kms", "s3", "sagemaker-runtime", "sts"], specifier = "==1.43.30" },
|
||||
{ name = "types-pyyaml", specifier = "==6.0.12.20250915" },
|
||||
{ name = "types-redis", specifier = "==4.6.0.20241004" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue