fix: satisfy stable/1.101.x lint and api-sync gates for the jev backport

This commit is contained in:
mateo 2026-09-23 01:22:41 +00:00
parent 662a89290f
commit 7e6d9e89ed
15 changed files with 193 additions and 172 deletions

View file

@ -5229,7 +5229,9 @@ async def _check_team_member_model_access(
):
return # no per-member restriction — inherit team-level check
member_allowed_models: Final[list[str]] = loaded_membership.litellm_budget_table.allowed_models
member_allowed_models: Final[list[str]] = (
loaded_membership.litellm_budget_table.allowed_models
) # mutable-ok: allowed_models is a prisma model list consumed read-only
try:
_can_object_call_model(
model=model,

View file

@ -59,7 +59,7 @@ class TeamGrants(TypedDict, total=False):
team_tpd_limit: ReadOnly[int | None]
team_max_budget: ReadOnly[float | None]
team_soft_budget: ReadOnly[float | None]
team_model_max_budget: ReadOnly[dict[str, object] | None]
team_model_max_budget: ReadOnly[dict[str, object] | None] # mutable-ok: prisma JSON column deserialized per-request
team_spend: ReadOnly[float | None]
team_models: ReadOnly[Sequence[str]]
team_blocked: ReadOnly[bool]

View file

@ -107,7 +107,7 @@ def cross_entry_family_error(
"""
if not callback_vars:
return None
stored_by_var: Final = {
stored_by_var: Final = { # mutable-ok: config dict is updated in place per callback entry
var: value for entry in stored_vars_by_entry for var, value in entry.items() if _family_of(var) is not None
}
family_values: Final = frozenset(

View file

@ -20,8 +20,8 @@ if TYPE_CHECKING:
def _coerce_event_hook(
mode: str | list[str] | Mode,
) -> GuardrailEventHooks | list[GuardrailEventHooks] | Mode:
mode: str | list[str] | Mode, # mutable-ok: event hook unions accept an ordered list
) -> GuardrailEventHooks | list[GuardrailEventHooks] | Mode: # mutable-ok: event hook unions accept an ordered list
if isinstance(mode, Mode):
return mode
if isinstance(mode, list):

View file

@ -60,14 +60,14 @@ _STR_OBJECT_DICT_ADAPTER: Final = TypeAdapter(dict[str, object])
_OBJECT_LIST_ADAPTER: Final = TypeAdapter(list[object])
def _as_str_object_dict(value: object) -> dict[str, object] | None:
def _as_str_object_dict(value: object) -> dict[str, object] | None: # mutable-ok: parsed dict feeds the JSON log record
try:
return _STR_OBJECT_DICT_ADAPTER.validate_python(value)
except ValidationError:
return None
def _as_object_list(value: object) -> list[object] | None:
def _as_object_list(value: object) -> list[object] | None: # mutable-ok: tool call list is serialized to JSON
try:
return _OBJECT_LIST_ADAPTER.validate_python(value)
except ValidationError:
@ -120,7 +120,7 @@ def _question_instructions(question_id: str) -> str:
)
def _tool_call_entry(tool_call: object) -> dict[str, object] | None:
def _tool_call_entry(tool_call: object) -> dict[str, object] | None: # mutable-ok: entry dict is serialized to JSON
parsed_call = _as_str_object_dict(tool_call)
if parsed_call is None:
return None
@ -129,7 +129,9 @@ def _tool_call_entry(tool_call: object) -> dict[str, object] | None:
return {"name": fn.get("name"), "arguments": fn.get("arguments")} # mutable-ok: serialized to JSON
def _tool_call_entries(assistant_message: Mapping[str, object]) -> tuple[dict[str, object], ...]:
def _tool_call_entries(
assistant_message: Mapping[str, object],
) -> tuple[dict[str, object], ...]: # mutable-ok: entry dicts are serialized to JSON
tool_calls: Final = _as_object_list(assistant_message.get("tool_calls"))
if tool_calls is None:
return ()
@ -158,7 +160,10 @@ class TypeSafeGuardrail(CustomGuardrail):
max_result_chars_in_state: int | None = None,
unreachable_fallback: str | None = None,
guardrail_name: str | None = None,
event_hook: GuardrailEventHooks | list[GuardrailEventHooks] | Mode | None = None,
event_hook: GuardrailEventHooks
| list[GuardrailEventHooks]
| Mode
| None = None, # mutable-ok: event hook unions accept an ordered list
default_on: bool = False,
async_handler: AsyncHTTPHandler | None = None,
) -> None:
@ -190,7 +195,7 @@ class TypeSafeGuardrail(CustomGuardrail):
default_on=default_on,
)
def _handle_failure(self, error: str, log_detail: dict[str, object]) -> None:
def _handle_failure(self, error: str, log_detail: dict[str, object]) -> None: # mutable-ok: log detail record
"""fail_open logs and returns; fail_closed raises a generic 502 (upstream bodies stay in server logs)."""
if self.unreachable_fallback == "fail_open":
verbose_proxy_logger.warning(
@ -202,7 +207,9 @@ class TypeSafeGuardrail(CustomGuardrail):
verbose_proxy_logger.error("TypeSafe: %s. detail=%s", error, log_detail)
raise HTTPException(status_code=502, detail={"error": error}) # mutable-ok: FastAPI wants a dict detail
def _candidate_exchanges(self, messages: Sequence[dict[str, object]]) -> tuple[tuple[int, ...], ...]:
def _candidate_exchanges(
self, messages: Sequence[dict[str, object]]
) -> tuple[tuple[int, ...], ...]: # mutable-ok: message dicts come from the request payload
"""Completed tool exchanges eligible for evaluation: unprotected, and long enough to be worth a call."""
protected: Final = _protected_indices(messages)
candidates: Final = tuple(
@ -216,7 +223,9 @@ class TypeSafeGuardrail(CustomGuardrail):
return candidates[-_MAX_EXCHANGES_EVALUATED:]
@staticmethod
def _exchange_tool_text(messages: Sequence[dict[str, object]], group: tuple[int, ...]) -> str:
def _exchange_tool_text(
messages: Sequence[dict[str, object]], group: tuple[int, ...]
) -> str: # mutable-ok: message dicts come from the request payload
return "".join(
content_to_text(messages[index].get("content"))
for index in group[1:]
@ -224,8 +233,10 @@ class TypeSafeGuardrail(CustomGuardrail):
)
def _build_state(
self, messages: Sequence[dict[str, object]], candidates: tuple[tuple[int, ...], ...]
) -> dict[str, object]:
self,
messages: Sequence[dict[str, object]],
candidates: tuple[tuple[int, ...], ...], # mutable-ok: candidate groups index request message dicts
) -> dict[str, object]: # mutable-ok: result dict feeds the JSON log record
task: Final = next(
(
content_to_text(messages[index].get("content"))
@ -249,7 +260,9 @@ class TypeSafeGuardrail(CustomGuardrail):
return {"task": task, "system": system, "tool_exchanges": tool_exchanges} # mutable-ok: serialized to JSON
async def _call_systemone(
self, state: dict[str, object], question_ids: Sequence[str]
self,
state: dict[str, object],
question_ids: Sequence[str], # mutable-ok: state dict is the parsed log record
) -> _JevSystemOneResponse | None:
"""Returns the response, or None when the service failed and fail_open applies."""
payload: Final[dict[str, object]] = { # mutable-ok: serialized to JSON by httpx
@ -275,8 +288,8 @@ class TypeSafeGuardrail(CustomGuardrail):
)
except asyncio.CancelledError:
raise
except Exception as e:
detail: Final[dict[str, object]] = (
except Exception as e: # noqa: BLE001 # a logging-path failure must never break the guardrail call
detail: Final[dict[str, object]] = ( # mutable-ok: log detail record
{ # mutable-ok: log detail record
"error_type": type(e).__name__,
"detail": str(e),
@ -318,7 +331,7 @@ class TypeSafeGuardrail(CustomGuardrail):
async def apply_guardrail(
self,
inputs: GenericGuardrailAPIInputs,
request_data: dict[str, object],
request_data: dict[str, object], # mutable-ok: request-shaped dict
input_type: Literal["request", "response"],
logging_obj: LiteLLMLoggingObj | None = None,
) -> GenericGuardrailAPIInputs:

View file

@ -356,7 +356,8 @@ async def add_team_callbacks(
decrypted_logging: Final = decrypt_callback_vars(team_metadata).get("logging")
stored_entries: Final = decrypted_logging if isinstance(decrypted_logging, list) else ()
stored_entry_vars: Final = [ # mutable-ok: read-only input to the check, never stored
entry.get("callback_vars") or {} for entry in stored_entries
entry.get("callback_vars") or {}
for entry in stored_entries # mutable-ok: callback settings dict is assembled from the request body
]
family_error: Final = cross_entry_family_error(data.callback_vars, stored_entry_vars)
if family_error is not None:

View file

@ -66,7 +66,7 @@ class TypeSafePassthroughLoggingHandler:
cache_hit: bool,
request_body: Mapping[str, object],
custom_llm_provider: str,
**kwargs: object,
**kwargs: object, # kwargs-ok: logging handler receives the standard logging kwargs
) -> PassThroughEndpointLoggingTypedDict:
response: Final = _parse_typesafe_response(response_body)
response_model: Final = response.model

View file

@ -93,10 +93,12 @@ class _LiteLLMParamsDictView:
return dict(self._params)
async def _push_increments_to_redis(redis_cache: RedisCache, queued: list[RedisPipelineIncrementOperation]) -> None:
async def _push_increments_to_redis(
redis_cache: RedisCache, queued: list[RedisPipelineIncrementOperation]
) -> None: # mutable-ok: pipeline ops list is built once and consumed by redis
try:
await redis_cache.async_increment_pipeline(increment_list=queued)
except Exception as e:
except Exception as e: # noqa: BLE001 # a budget push failure must not abort the pipeline
log_redis_failure(verbose_router_logger, logging.ERROR, "Error syncing in-memory cache with Redis", e)

View file

@ -497,7 +497,7 @@ def _human_text(content: object, marker_pairs: tuple[tuple[str, str], ...] = _DE
def _encrypted_classifier_task(
request_kwargs: Mapping[str, object] | None,
marker_pairs: tuple[tuple[str, str], ...],
) -> dict[str, object] | None:
) -> dict[str, object] | None: # mutable-ok: task dict mirrors the request payload shape
from litellm.litellm_core_utils.prompt_templates.factory import resolve_structured_messages
raw_input: Final = (request_kwargs or EMPTY_MAPPING).get("input")
@ -511,7 +511,9 @@ def _encrypted_classifier_task(
(
item
for item in reversed(items)
if (messages := resolve_structured_messages(messages=None, request_kwargs={"input": [item]}))
if (
messages := resolve_structured_messages(messages=None, request_kwargs={"input": [item]})
) # mutable-ok: request-shaped literal
and any(_iter_human_asks_newest_first(messages, marker_pairs))
),
None,
@ -524,9 +526,11 @@ def _encrypted_classifier_task(
return None
if not any(part.get("type") == "encrypted_content" and part.get("encrypted_content") for part in parts):
return None
return {
return { # mutable-ok: request-shaped dict
**current,
"content": [part for part in parts if part.get("type") in ("input_text", "encrypted_content")],
"content": [
part for part in parts if part.get("type") in ("input_text", "encrypted_content")
], # mutable-ok: request-shaped dict
}
@ -2133,7 +2137,9 @@ class ComplexityRouter(CustomLogger):
if llm_config is None or classifier_system_prompt is None or classifier_response_format is None:
raise ValueError("classifier_llm_config is not set")
marker_pairs: Final = self._reminder_markers_for_request(request_kwargs or {})
marker_pairs: Final = self._reminder_markers_for_request(
request_kwargs or {}
) # mutable-ok: empty kwargs fallback
encrypted_task: Final = _encrypted_classifier_task(request_kwargs, marker_pairs)
user_payload: Final = self._classifier_context_payload(
prompt, system_prompt, request_kwargs, messages, encrypted_task=encrypted_task is not None

View file

@ -1102,7 +1102,9 @@ def test_async_http_handler(mock_async_client):
concurrent_limit = 2
# Mock the transport creation to return a specific transport
with mock.patch.object(AsyncHTTPHandler, "_create_async_transport") as mock_create_transport:
with mock.patch.object(
AsyncHTTPHandler, "_create_async_transport"
) as mock_create_transport: # test-quality-ok: the patched global is the collaborator's only injection seam
mock_transport = mock.MagicMock()
mock_create_transport.return_value = mock_transport
@ -1548,7 +1550,9 @@ def test_get_valid_models_openai_proxy(monkeypatch):
mock_response.status_code = 200
mock_response.json.return_value = mock_response_data
with patch.object(litellm.module_level_client, "get", return_value=mock_response) as mock_post:
with patch.object(
litellm.module_level_client, "get", return_value=mock_response
) as mock_post: # test-quality-ok: the patched global is the collaborator's only injection seam
valid_models = get_valid_models(check_provider_endpoint=True)
assert "litellm_proxy/gpt-5.5" in valid_models
@ -1625,7 +1629,9 @@ def test_get_valid_models_fireworks_ai(monkeypatch):
mock_response.status_code = 200
mock_response.json.return_value = mock_response_data
with patch.object(litellm.module_level_client, "get", return_value=mock_response) as mock_post:
with patch.object(
litellm.module_level_client, "get", return_value=mock_response
) as mock_post: # test-quality-ok: the patched global is the collaborator's only injection seam
valid_models = get_valid_models(check_provider_endpoint=True)
print("valid_models", valid_models)
mock_post.assert_called_once()

View file

@ -308,22 +308,21 @@ class TestModelManagementAuthChecks:
@pytest.mark.asyncio
async def test_add_new_model_rejects_credential_attach_for_non_admin(self):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
add_new_model,
)
mock_prisma = MagicMock()
with (
patch(
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", mock_prisma
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
),
patch( # test-quality-ok: prior auth check needs a live DB; only the credential check is under test
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
new=AsyncMock(return_value=None),
@ -345,7 +344,6 @@ class TestModelManagementAuthChecks:
@pytest.mark.asyncio
async def test_patch_model_rejects_credential_attach_for_non_admin(self):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
@ -358,18 +356,18 @@ class TestModelManagementAuthChecks:
model_info={"id": model_id},
)
with (
patch(
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", MagicMock()
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.llm_router", MagicMock()
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
),
patch( # test-quality-ok: stubs the DB row fetch; only the credential check is under test
"litellm.proxy.management_endpoints.model_management_endpoints.get_db_model",
new=AsyncMock(return_value=db_model),
@ -990,9 +988,6 @@ class TestUpdateModel:
model-level guardrails (and any other litellm_params change) silently no-op
until the APScheduler reload tick fires ~30 s later.
"""
from litellm.proxy.management_endpoints.model_management_endpoints import (
update_model,
)
from litellm.types.router import (
ModelInfo,
updateDeployment,
@ -1354,7 +1349,9 @@ class TestTeamModelUpdate:
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.team_model_add"
) as mock_team_model_add,
patch("litellm.proxy.management_endpoints.model_management_endpoints.update_team") as mock_update_team,
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.update_team"
) as mock_update_team, # test-quality-ok: the patched global is the collaborator's only injection seam
):
result = await _update_team_model_in_db(
db_model=db_model,
@ -1409,7 +1406,9 @@ class TestTeamModelUpdate:
)
with (
patch("litellm.proxy.management_endpoints.model_management_endpoints.team_model_delete") as mock_delete,
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.team_model_delete"
) as mock_delete, # test-quality-ok: the patched global is the collaborator's only injection seam
patch("litellm.proxy.management_endpoints.model_management_endpoints.team_model_add") as mock_add,
):
await _update_existing_team_model_assignment(
@ -1450,7 +1449,9 @@ class TestTeamModelUpdate:
)
with (
patch("litellm.proxy.management_endpoints.model_management_endpoints.team_model_delete") as mock_delete,
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.team_model_delete"
) as mock_delete, # test-quality-ok: the patched global is the collaborator's only injection seam
patch("litellm.proxy.management_endpoints.model_management_endpoints.team_model_add") as mock_add,
):
await _update_existing_team_model_assignment(
@ -1505,9 +1506,9 @@ class TestTeamModelUpdate:
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.allow_team_model_action",
AsyncMock(return_value=True),
),
patch(
patch( # test-quality-ok: team models are premium-gated through a proxy global with no injection seam
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: team models are premium-gated through a proxy global with no injection seam
),
patch( # test-quality-ok: the team list write is the collaborator whose ordering is asserted
"litellm.proxy.management_endpoints.model_management_endpoints.team_model_add",
side_effect=team_add,
@ -1567,7 +1568,9 @@ class TestTeamModelUpdate:
)
with (
patch("litellm.proxy.management_endpoints.model_management_endpoints.team_model_delete") as mock_delete,
patch(
"litellm.proxy.management_endpoints.model_management_endpoints.team_model_delete"
) as mock_delete, # test-quality-ok: the patched global is the collaborator's only injection seam
patch("litellm.proxy.management_endpoints.model_management_endpoints.team_model_add") as mock_add,
):
await _update_existing_team_model_assignment(
@ -3248,9 +3251,6 @@ class TestPatchModelBlockedAuthGate:
@pytest.mark.asyncio
async def test_team_admin_cannot_toggle_blocked(self):
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
non_admin = UserAPIKeyAuth(
user_id="team_admin",
@ -3290,9 +3290,6 @@ class TestPatchModelBlockedAuthGate:
@pytest.mark.asyncio
async def test_proxy_admin_can_toggle_blocked(self):
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
admin = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
existing_row = MagicMock()
@ -3340,9 +3337,6 @@ class TestPatchModelRowDeletedBeforeWrite:
@pytest.mark.asyncio
async def test_patch_model_404s_when_update_returns_none(self):
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
from litellm.proxy.proxy_server import ProxyException
admin = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
@ -3360,18 +3354,18 @@ class TestPatchModelRowDeletedBeforeWrite:
mock_prisma.db.litellm_proxymodeltable.update = AsyncMock(return_value=None)
with (
patch(
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.prisma_client", mock_prisma
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
patch(
),
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.llm_router", MagicMock(**{"get_model_ids.return_value": ["m1"]})
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
patch(
),
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
patch(
),
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
),
patch( # test-quality-ok: stubs the auth gate so the test exercises the not-found branch under test
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
new=AsyncMock(return_value=None),
@ -3438,7 +3432,6 @@ class TestWriteSurfacesReloadDrop:
def test_raise_if_reload_degraded_serving_contract(self, monkeypatch):
import litellm
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
raise_if_reload_degraded_serving,
)
@ -3484,7 +3477,6 @@ class TestWriteSurfacesReloadDrop:
all the desired set is unknown, so every drop is reported.
"""
import litellm
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
raise_if_reload_degraded_serving,
reload_serving_verdict,
@ -3630,7 +3622,6 @@ class TestConcurrentModelWritesDoNotEvictEachOther:
request's reload for it is the 500 that made concurrent model creates fail.
"""
import litellm
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
raise_if_reload_degraded_serving,
reload_serving_verdict,
@ -4121,7 +4112,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_patch_model_rejects_double_prefix(self):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
@ -4161,7 +4151,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_add_new_model_rejects_prefixed_model_without_config(self):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
add_new_model,
)
@ -4526,12 +4515,12 @@ class TestStrategyRouterWriteValidation:
self._live_router_holding_one_capability(limit, config_config) if config_config is not None else None
)
with (
patch(
patch( # test-quality-ok: the guard reads the proxy license singleton with no injection seam
"litellm.proxy.proxy_server._license_check.auto_router_capability_limit", lambda: limit
), # test-quality-ok: the guard reads the proxy license singleton with no injection seam
patch(
),
patch( # test-quality-ok: the guard reads the proxy router global with no injection seam
"litellm.proxy.proxy_server.llm_router", live_router
), # test-quality-ok: the guard reads the proxy router global with no injection seam
),
patch( # test-quality-ok: the cross-pod publish is the side effect under test; redis is not configured here
"litellm.proxy.management_endpoints.model_management_endpoints.publish_config_change",
new=AsyncMock(),
@ -4615,7 +4604,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_add_new_model_refuses_a_second_heuristic_v2_router_before_the_db_write(self) -> None:
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
add_new_model,
)
@ -4624,21 +4612,21 @@ class TestStrategyRouterWriteValidation:
fake = self._FakeDb(["auto_router/complexity_router"])
with (
patch(
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", fake
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.llm_router", None
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server._license_check.auto_router_capability_limit", lambda: 1
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
),
patch( # test-quality-ok: prior auth check needs a live DB; only the license limit is under test
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
new=AsyncMock(return_value=None),
@ -4666,7 +4654,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_model_less_patch_rejects_router_config_on_a_regular_model(self) -> None:
"""PATCH rejects the poison before its row write or the capability slot."""
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import patch_model
from litellm.types.router import updateLiteLLMParams
@ -4679,18 +4666,18 @@ class TestStrategyRouterWriteValidation:
)
fake = self._FakeDb([])
with (
patch(
patch( # test-quality-ok: endpoint reads proxy globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", fake
), # test-quality-ok: endpoint reads proxy globals with no injection seam
patch(
),
patch( # test-quality-ok: config-based lookup must be absent to drive the stored-row branch
"litellm.proxy.proxy_server.llm_router", None
), # test-quality-ok: config-based lookup must be absent to drive the stored-row branch
patch(
),
patch( # test-quality-ok: endpoint reaches its DB-write branch only with this process setting
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reaches its DB-write branch only with this process setting
patch(
),
patch( # test-quality-ok: authorization branch reads the proxy-wide premium flag
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: authorization branch reads the proxy-wide premium flag
),
patch( # test-quality-ok: inject stored regular row without a database
"litellm.proxy.management_endpoints.model_management_endpoints.get_db_model",
new=AsyncMock(return_value=regular),
@ -4718,7 +4705,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_model_less_legacy_update_rejects_router_config_on_a_regular_model(self) -> None:
"""The legacy update endpoint enforces the same boundary before its row write or slot."""
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import update_model
from litellm.types.router import ModelInfo, updateLiteLLMParams
@ -4734,18 +4720,18 @@ class TestStrategyRouterWriteValidation:
existing_row.litellm_params = regular.litellm_params.model_dump()
fake = self._FakeDb([], existing_row=existing_row)
with (
patch(
patch( # test-quality-ok: endpoint reads proxy globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", fake
), # test-quality-ok: endpoint reads proxy globals with no injection seam
patch(
),
patch( # test-quality-ok: config-based lookup must be absent to drive the stored-row branch
"litellm.proxy.proxy_server.llm_router", None
), # test-quality-ok: config-based lookup must be absent to drive the stored-row branch
patch(
),
patch( # test-quality-ok: endpoint reaches its DB-write branch only with this process setting
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reaches its DB-write branch only with this process setting
patch(
),
patch( # test-quality-ok: authorization branch reads the proxy-wide premium flag
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: authorization branch reads the proxy-wide premium flag
),
patch( # test-quality-ok: endpoint must reject before database authorization needs a live store
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
new=AsyncMock(return_value=None),
@ -4771,9 +4757,6 @@ class TestStrategyRouterWriteValidation:
"""patch_model relays HTTPException as-is, so the license refusal reaches the client as a plain 403."""
from fastapi import HTTPException
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
from litellm.types.router import updateLiteLLMParams
model_id = "other-id"
@ -4781,21 +4764,21 @@ class TestStrategyRouterWriteValidation:
fake = self._FakeDb(["auto_router/complexity_router"])
with (
patch(
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", fake
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.llm_router", None
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server._license_check.auto_router_capability_limit", lambda: 1
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
),
patch( # test-quality-ok: the write must be refused before this DB step runs
"litellm.proxy.management_endpoints.model_management_endpoints.get_db_model",
new=AsyncMock(return_value=self._db_complexity_router(model_id)),
@ -4821,7 +4804,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_update_model_refuses_switching_another_router_to_heuristic_v2(self) -> None:
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
update_model,
)
@ -4843,21 +4825,21 @@ class TestStrategyRouterWriteValidation:
fake = self._FakeDb(["auto_router/complexity_router"], existing_row=existing_row)
with (
patch(
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.prisma_client", fake
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.llm_router", None
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server.premium_user", True
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
patch(
),
patch( # test-quality-ok: endpoint reads proxy server globals with no injection seam
"litellm.proxy.proxy_server._license_check.auto_router_capability_limit", lambda: 1
), # test-quality-ok: endpoint reads proxy server globals with no injection seam
),
patch( # test-quality-ok: prior auth check needs a live DB; only the license limit is under test
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
new=AsyncMock(return_value=None),
@ -4877,7 +4859,6 @@ class TestStrategyRouterWriteValidation:
@pytest.mark.asyncio
async def test_update_model_rejects_prefix_strip(self):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
update_model,
)
@ -4980,7 +4961,6 @@ class TestAutoRouterClassifierDefaultPrompt:
@pytest.mark.asyncio
async def test_negative_context_window_size_is_rejected(self):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
get_auto_router_classifier_default_prompt,
)
@ -5124,7 +5104,6 @@ class TestAutoRouterClassifierDefaultPrompt:
],
)
def test_built_in_preview_rejects_the_same_invalid_labels_as_get(self, tier_labels):
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
AutoRouterClassifierPromptPreviewRequest,
preview_auto_router_classifier_prompt,
@ -5230,7 +5209,6 @@ class TestAutoRouterClassifierDefaultPrompt:
async def test_malformed_tier_labels_are_rejected_rather_than_silently_ignored(self):
"""An unparseable or invalid rename must not fall back to the canonical classification_rubric: that would
prefill tier names the router does not accept while looking like it worked."""
from litellm.proxy._types import ProxyException
from litellm.proxy.management_endpoints.model_management_endpoints import (
get_auto_router_classifier_default_prompt,
)
@ -5282,7 +5260,6 @@ class TestEnforceRpmTpmOnModelAdd:
],
)
def test_raises_when_enabled_and_missing(self, params, expected_missing):
from litellm.proxy._types import ProxyException
with pytest.raises(ProxyException) as exc_info:
_raise_if_rate_limits_required_but_missing(litellm_params=params, enforced=True)
@ -5322,19 +5299,19 @@ class TestBlockModelResponseSerialization:
app.dependency_overrides[ps.user_api_key_auth] = lambda: admin
try:
with (
patch(
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.prisma_client", mock_prisma
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
patch(
),
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.store_model_in_db", True
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
),
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.llm_router",
MagicMock(**{"get_model_ids.return_value": ["m-block-1"]}),
),
patch(
patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point
"litellm.proxy.proxy_server.redis_usage_cache", None
), # test-quality-ok: proxy_server module global is the endpoint's only injection point
),
patch( # test-quality-ok: stubs the cache write so the test observes only response serialization
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
new=AsyncMock(return_value=ReconcileOutcome(still_desired=None, live_after=None)),
@ -5424,7 +5401,6 @@ class TestAccessGroupModelSync:
@pytest.mark.asyncio
async def test_patch_model_rename_rewrites_the_groups_that_named_the_model(self):
from litellm.proxy.management_endpoints.model_management_endpoints import patch_model
mock_prisma = self._prisma_with_row("m-rename", "gpt-5.6", deployment_count=0)
router = MagicMock()
@ -5446,7 +5422,6 @@ class TestAccessGroupModelSync:
@pytest.mark.asyncio
async def test_patch_model_rename_appends_when_a_sibling_deployment_keeps_the_old_name(self):
from litellm.proxy.management_endpoints.model_management_endpoints import patch_model
mock_prisma = self._prisma_with_row("m-rename", "gpt-5.6", deployment_count=1)
router = MagicMock()
@ -5465,7 +5440,6 @@ class TestAccessGroupModelSync:
@pytest.mark.asyncio
async def test_patch_model_without_a_rename_leaves_access_groups_alone(self):
from litellm.proxy.management_endpoints.model_management_endpoints import patch_model
mock_prisma = self._prisma_with_row("m-same", "gpt-5.6", deployment_count=0)
router = MagicMock()
@ -5511,7 +5485,6 @@ class TestAccessGroupModelSync:
@pytest.mark.asyncio
async def test_update_model_persists_a_new_model_name_and_rewrites_the_groups(self):
from litellm.proxy.management_endpoints.model_management_endpoints import update_model
from litellm.types.router import ModelInfo, updateLiteLLMParams
mock_prisma = self._prisma_with_row("m-terraform", "gpt-5.6", deployment_count=0)

View file

@ -274,7 +274,9 @@ class TestVertexAIPassThroughHandler:
test_token = vertex_credentials
with (
mock.patch("litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth") as mock_load_auth,
mock.patch(
"litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth"
) as mock_load_auth, # test-quality-ok: the patched global is the collaborator's only injection seam
mock.patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
) as mock_create_route,
@ -375,7 +377,9 @@ class TestVertexAIPassThroughHandler:
test_token = vertex_credentials
with (
mock.patch("litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth") as mock_load_auth,
mock.patch(
"litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth"
) as mock_load_auth, # test-quality-ok: the patched global is the collaborator's only injection seam
mock.patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
) as mock_create_route,
@ -473,7 +477,9 @@ class TestVertexAIPassThroughHandler:
mock_response = Response()
with (
mock.patch("litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth") as mock_load_auth,
mock.patch(
"litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth"
) as mock_load_auth, # test-quality-ok: the patched global is the collaborator's only injection seam
mock.patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
) as mock_create_route,
@ -615,7 +621,9 @@ class TestVertexAIPassThroughHandler:
mock_request.method = "POST"
mock_response = Mock()
with patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.user_api_key_auth") as mock_auth:
with patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.user_api_key_auth"
) as mock_auth: # test-quality-ok: the patched global is the collaborator's only injection seam
mock_auth.return_value = {"api_key": "test-key-123"}
with patch(
@ -1196,7 +1204,9 @@ class TestVertexAIDiscoveryPassThroughHandler:
test_token = "test-auth-token"
with (
mock.patch("litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth") as mock_load_auth,
mock.patch(
"litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth"
) as mock_load_auth, # test-quality-ok: the patched global is the collaborator's only injection seam
mock.patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
) as mock_create_route,
@ -1257,7 +1267,9 @@ class TestVertexAIDiscoveryPassThroughHandler:
mock_request.method = "POST"
mock_response = Mock()
with patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.user_api_key_auth") as mock_auth:
with patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.user_api_key_auth"
) as mock_auth: # test-quality-ok: the patched global is the collaborator's only injection seam
mock_auth.return_value = {"api_key": "test-key-123"}
with patch(
@ -1851,7 +1863,9 @@ class TestLLMPassthroughFactoryProxyRoute:
mock_user_api_key_dict = MagicMock()
with (
patch("litellm.utils.ProviderConfigManager.get_provider_model_info") as mock_get_provider,
patch(
"litellm.utils.ProviderConfigManager.get_provider_model_info"
) as mock_get_provider, # test-quality-ok: the patched global is the collaborator's only injection seam
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials"
) as mock_get_creds,
@ -1898,9 +1912,9 @@ class TestVLLMProxyRoute:
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_passthrough_request_using_router_model",
return_value=True,
)
@patch(
@patch( # test-quality-ok: patching litellm internal for unit test isolation
"litellm.proxy.proxy_server.llm_router"
) # test-quality-ok: patching litellm internal for unit test isolation
)
async def test_vllm_proxy_route_with_router_model(self, mock_llm_router, mock_is_router, mock_get_body):
mock_request = MagicMock(spec=Request)
mock_request.method = "POST"
@ -1961,9 +1975,9 @@ class TestGigachatProxyRoute:
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_passthrough_request_using_router_model",
return_value=True,
)
@patch(
@patch( # test-quality-ok: patching litellm internal for unit test isolation
"litellm.proxy.proxy_server.llm_router"
) # test-quality-ok: patching litellm internal for unit test isolation
)
async def test_gigachat_proxy_route_with_router_model(self, mock_llm_router, mock_is_router, mock_get_body):
mock_request = MagicMock(spec=Request)
mock_request.method = "POST"
@ -2498,7 +2512,9 @@ class TestForwardHeaders:
mock_httpx_response.aread = AsyncMock(return_value=b'{"result": "success"}')
with (
patch("litellm.utils.ProviderConfigManager.get_provider_model_info") as mock_get_provider,
patch(
"litellm.utils.ProviderConfigManager.get_provider_model_info"
) as mock_get_provider, # test-quality-ok: the patched global is the collaborator's only injection seam
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials"
) as mock_get_creds,
@ -2821,7 +2837,9 @@ class TestMilvusProxyRoute:
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_allowed_to_call_vector_store_endpoint"
),
patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints._safe_set_request_parsed_body"),
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints._safe_set_request_parsed_body"
), # test-quality-ok: the patched global is the collaborator's only injection seam
patch.object(litellm, "vector_store_index_registry") as mock_index_registry,
patch.object(litellm, "vector_store_registry") as mock_vector_registry,
):
@ -2876,7 +2894,9 @@ class TestMilvusProxyRoute:
patch.object(litellm, "vector_store_registry") as mock_vector_registry,
):
mock_provider_config = MagicMock()
mock_provider_config.get_auth_credentials.return_value = {"headers": {}}
mock_provider_config.get_auth_credentials.return_value = {
"headers": {}
} # test-quality-ok: the patched global is the collaborator's only injection seam
mock_provider_config.get_complete_url.return_value = None
mock_get_config.return_value = mock_provider_config
@ -2926,7 +2946,9 @@ class TestMilvusProxyRoute:
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_allowed_to_call_vector_store_endpoint"
),
patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints._safe_set_request_parsed_body"),
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints._safe_set_request_parsed_body"
), # test-quality-ok: the patched global is the collaborator's only injection seam
patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
) as mock_create_route,

View file

@ -8,7 +8,7 @@ import asyncio
import logging
import sys
from collections.abc import Mapping
from typing import Dict, List
from typing import Dict, Final, List
from unittest.mock import AsyncMock, MagicMock, patch
import pytest

View file

@ -269,7 +269,6 @@ const ClassificationMethodConfig: React.FC<ClassificationMethodConfigProps> = ({
const explicitlySupportedClassifierEfforts = effortOptionsByModel[classifierModel];
const handleClassifierTypeChange = (classifierType: ClassifierType) => {
onChange(transitionClassifierType(value, classifierType));
};

View file

@ -140,8 +140,7 @@ export interface StoredComplexityRouterConfig {
*/
const asNumber = (v: unknown): number | undefined => (typeof v === "number" ? v : undefined);
const asBoolean = (v: unknown): boolean | undefined => (typeof v === "boolean" ? v : undefined);
const asNonEmptyString = (v: unknown): string | undefined =>
typeof v === "string" && v.trim() !== "" ? v : undefined;
const asNonEmptyString = (v: unknown): string | undefined => (typeof v === "string" && v.trim() !== "" ? v : undefined);
export const hydrateComplexityRouterConfig = (
parsedConfig: StoredComplexityRouterConfig,
@ -173,9 +172,7 @@ export const hydrateComplexityRouterConfig = (
classifier_context_window_size: asNumber(parsedConfig.classifier_context_window_size),
classifier_context_budget_chars: asNumber(parsedConfig.classifier_context_budget_chars),
classifier_context_per_turn_chars: asNumber(parsedConfig.classifier_context_per_turn_chars),
classifier_context_include_assistant_turns: asBoolean(
parsedConfig.classifier_context_include_assistant_turns,
),
classifier_context_include_assistant_turns: asBoolean(parsedConfig.classifier_context_include_assistant_turns),
classifier_fallback:
parsedConfig.classifier_fallback === "default_model" || parsedConfig.classifier_fallback === "heuristic"
? parsedConfig.classifier_fallback