chore(typing): clear basedpyright Any errors in proxy management endpoints and http handlers

Replace Any type seams with real types across proxy_server.py, llm_http_handler.py,
proxy/utils.py, streaming_handler.py, key_management_endpoints.py, and
team_endpoints.py: Prisma repository return types, request/response payload
TypedDicts and Pydantic models, and precise callback/logging-object types
instead of Any.

Whole-tree basedpyright totals (severity=error): reportAny 22710 -> 22448,
reportExplicitAny 7283 -> 7074, all other rules unchanged or improved,
zero regressions.
This commit is contained in:
mateo-berri 2026-07-30 10:52:05 +00:00
parent 4d54324515
commit fee80d6c5f
No known key found for this signature in database
12 changed files with 390 additions and 326 deletions

View file

@ -1,9 +1,9 @@
{
"reportAny": {
"limit": 33171
"limit": 32379
},
"reportArgumentType": {
"limit": 2645
"limit": 2633
},
"reportAssignmentType": {
"limit": 329
@ -24,7 +24,7 @@
"limit": 42
},
"reportExplicitAny": {
"limit": 10228
"limit": 9602
},
"reportFunctionMemberAccess": {
"limit": 11
@ -54,10 +54,10 @@
"limit": 0
},
"reportMissingParameterType": {
"limit": 5869
"limit": 5857
},
"reportMissingTypeArgument": {
"limit": 15864
"limit": 15831
},
"reportMissingTypeStubs": {
"limit": 41
@ -99,19 +99,19 @@
"limit": 0
},
"reportUnknownArgumentType": {
"limit": 45522
"limit": 45456
},
"reportUnknownLambdaType": {
"limit": 113
},
"reportUnknownMemberType": {
"limit": 40479
"limit": 40416
},
"reportUnknownParameterType": {
"limit": 20341
"limit": 20296
},
"reportUnknownVariableType": {
"limit": 32052
"limit": 31976
},
"reportUnnecessaryCast": {
"limit": 177

View file

@ -105,7 +105,7 @@ class _ProviderChunkParsed:
@dataclass(frozen=True, slots=True)
class _ProviderChunkEarlyReturn:
value: Any
value: ModelResponseStream | None
_ProviderChunkResult = Union[_ProviderChunkParsed, _ProviderChunkEarlyReturn]

View file

@ -21,10 +21,10 @@ class AzureRealtimeHTTPConfig(BaseRealtimeHTTPConfig):
def validate_environment(
self,
headers: dict,
headers: dict[str, str],
model: str,
api_key: Optional[str] = None,
) -> dict:
) -> dict[str, str]:
return {
**headers,
"api-key": api_key or "",
@ -43,7 +43,7 @@ class AzureRealtimeHTTPConfig(BaseRealtimeHTTPConfig):
version = api_version or get_secret_str("AZURE_API_VERSION") or "2024-12-17"
return f"{base}/openai/realtime/transcription_sessions?api-version={version}"
def get_realtime_calls_headers(self, ephemeral_key: str) -> dict:
def get_realtime_calls_headers(self, ephemeral_key: str) -> dict[str, str]:
return {
"api-key": ephemeral_key,
}

View file

@ -69,10 +69,10 @@ class BaseRealtimeHTTPConfig(ABC):
@abstractmethod
def validate_environment(
self,
headers: dict,
headers: dict[str, str],
model: str,
api_key: Optional[str] = None,
) -> dict:
) -> dict[str, str]:
"""
Build and return the request headers for the client_secrets call.
@ -89,7 +89,7 @@ class BaseRealtimeHTTPConfig(ABC):
base = (api_base or "").rstrip("/")
return f"{base}/v1/realtime/calls"
def get_realtime_calls_headers(self, ephemeral_key: str) -> dict:
def get_realtime_calls_headers(self, ephemeral_key: str) -> dict[str, str]:
"""
Build headers for the realtime_calls POST.

File diff suppressed because it is too large Load diff

View file

@ -36,10 +36,10 @@ class OpenAIRealtimeHTTPConfig(BaseRealtimeHTTPConfig):
def validate_environment(
self,
headers: dict,
headers: dict[str, str],
model: str,
api_key: Optional[str] = None,
) -> dict:
) -> dict[str, str]:
return {
**headers,
"Authorization": f"Bearer {api_key or ''}",

View file

@ -20,7 +20,7 @@ import secrets
import traceback
from collections.abc import Mapping
from datetime import datetime, timedelta, timezone
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, cast
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, cast
import fastapi
import yaml
@ -127,6 +127,7 @@ from litellm.types.proxy.management_endpoints.key_management_endpoints import (
from litellm.types.router import Deployment
from litellm.types.utils import (
BudgetConfig,
CredentialItem,
PersonalUIKeyGenerationConfig,
TeamUIKeyGenerationConfig,
)
@ -489,8 +490,8 @@ _NON_ADMIN_SAFE_ALLOWED_ROUTES_PRESETS = frozenset({"llm_api_routes", "info_rout
def _validate_caller_can_change_key_ownership(
data: Optional[BaseModel],
existing_key_row: Any,
data: Union[UpdateKeyRequest, RegenerateKeyRequest] | None,
existing_key_row: LiteLLM_VerificationToken,
user_api_key_dict: UserAPIKeyAuth,
) -> None:
"""
@ -514,16 +515,16 @@ def _validate_caller_can_change_key_ownership(
# ``prepare_key_update_data`` and writes NULL to the token row —
# detaching the key from its user and bypassing the user-row
# role check on subsequent requests.
fields_set = getattr(data, "model_fields_set", None) or set()
fields_set = data.model_fields_set
if "user_id" not in fields_set:
return
incoming_user_id = getattr(data, "user_id", None)
incoming_user_id = data.user_id
if incoming_user_id is None or incoming_user_id == "":
raise HTTPException(
status_code=403,
detail="Non-admin users cannot remove the user_id from a key.",
)
existing_user_id = getattr(existing_key_row, "user_id", None)
existing_user_id = existing_key_row.user_id
if incoming_user_id != existing_user_id:
raise HTTPException(
status_code=403,
@ -889,14 +890,14 @@ async def _common_key_generation_helper(
)
new_budget = prisma_client.jsonify_object(budget_row.json(exclude_none=True))
_budget = await BudgetRepository(prisma_client).table.create(
_budget: LiteLLM_BudgetTable = await BudgetRepository(prisma_client).table.create(
data={
**new_budget, # type: ignore
"created_by": user_api_key_dict.user_id or litellm_proxy_admin_name,
"updated_by": user_api_key_dict.user_id or litellm_proxy_admin_name,
}
)
_budget_id = getattr(_budget, "budget_id", None)
_budget_id = _budget.budget_id
# ADD METADATA FIELDS
# Set Management Endpoint Metadata Fields
@ -1405,14 +1406,12 @@ async def _validate_caller_can_assign_key_org(
detail="Cannot assign a key to an organization without a user_id on the caller's token",
)
user_row = await UserRepository(prisma_client).table.find_unique(
user_row: LiteLLM_UserTable | None = await UserRepository(prisma_client).table.find_unique(
where={"user_id": user_api_key_dict.user_id},
include={"organization_memberships": True},
)
memberships = getattr(user_row, "organization_memberships", None) if user_row else None
member_org_ids = {
membership.organization_id for membership in (memberships or []) if membership.organization_id is not None
}
memberships = user_row.organization_memberships if user_row else None
member_org_ids = {membership.organization_id for membership in (memberships or [])}
if organization_id not in member_org_ids:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
@ -2112,7 +2111,7 @@ async def _process_single_key_update(
litellm_changed_by: Optional[str],
prisma_client: Optional[PrismaClient],
user_api_key_cache: UserApiKeyCache,
proxy_logging_obj: Any,
proxy_logging_obj: ProxyLogging,
llm_router: Optional[Router],
user_custom_key_update: Optional[Callable] = None,
existing_key_row: Optional[LiteLLM_VerificationToken] = None,
@ -2265,9 +2264,9 @@ async def _process_single_key_update(
async def _validate_mcp_servers_for_key_update(
data: "UpdateKeyRequest",
team_obj: Optional["LiteLLM_TeamTableCachedObj"],
existing_key_row: Any,
prisma_client: Any,
user_api_key_cache: Any,
existing_key_row: LiteLLM_VerificationToken,
prisma_client: PrismaClient | None,
user_api_key_cache: UserApiKeyCache,
is_proxy_admin: bool,
) -> Optional[ObjectPermissionDict]:
"""Validate MCP servers in object_permission against the effective team."""
@ -2302,14 +2301,20 @@ async def _validate_mcp_servers_for_key_update(
async def _validate_update_key_data(
data: UpdateKeyRequest,
existing_key_row: Any,
existing_key_row: LiteLLM_VerificationToken,
user_api_key_dict: UserAPIKeyAuth,
llm_router: Any,
llm_router: Router | None,
premium_user: bool,
prisma_client: Any,
user_api_key_cache: Any,
prisma_client: PrismaClient | None,
user_api_key_cache: UserApiKeyCache,
) -> None:
"""Validate permissions and constraints for key update."""
if prisma_client is None:
raise HTTPException(
status_code=500,
detail={"error": "Database not connected"},
)
# Reject NaN/±inf spend before it can reach the DB / spend counter.
validate_finite_spend(data.spend)
@ -2404,17 +2409,24 @@ async def _validate_update_key_data(
# admin authorization after the key was reassigned.
caller_is_creator = (
user_api_key_dict.user_id is not None
and getattr(existing_key_row, "created_by", None) == user_api_key_dict.user_id
and getattr(existing_key_row, "user_id", None) == user_api_key_dict.user_id
and existing_key_row.created_by == user_api_key_dict.user_id
and existing_key_row.user_id == user_api_key_dict.user_id
)
# Team keys: can_team_member_execute_key_management_endpoint (called above)
# already validated team membership + /key/update permission and would have
# raised if the caller lacked it. Reaching this point on a team key for a
# non-budget change means the caller was authorized — skip the redundant
# _check_key_admin_access that would otherwise require team/org admin status.
_key_is_team_key = getattr(existing_key_row, "team_id", None) is not None
_key_is_team_key = existing_key_row.team_id is not None
can_skip_admin_check = (caller_is_creator or _key_is_team_key) and not _is_budget_change
if (not _is_proxy_admin) and prisma_client is not None and not can_skip_admin_check:
if (not _is_proxy_admin) and not can_skip_admin_check:
if existing_key_row.token is None:
raise ProxyException(
message="Key not found.",
type=ProxyErrorTypes.not_found_error,
param="key",
code=status.HTTP_404_NOT_FOUND,
)
hashed_key = existing_key_row.token
await _check_key_admin_access(
user_api_key_dict=user_api_key_dict,
@ -2426,7 +2438,7 @@ async def _validate_update_key_data(
# Check team limits if key has a team_id (from request or existing key)
team_obj: Optional[LiteLLM_TeamTableCachedObj] = None
_team_id_to_check = data.team_id or getattr(existing_key_row, "team_id", None)
_team_id_to_check = data.team_id or existing_key_row.team_id
if _team_id_to_check is not None:
team_obj = await get_team_object(
team_id=_team_id_to_check,
@ -2456,7 +2468,7 @@ async def _validate_update_key_data(
)
# Validate key against project limits if project_id is being set
_project_id_to_check = getattr(data, "project_id", None) or getattr(existing_key_row, "project_id", None)
_project_id_to_check = getattr(data, "project_id", None) or existing_key_row.project_id
if _project_id_to_check is not None and (data.models is not None or data.max_budget is not None):
await _check_project_key_limits(
project_id=_project_id_to_check,
@ -3004,6 +3016,7 @@ async def bulk_update_team_keys(
},
)
existing_keys: Sequence[LiteLLM_VerificationToken]
if data.all_keys_in_team:
# "all" excludes blocked/expired — bulk refresh shouldn't revive a key an admin disabled.
# `blocked` is Boolean? with no default; `/key/generate` writes NULL. Prisma's `NOT`
@ -3027,7 +3040,7 @@ async def bulk_update_team_keys(
"error": f"Team {data.team_id} has more than {MAX_BATCH_SIZE} keys. Use `key_ids` to update in batches of {MAX_BATCH_SIZE}."
},
)
requested_tokens = [row.token for row in existing_keys]
requested_tokens = [row.token for row in existing_keys if row.token is not None]
else:
if data.key_ids is None or len(data.key_ids) == 0:
raise HTTPException(
@ -3416,7 +3429,9 @@ async def info_key_fn_v2(
# Resolve key_aliases to tokens so we never pass token=None (unbounded query)
tokens_to_query = list(data.keys) if data.keys else []
if data.key_aliases:
alias_rows = await VerificationTokenRepository(prisma_client).table.find_many(
alias_rows: Sequence[LiteLLM_VerificationToken] = await VerificationTokenRepository(
prisma_client
).table.find_many(
where={"key_alias": {"in": data.key_aliases}},
include={"litellm_budget_table": True},
)
@ -4175,9 +4190,9 @@ async def delete_key_aliases(
user_api_key_dict: UserAPIKeyAuth,
litellm_changed_by: Optional[str] = None,
) -> Tuple[Optional[Dict], List[LiteLLM_VerificationToken]]:
_keys_being_deleted = await VerificationTokenRepository(prisma_client).table.find_many(
where={"key_alias": {"in": key_aliases}}
)
_keys_being_deleted: Sequence[LiteLLM_VerificationToken] = await VerificationTokenRepository(
prisma_client
).table.find_many(where={"key_alias": {"in": key_aliases}})
tokens = [key.token for key in _keys_being_deleted]
return await delete_verification_tokens(
@ -4242,7 +4257,7 @@ async def _rotate_master_key(
)
# 3. process config table
try:
config = await ConfigRepository(prisma_client).table.find_many()
config: Sequence[LiteLLM_Config] | None = await ConfigRepository(prisma_client).table.find_many()
except Exception:
config = None
@ -4307,7 +4322,7 @@ async def _rotate_master_key(
# 5. process credentials table
try:
credentials = await CredentialsRepository(prisma_client).table.find_many()
credentials: Sequence[CredentialItem] | None = await CredentialsRepository(prisma_client).table.find_many()
except Exception:
credentials = None
if credentials:
@ -4571,7 +4586,7 @@ async def _execute_virtual_key_regeneration(
grace_period=data.grace_period if data else None,
)
updated_token = await VerificationTokenRepository(prisma_client).table.update(
updated_token: LiteLLM_VerificationToken | None = await VerificationTokenRepository(prisma_client).table.update(
where={"token": hashed_api_key},
data=update_data, # type: ignore
)
@ -4772,7 +4787,9 @@ async def regenerate_key_fn(
else:
hashed_api_key = hash_token(key)
_key_in_db = await VerificationTokenRepository(prisma_client).table.find_unique(
_key_in_db: LiteLLM_VerificationToken | None = await VerificationTokenRepository(
prisma_client
).table.find_unique(
where={"token": hashed_api_key},
)
if _key_in_db is None:
@ -4976,7 +4993,9 @@ async def reset_key_spend_fn(
else:
hashed_api_key = hash_token(key)
_key_in_db = await VerificationTokenRepository(prisma_client).table.find_unique(
_key_in_db: LiteLLM_VerificationToken | None = await VerificationTokenRepository(
prisma_client
).table.find_unique(
where={"token": hashed_api_key},
include={"litellm_budget_table": True},
)
@ -4996,7 +5015,7 @@ async def reset_key_spend_fn(
user_api_key_cache=user_api_key_cache,
)
updated_key = await VerificationTokenRepository(prisma_client).table.update(
updated_key: LiteLLM_VerificationToken | None = await VerificationTokenRepository(prisma_client).table.update(
where={"token": hashed_api_key},
data={"spend": reset_to},
)
@ -5421,8 +5440,8 @@ async def list_keys(
async def _apply_non_admin_alias_scope(
user_api_key_dict: UserAPIKeyAuth,
prisma_client: Any,
query_params: List[Any],
prisma_client: PrismaClient,
query_params: List[str],
where_parts: List[str],
) -> None:
"""Append SQL scope conditions so non-admin users only see aliases for
@ -5493,7 +5512,7 @@ async def key_aliases(
# support column-level SELECT projection on find_many.
#
# $1 is always UI_SESSION_TOKEN_TEAM_ID (filters out UI session tokens).
query_params: List[Any] = [UI_SESSION_TOKEN_TEAM_ID]
query_params: List[str] = [UI_SESSION_TOKEN_TEAM_ID]
where_parts = [
"key_alias IS NOT NULL",
"key_alias != ''",
@ -5932,7 +5951,7 @@ def _get_condition_to_filter_out_ui_session_tokens() -> Dict[str, Any]:
async def _check_key_admin_access(
user_api_key_dict: UserAPIKeyAuth,
hashed_token: str,
prisma_client: Any,
prisma_client: PrismaClient,
user_api_key_cache: UserApiKeyCache,
route: str,
) -> None:
@ -5951,7 +5970,9 @@ async def _check_key_admin_access(
return
# Look up the target key to find its team
target_key_row = await VerificationTokenRepository(prisma_client).table.find_unique(where={"token": hashed_token})
target_key_row: LiteLLM_VerificationToken | None = await VerificationTokenRepository(
prisma_client
).table.find_unique(where={"token": hashed_token})
if target_key_row is None:
raise HTTPException(
status_code=404,
@ -6047,7 +6068,9 @@ async def block_key(
)
# Check if the key exists before trying to block it
existing_record = await VerificationTokenRepository(prisma_client).table.find_unique(where={"token": hashed_token})
existing_record: LiteLLM_VerificationToken | None = await VerificationTokenRepository(
prisma_client
).table.find_unique(where={"token": hashed_token})
if existing_record is None:
raise ProxyException(
message="Key not found.",
@ -6077,7 +6100,7 @@ async def block_key(
)
)
record = await VerificationTokenRepository(prisma_client).table.update(
record: LiteLLM_VerificationToken | None = await VerificationTokenRepository(prisma_client).table.update(
where={"token": hashed_token},
data={"blocked": True}, # type: ignore
)
@ -6158,7 +6181,9 @@ async def unblock_key(
)
# Check if the key exists before trying to unblock it
existing_record = await VerificationTokenRepository(prisma_client).table.find_unique(where={"token": hashed_token})
existing_record: LiteLLM_VerificationToken | None = await VerificationTokenRepository(
prisma_client
).table.find_unique(where={"token": hashed_token})
if existing_record is None:
raise ProxyException(
message="Key not found.",
@ -6188,7 +6213,7 @@ async def unblock_key(
)
)
record = await VerificationTokenRepository(prisma_client).table.update(
record: LiteLLM_VerificationToken | None = await VerificationTokenRepository(prisma_client).table.update(
where={"token": hashed_token},
data={"blocked": False}, # type: ignore
)
@ -6443,7 +6468,7 @@ def _validate_key_alias_format(key_alias: Optional[str]) -> None:
async def _enforce_unique_key_alias(
key_alias: Optional[str],
prisma_client: Any,
prisma_client: PrismaClient | None,
existing_key_token: Optional[str] = None,
) -> None:
"""
@ -6451,7 +6476,7 @@ async def _enforce_unique_key_alias(
Args:
key_alias (Optional[str]): The key alias to check
prisma_client (Any): Prisma client instance
prisma_client (Optional[PrismaClient]): Prisma client instance
existing_key_token (Optional[str]): ID of existing key being updated, to exclude from uniqueness check
(The Admin UI passes key_alias, in all Edit key requests. So we need to be sure that if we find a key with the same alias, it's not the same key we're updating)

View file

@ -14,7 +14,7 @@ import json
import math
import traceback
from datetime import datetime, timezone
from typing import Annotated, Any, Dict, List, Mapping, Optional, Tuple, Union, cast
from typing import Annotated, Dict, List, Mapping, Optional, Sequence, Tuple, TypedDict, Union, cast
import fastapi
from fastapi import APIRouter, Depends, Header, HTTPException, Request, status
@ -30,7 +30,10 @@ from litellm.proxy._types import (
BlockTeamRequest,
CommonProxyErrors,
DeleteTeamRequest,
KeyManagementRoutes,
LiteLLM_AccessGroupTable,
LiteLLM_AuditLogs,
LiteLLM_BudgetTable,
LiteLLM_DeletedTeamTable,
LiteLLM_ManagementEndpoint_MetadataFields,
LiteLLM_ManagementEndpoint_MetadataFields_Premium,
@ -78,6 +81,7 @@ from litellm.proxy.auth.auth_checks import (
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.common_utils.callback_utils import encrypt_callback_vars
from litellm.proxy.common_utils.json_merge_patch import apply_json_merge_patch
from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
from litellm.proxy.management_endpoints.common_utils import (
_check_passthrough_routes_caller_permission,
_is_user_org_admin_for_team,
@ -106,7 +110,7 @@ from litellm.proxy.management_helpers.utils import (
add_new_member,
management_endpoint_wrapper,
)
from litellm.proxy.utils import PrismaClient, handle_exception_on_proxy
from litellm.proxy.utils import PrismaClient, ProxyLogging, handle_exception_on_proxy
from litellm.repositories.budget_repository import BudgetRepository
from litellm.repositories.organization_repository import OrganizationRepository
from litellm.repositories.table_repositories import (
@ -141,6 +145,11 @@ from litellm.types.proxy.management_endpoints.team_endpoints import (
router = APIRouter()
class _TeamKeyCountRow(TypedDict):
team_id: str
_count: Mapping[str, int]
def _sanitize_for_log(value: object) -> str:
"""Strip CR/LF from user-controlled values to prevent log injection."""
try:
@ -151,9 +160,9 @@ def _sanitize_for_log(value: object) -> str:
async def _refresh_cached_team(
team_row: Any,
user_api_key_cache: Any,
proxy_logging_obj: Any,
team_row: LiteLLM_TeamTable,
user_api_key_cache: UserApiKeyCache,
proxy_logging_obj: ProxyLogging | None,
) -> None:
"""
Refresh the in-memory cached team object after a DB write.
@ -274,7 +283,7 @@ class TeamMemberBudgetHandler:
if team_member_budget_duration is not None:
budget_request.budget_duration = team_member_budget_duration
team_member_budget_table = await new_budget(
team_member_budget_table: LiteLLM_BudgetTable = await new_budget(
budget_obj=budget_request,
user_api_key_dict=user_api_key_dict,
)
@ -322,7 +331,7 @@ class TeamMemberBudgetHandler:
if team_member_budget_duration is not None:
budget_request.budget_duration = team_member_budget_duration
budget_row = await update_budget(
budget_row: LiteLLM_BudgetTable = await update_budget(
budget_obj=budget_request,
user_api_key_dict=user_api_key_dict,
)
@ -395,7 +404,7 @@ class TeamMemberBudgetHandler:
@staticmethod
async def backfill_team_member_budget_entries(
team_id: str,
members_with_roles: List[Union[Member, dict]],
members_with_roles: Sequence[Union[Member, dict]],
team_member_budget_id: str,
prisma_client: PrismaClient,
) -> None:
@ -460,7 +469,7 @@ class TeamMemberBudgetHandler:
)
def _get_default_team_param(field: str) -> Any:
def _get_default_team_param(field: str) -> object:
"""
Returns a default value for the given field from litellm.default_team_params config.
Returns None if no default is configured.
@ -790,7 +799,7 @@ async def _check_user_team_limits(
data: Union[NewTeamRequest, UpdateTeamRequest],
user_api_key_dict: UserAPIKeyAuth,
prisma_client: PrismaClient,
user_api_key_cache: Any,
user_api_key_cache: UserApiKeyCache,
) -> None:
"""
Enforce the caller's personal limits when CREATING a standalone team.
@ -1051,7 +1060,7 @@ async def new_team(
)
# Check if license is over limit
total_teams = await TeamRepository(prisma_client).table.count()
total_teams: int = await TeamRepository(prisma_client).table.count()
if total_teams and _license_check.is_team_count_over_limit(team_count=total_teams):
raise HTTPException(
status_code=403,
@ -1153,7 +1162,7 @@ async def new_team(
created_by=user_api_key_dict.user_id or litellm_proxy_admin_name,
updated_by=user_api_key_dict.user_id or litellm_proxy_admin_name,
)
model_dict = await ModelTableRepository(prisma_client).table.create(
model_dict: LiteLLM_ModelTable = await ModelTableRepository(prisma_client).table.create(
{**litellm_modeltable.json(exclude_none=True)} # type: ignore
) # type: ignore
@ -1357,11 +1366,11 @@ async def _create_team_update_audit_log(
async def _update_model_table(
data: UpdateTeamRequest,
model_id: Optional[str],
model_id: int | None,
prisma_client: PrismaClient,
user_api_key_dict: UserAPIKeyAuth,
litellm_proxy_admin_name: str,
) -> Optional[str]:
) -> int | None:
"""
Upsert model table and return the model id
"""
@ -1374,7 +1383,7 @@ async def _update_model_table(
updated_by=user_api_key_dict.user_id or litellm_proxy_admin_name,
)
if model_id is None:
model_dict = await ModelTableRepository(prisma_client).table.create(
model_dict: LiteLLM_ModelTable = await ModelTableRepository(prisma_client).table.create(
data={**litellm_modeltable.json(exclude_none=True)} # type: ignore
)
else:
@ -1394,7 +1403,7 @@ async def _update_model_table(
async def _auto_add_team_members_to_organization(
team: LiteLLM_TeamTable,
organization: LiteLLM_OrganizationTableWithMembers,
prisma_client: Any,
prisma_client: PrismaClient,
) -> None:
"""
When moving a team to an org, ensure all team members are also org members.
@ -1432,11 +1441,11 @@ async def _auto_add_team_members_to_organization(
async def fetch_and_validate_organization(
organization_id: str,
existing_team_row: Any,
existing_team_row: LiteLLM_TeamTable,
llm_router: Optional[Router],
prisma_client: Any,
prisma_client: PrismaClient,
user_api_key_dict: Optional[UserAPIKeyAuth] = None,
) -> Any:
) -> LiteLLM_OrganizationTable:
"""
Fetch and validate an organization for team update operations.
@ -1455,7 +1464,7 @@ async def fetch_and_validate_organization(
if llm_router is None:
raise HTTPException(status_code=500, detail={"error": CommonProxyErrors.no_llm_router.value})
organization_row = await OrganizationRepository(prisma_client).table.find_unique(
organization_row: LiteLLM_OrganizationTable | None = await OrganizationRepository(prisma_client).table.find_unique(
where={"organization_id": organization_id},
include={"litellm_budget_table": True, "members": True, "teams": True},
)
@ -2004,7 +2013,9 @@ async def patch_team(
patch_fields = data.model_dump(exclude_unset=True, exclude={"team_id"})
if "metadata" in patch_fields:
existing_team_row = await TeamRepository(prisma_client).table.find_unique(where={"team_id": team_id})
existing_team_row: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": team_id}
)
if existing_team_row is None:
raise HTTPException(
status_code=404,
@ -2259,7 +2270,7 @@ async def _process_team_members(
# Resolve allowed_models: explicit request value, or fall back to team's default_team_member_models
member_allowed_models = data.allowed_models
team_default_member_models = getattr(complete_team_data, "default_team_member_models", None)
team_default_member_models = complete_team_data.default_team_member_models
if member_allowed_models is None and team_default_member_models:
member_allowed_models = team_default_member_models
@ -2470,10 +2481,10 @@ async def _validate_and_populate_member_user_info(
)
# Get the single user
user_by_email = users_by_email[0]
matched_user: LiteLLM_UserTable = users_by_email[0]
# Verify the user_id matches
if user_by_email.user_id != member.user_id:
if matched_user.user_id != member.user_id:
raise HTTPException(
status_code=400,
detail={
@ -2486,7 +2497,7 @@ async def _validate_and_populate_member_user_info(
# Case 2: Only user_email provided - populate user_id from DB
if member.user_email is not None and member.user_id is None:
user_by_email = await UserRepository(prisma_client).table.find_first(
user_by_email: LiteLLM_UserTable | None = await UserRepository(prisma_client).table.find_first(
where={"user_email": {"equals": member.user_email, "mode": "insensitive"}}
)
@ -2515,7 +2526,9 @@ async def _validate_and_populate_member_user_info(
# Case 3: Only user_id provided - populate user_email from DB if user exists
if member.user_id is not None and member.user_email is None:
user_by_id = await UserRepository(prisma_client).table.find_unique(where={"user_id": member.user_id})
user_by_id: LiteLLM_UserTable | None = await UserRepository(prisma_client).table.find_unique(
where={"user_id": member.user_id}
)
if user_by_id is None:
# User doesn't exist yet - allow it to pass with user_email as None
@ -2706,7 +2719,9 @@ async def team_member_delete(
detail={"error": "Either user_id or user_email needs to be passed in"},
)
_existing_team_row = await TeamRepository(prisma_client).table.find_unique(where={"team_id": data.team_id})
_existing_team_row: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": data.team_id}
)
if _existing_team_row is None:
raise HTTPException(
@ -2834,7 +2849,7 @@ _MEMBER_BUDGET_PATCH_FIELDS = {
}
def _build_member_budget_patch(data: TeamMemberUpdateRequest) -> Dict[str, Any]:
def _build_member_budget_patch(data: TeamMemberUpdateRequest) -> Dict[str, object]:
"""Map the budget fields the request actually set (merge-patch: a sent
value updates, an explicit null clears, an absent field is left untouched)
to their budget-table columns."""
@ -2910,7 +2925,9 @@ async def team_member_update(
_validate_budget_duration(data.budget_duration)
_existing_team_row = await TeamRepository(prisma_client).table.find_unique(where={"team_id": data.team_id})
_existing_team_row: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": data.team_id}
)
if _existing_team_row is None:
raise HTTPException(
@ -3378,7 +3395,7 @@ def _transform_teams_to_deleted_records(
teams: List[LiteLLM_TeamTable],
user_api_key_dict: UserAPIKeyAuth,
litellm_changed_by: Optional[str] = None,
) -> List[Dict[str, Any]]:
) -> List[Dict[str, object]]:
"""Transform teams into deleted team records ready for persistence."""
if not teams:
return []
@ -3423,7 +3440,7 @@ def _transform_teams_to_deleted_records(
async def _save_deleted_team_records(
records: List[Dict[str, Any]],
records: List[Dict[str, object]],
prisma_client: PrismaClient,
) -> None:
"""Save deleted team records to the database."""
@ -3517,7 +3534,7 @@ async def _add_team_member_budget_table(
return team_info_response_object
async def _resolve_team_access_group_resources(_team_info: Any) -> None:
async def _resolve_team_access_group_resources(_team_info: TeamInfoResponseObjectTeamTable) -> None:
"""Populate access_group_models / mcp_server_ids / agent_ids on the team
info response by resolving inherited resources from its access groups."""
if not _team_info.access_group_ids:
@ -3818,7 +3835,9 @@ async def block_team(
if prisma_client is None:
raise Exception("No DB Connected.")
existing_team = await TeamRepository(prisma_client).table.find_unique(where={"team_id": data.team_id})
existing_team: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": data.team_id}
)
if existing_team is None:
raise HTTPException(
status_code=404,
@ -3867,7 +3886,9 @@ async def unblock_team(
if prisma_client is None:
raise Exception("No DB Connected.")
existing_team = await TeamRepository(prisma_client).table.find_unique(where={"team_id": data.team_id})
existing_team: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": data.team_id}
)
if existing_team is None:
raise HTTPException(
status_code=404,
@ -3933,9 +3954,9 @@ async def list_available_teams(
async def _get_org_admin_org_ids(
user_id: str,
prisma_client: Any,
user_api_key_cache: Any,
proxy_logging_obj: Any,
prisma_client: PrismaClient,
user_api_key_cache: UserApiKeyCache,
proxy_logging_obj: ProxyLogging | None,
) -> Optional[List[str]]:
"""
Return the list of organization IDs where the user is an org admin.
@ -3974,16 +3995,16 @@ async def _build_team_list_where_conditions(
use_deleted_table: bool,
search: Optional[str] = None,
org_admin_org_ids: Optional[List[str]] = None,
user_api_key_cache: Optional[Any] = None,
proxy_logging_obj: Optional[Any] = None,
) -> Optional[Dict[str, Any]]:
user_api_key_cache: UserApiKeyCache | None = None,
proxy_logging_obj: ProxyLogging | None = None,
) -> Dict[str, object] | None:
"""
Build where conditions for team list query.
Returns None when the query is guaranteed to yield no results (e.g. user
has no team memberships), allowing the caller to skip the DB round-trip.
"""
where_conditions: Dict[str, Any] = {}
where_conditions: Dict[str, object] = {}
if team_id:
where_conditions["team_id"] = team_id
@ -4065,7 +4086,7 @@ async def _batch_resolve_access_group_resources(
return {}
unique_ids = list(set(all_access_group_ids))
rows = await AccessGroupRepository(_prisma_client).table.find_many(
rows: Sequence[LiteLLM_AccessGroupTable] = await AccessGroupRepository(_prisma_client).table.find_many(
where={"access_group_id": {"in": unique_ids}},
)
@ -4113,7 +4134,7 @@ def _convert_teams_to_response_models(
async def _get_keys_count_by_team(
prisma_client: Any,
prisma_client: PrismaClient,
teams: list,
) -> Dict[str, int]:
"""Aggregate virtual-key counts per team for the given page of teams.
@ -4126,7 +4147,7 @@ async def _get_keys_count_by_team(
if not page_team_ids:
return {}
grouped = await VerificationTokenRepository(prisma_client).table.group_by(
grouped: Sequence[_TeamKeyCountRow] = await VerificationTokenRepository(prisma_client).table.group_by(
by=["team_id"],
where={"team_id": {"in": page_team_ids}},
count={"team_id": True},
@ -4138,9 +4159,9 @@ async def _enforce_list_team_v2_access(
user_api_key_dict: UserAPIKeyAuth,
user_id: Optional[str],
organization_id: Optional[str],
prisma_client: Any,
user_api_key_cache: Any,
proxy_logging_obj: Any,
prisma_client: PrismaClient,
user_api_key_cache: UserApiKeyCache,
proxy_logging_obj: ProxyLogging | None,
) -> Tuple[Optional[str], Optional[List[str]]]:
"""Enforce access control for list_team_v2.
@ -4339,7 +4360,7 @@ async def list_team_v2(
order=order_by if order_by else {"created_at": "desc"}, # Default sort
)
# Get total count for pagination
total_count = await DeletedTeamRepository(prisma_client).table.count(where=where_conditions)
total_count: int = await DeletedTeamRepository(prisma_client).table.count(where=where_conditions)
else:
teams = await TeamRepository(prisma_client).table.find_many(
where=where_conditions,
@ -4391,9 +4412,9 @@ async def list_team_v2(
async def _authorize_and_filter_teams(
user_api_key_dict: UserAPIKeyAuth,
user_id: Optional[str],
prisma_client: Any,
user_api_key_cache: Any,
proxy_logging_obj: Any,
prisma_client: PrismaClient,
user_api_key_cache: UserApiKeyCache,
proxy_logging_obj: ProxyLogging | None,
) -> list:
"""
Authorize the /team/list request and return filtered teams.
@ -4565,7 +4586,7 @@ async def get_paginated_teams(
# Calculate skip for pagination
skip = (page - 1) * page_size
# Get total count
total_count = await TeamRepository(prisma_client).table.count()
total_count: int = await TeamRepository(prisma_client).table.count()
# Get paginated teams
teams = await TeamRepository(prisma_client).table.find_many(
@ -4701,7 +4722,9 @@ async def team_model_add(
raise HTTPException(status_code=500, detail={"error": "No db connected"})
# Get existing team
team_row = await TeamRepository(prisma_client).table.find_unique(where={"team_id": data.team_id})
team_row: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": data.team_id}
)
if team_row is None:
raise HTTPException(
@ -4747,7 +4770,7 @@ async def team_model_add(
# the writer and lets Prisma bump updated_at.
# `include` mirrors the relations the auth path consumes off the cached
# team object so that `_refresh_cached_team` doesn't null them out.
updated_team = await TeamRepository(prisma_client).table.update(
updated_team: LiteLLM_TeamTable = await TeamRepository(prisma_client).table.update(
where={"team_id": data.team_id},
data={"updated_at": datetime.now(timezone.utc)},
include={"object_permission": True}, # type: ignore
@ -4801,7 +4824,9 @@ async def team_model_delete(
raise HTTPException(status_code=500, detail={"error": "No db connected"})
# Get existing team
team_row = await TeamRepository(prisma_client).table.find_unique(where={"team_id": data.team_id})
team_row: LiteLLM_TeamTable | None = await TeamRepository(prisma_client).table.find_unique(
where={"team_id": data.team_id}
)
if team_row is None:
raise HTTPException(
@ -4829,7 +4854,7 @@ async def team_model_delete(
updated_models = [m for m in current_models if m not in data.models]
# Update team. See team_model_add for the rationale on `include`.
updated_team = await TeamRepository(prisma_client).table.update(
updated_team: LiteLLM_TeamTable = await TeamRepository(prisma_client).table.update(
where={"team_id": data.team_id},
data={"models": updated_models},
include={"object_permission": True}, # type: ignore
@ -4964,7 +4989,7 @@ async def update_team_member_permissions(
},
)
# Update the team member permissions
updated_team = await TeamRepository(prisma_client).table.update(
updated_team: LiteLLM_TeamTable = await TeamRepository(prisma_client).table.update(
where={"team_id": data.team_id},
data={"team_member_permissions": data.team_member_permissions},
)
@ -5034,7 +5059,11 @@ async def bulk_update_team_member_permissions(
}
async def _compute_and_batch_updates(prisma_client, teams, permissions_to_add: set) -> int:
async def _compute_and_batch_updates(
prisma_client: PrismaClient,
teams: Sequence[LiteLLM_TeamTable],
permissions_to_add: set[KeyManagementRoutes],
) -> int:
"""Compute merged permissions and batch-write updates. Returns count of teams updated."""
updates = []
for team in teams:
@ -5056,9 +5085,11 @@ async def _compute_and_batch_updates(prisma_client, teams, permissions_to_add: s
return len(updates)
async def _append_permissions_to_specific_teams(prisma_client, team_ids: List[str], permissions_to_add: set) -> int:
async def _append_permissions_to_specific_teams(
prisma_client: PrismaClient, team_ids: List[str], permissions_to_add: set[KeyManagementRoutes]
) -> int:
"""Fetch specific teams by ID and append permissions."""
teams = await TeamRepository(prisma_client).table.find_many(
teams: Sequence[LiteLLM_TeamTable] = await TeamRepository(prisma_client).table.find_many(
where={"team_id": {"in": team_ids}},
)
@ -5073,10 +5104,12 @@ async def _append_permissions_to_specific_teams(prisma_client, team_ids: List[st
return await _compute_and_batch_updates(prisma_client, teams, permissions_to_add)
async def _append_permissions_to_all_teams(prisma_client, permissions_to_add: set) -> int:
async def _append_permissions_to_all_teams(
prisma_client: PrismaClient, permissions_to_add: set[KeyManagementRoutes]
) -> int:
"""Paginated read + batched write across all teams."""
teams_updated = 0
cursor = None
cursor: str | None = None
BATCH_SIZE = 500
while True:
@ -5088,7 +5121,7 @@ async def _append_permissions_to_all_teams(prisma_client, permissions_to_add: se
find_args["cursor"] = {"team_id": cursor}
find_args["skip"] = 1
teams = await TeamRepository(prisma_client).table.find_many(**find_args)
teams: Sequence[LiteLLM_TeamTable] = await TeamRepository(prisma_client).table.find_many(**find_args)
if not teams:
break
@ -5188,8 +5221,12 @@ async def get_team_daily_activity(
where_condition = {}
if team_ids_list:
where_condition["team_id"] = {"in": list(team_ids_list)}
team_aliases = await TeamRepository(prisma_client).table.find_many(where=where_condition)
team_alias_metadata = {t.team_id: {"team_alias": t.team_alias} for t in team_aliases}
team_aliases: Sequence[LiteLLM_TeamTable] = await TeamRepository(prisma_client).table.find_many(
where=where_condition
)
team_alias_metadata: Mapping[str, Dict[str, object]] = {
t.team_id: {"team_alias": t.team_alias} for t in team_aliases
}
# Check if user is team admin or has /team/daily/activity permission
# If not, filter by user's API keys.
@ -5219,9 +5256,9 @@ async def get_team_daily_activity(
# If user does not have full team view, filter by their API keys
if not has_full_team_view:
# Get all API keys for this user
user_keys = await VerificationTokenRepository(prisma_client).table.find_many(
where={"user_id": user_api_key_dict.user_id}
)
user_keys: Sequence[LiteLLM_VerificationToken] = await VerificationTokenRepository(
prisma_client
).table.find_many(where={"user_id": user_api_key_dict.user_id})
user_api_keys = [key.token for key in user_keys if key.token]
# If user has no API keys, return empty result
if not user_api_keys:

View file

@ -648,7 +648,7 @@ from fastapi.responses import (
RedirectResponse,
StreamingResponse,
)
from fastapi.routing import APIRouter
from fastapi.routing import APIRoute, APIRouter
from fastapi.security import OAuth2PasswordBearer
from fastapi.security.api_key import APIKeyHeader
from fastapi.staticfiles import StaticFiles
@ -839,21 +839,14 @@ async def _initialize_shared_aiohttp_session():
_build_aiohttp_keepalive_socket_factory,
)
connector_kwargs: Dict[str, Any] = {
"keepalive_timeout": AIOHTTP_KEEPALIVE_TIMEOUT,
"ttl_dns_cache": AIOHTTP_TTL_DNS_CACHE,
}
if AIOHTTP_NEEDS_CLEANUP_CLOSED:
connector_kwargs["enable_cleanup_closed"] = True
if AIOHTTP_CONNECTOR_LIMIT > 0:
connector_kwargs["limit"] = AIOHTTP_CONNECTOR_LIMIT
if AIOHTTP_CONNECTOR_LIMIT_PER_HOST > 0:
connector_kwargs["limit_per_host"] = AIOHTTP_CONNECTOR_LIMIT_PER_HOST
socket_factory = _build_aiohttp_keepalive_socket_factory()
if socket_factory is not None:
connector_kwargs["socket_factory"] = socket_factory
connector = TCPConnector(**connector_kwargs)
connector = TCPConnector(
keepalive_timeout=AIOHTTP_KEEPALIVE_TIMEOUT,
ttl_dns_cache=AIOHTTP_TTL_DNS_CACHE,
enable_cleanup_closed=AIOHTTP_NEEDS_CLEANUP_CLOSED,
limit=AIOHTTP_CONNECTOR_LIMIT if AIOHTTP_CONNECTOR_LIMIT > 0 else 100,
limit_per_host=AIOHTTP_CONNECTOR_LIMIT_PER_HOST if AIOHTTP_CONNECTOR_LIMIT_PER_HOST > 0 else 0,
socket_factory=_build_aiohttp_keepalive_socket_factory(),
)
session = ClientSession(connector=connector)
verbose_proxy_logger.info(
@ -1153,7 +1146,7 @@ async def proxy_startup_event(app: FastAPI):
await proxy_shutdown_event() # type: ignore[reportGeneralTypeIssues]
def _generate_stable_operation_id(route: Any) -> str:
def _generate_stable_operation_id(route: APIRoute) -> str:
operation_id = re.sub(r"\W", "_", f"{route.name}{route.path_format}")
route_methods = sorted(route.methods or [])
if len(route_methods) == 1:
@ -1195,7 +1188,9 @@ def ensure_unique_openapi_operation_ids(
) -> Dict[str, Any]:
operation_entries = []
operation_id_counts: Dict[str, int] = {}
for path_item in openapi_schema.get("paths", {}).values():
paths = openapi_schema.get("paths", {})
paths_dict = paths if isinstance(paths, dict) else {}
for path_item in paths_dict.values():
if not isinstance(path_item, dict):
continue
for method, operation in path_item.items():
@ -3518,11 +3513,11 @@ _DB_OVERLAY_REMOTE_MODULE_LIST_FIELDS: Dict[str, Tuple[str, ...]] = {
}
def _is_remote_module_url(value: Any) -> bool:
def _is_remote_module_url(value: object) -> bool:
return isinstance(value, str) and (value.startswith("s3://") or value.startswith("gcs://"))
def _scrub_guardrail_inner(inner: Dict[str, Any]) -> None:
def _scrub_guardrail_inner(inner: Dict[str, JsonValue]) -> None:
"""Strip remote-URL entries from a guardrail's ``callbacks`` list
and ``guardrail`` (v2 module-path) field. Mutates in place."""
cbs = inner.get("callbacks")
@ -3542,7 +3537,7 @@ def _scrub_guardrail_inner(inner: Dict[str, Any]) -> None:
inner["guardrail"] = None
def _scrub_db_overlay_remote_module_loads(section: str, db_value: Any) -> Any:
def _scrub_db_overlay_remote_module_loads(section: str, db_value: JsonValue) -> JsonValue:
"""Strip ``s3://`` / ``gcs://`` entries from the DB-overlay value for
fields whose contents reach ``get_instance_fn``. The same scheme is
allowed from a YAML config (the documented operator flow) but a
@ -5649,7 +5644,7 @@ class ProxyConfig:
)
@staticmethod
def _parse_router_settings_value(value: Any) -> Optional[dict]:
def _parse_router_settings_value(value: object) -> dict | None:
"""
Parse a router_settings value that may be a dict or a JSON/YAML string.
@ -8945,7 +8940,7 @@ async def model_info(
)
def _blocked_response_usage(original_response: Optional[Any]) -> "litellm.Usage":
def _blocked_response_usage(original_response: object | None) -> "litellm.Usage":
"""
Token usage for a synthetic guardrail-blocked response.
@ -11467,7 +11462,7 @@ def _enrich_model_info_with_litellm_data(
async def _get_caller_byok_team_scope(
user_api_key_dict: Optional[UserAPIKeyAuth],
prisma_client: Optional[Any],
prisma_client: PrismaClient | None,
) -> Optional[Set[str]]:
"""
Return the team IDs whose BYOK rows the caller is allowed to see via
@ -11524,8 +11519,8 @@ _SORTED_SEARCH_DB_FETCH_CAP = 500
async def _fetch_db_models_for_search(
prisma_client: Any,
proxy_config: Any,
prisma_client: PrismaClient,
proxy_config: ProxyConfig,
search_lower: str,
db_model_ids_in_router: Set[str],
router_models_count: int,
@ -11590,8 +11585,8 @@ async def _fetch_db_models_for_search(
async def _apply_search_filter_to_models(
all_models: List[Dict[str, Any]],
search: str,
prisma_client: Optional[Any],
proxy_config: Any,
prisma_client: PrismaClient | None,
proxy_config: ProxyConfig,
user_api_key_dict: Optional[UserAPIKeyAuth] = None,
page: int = 1,
size: int = 50,
@ -11696,7 +11691,7 @@ async def _apply_search_filter_to_models(
return filtered_router_models + db_models, search_total_count
def _normalize_datetime_for_sorting(dt: Any) -> Optional[datetime]:
def _normalize_datetime_for_sorting(dt: object) -> datetime | None:
"""
Normalize a datetime value to a timezone-aware UTC datetime for sorting.
@ -15095,7 +15090,7 @@ def _general_settings_ui_litellm_default(
return False if spec["type"] == "Boolean" else None
def _validate_general_settings_ui_litellm_value(field_name: str, value: Any) -> GeneralSettingsUILiteLLMValue:
def _validate_general_settings_ui_litellm_value(field_name: str, value: JsonValue) -> GeneralSettingsUILiteLLMValue:
spec = _GENERAL_SETTINGS_UI_LITELLM_FIELDS[field_name]
field_type = spec["type"]
if value is None or value == "":
@ -15135,7 +15130,7 @@ def _validate_general_settings_ui_litellm_value(field_name: str, value: Any) ->
async def _persist_general_settings_ui_litellm_field(
field_name: str, value: Any, user_api_key_dict: UserAPIKeyAuth
field_name: str, value: JsonValue, user_api_key_dict: UserAPIKeyAuth
) -> dict:
validated = _validate_general_settings_ui_litellm_value(field_name, value)
config = await proxy_config.get_config()

View file

@ -28,6 +28,7 @@ from typing import (
Optional,
Sequence,
Tuple,
TypeVar,
Union,
cast,
overload,
@ -178,7 +179,7 @@ if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.proxy.db.spend_log_tool_index import ToolUsageTransaction
Span = Union[_Span, Any]
Span = _Span
else:
Span = Any
@ -345,7 +346,7 @@ def _accepts_litellm_call_info(cb: CustomLogger) -> bool:
return _CALLBACK_ACCEPTS_CALL_INFO[key]
def _enrich_http_exception_with_guardrail_context(exc: BaseException, callback: Any) -> None:
def _enrich_http_exception_with_guardrail_context(exc: BaseException, callback: CustomLogger) -> None:
"""
If `exc` is an HTTPException with a dict `detail`, mutate it in place to
add `guardrail_name` and `guardrail_mode` taken from the callback instance.
@ -367,6 +368,9 @@ def _enrich_http_exception_with_guardrail_context(exc: BaseException, callback:
detail.setdefault("guardrail_mode", event_hook)
_GuardrailResultT = TypeVar("_GuardrailResultT")
def _exception_changes_request_flow(exc: BaseException) -> bool:
"""
True for guardrail exceptions the proxy turns into an alternate request flow
@ -394,11 +398,11 @@ class _CallbackCapabilities:
# Tuple[(resolved_callback, "override" | "apply_guardrail"), ...]
# Ordered the same as ``litellm.callbacks``; used to build the streaming
# iterator chain without re-scanning per request.
iterator_overrides: Tuple[Tuple[Any, str], ...] = field(default_factory=tuple)
iterator_overrides: Tuple[Tuple[CustomLogger, str], ...] = field(default_factory=tuple)
# Resolved CustomLogger callbacks in original order. Pre-resolving once
# avoids the per-request ``get_custom_logger_compatible_class`` walk for
# every string entry in ``litellm.callbacks``.
resolved_callbacks: Tuple[Any, ...] = field(default_factory=tuple)
resolved_callbacks: Tuple[CustomLogger, ...] = field(default_factory=tuple)
class ProxyLogging:
@ -676,7 +680,7 @@ class ProxyLogging:
return synthetic_data
def _convert_llm_result_to_mcp_response(self, llm_result, request_obj) -> Optional[Any]:
def _convert_llm_result_to_mcp_response(self, llm_result, request_obj) -> MCPPreCallResponseObject | None:
"""
Convert LLM guardrail result back to MCP response format.
"""
@ -802,7 +806,7 @@ class ProxyLogging:
verbose_proxy_logger.error(f"Error in manual argument parsing: {e}")
return None
def _convert_llm_result_to_mcp_during_response(self, llm_result, request_obj) -> Optional[Any]:
def _convert_llm_result_to_mcp_during_response(self, llm_result, request_obj) -> MCPDuringCallResponseObject | None:
"""
Convert LLM guardrail result back to MCP during call response format.
"""
@ -1438,9 +1442,7 @@ class ProxyLogging:
data = result
elif (
_callback is not None
and isinstance(_callback, CustomLogger)
and "async_pre_call_hook" in vars(_callback.__class__)
"async_pre_call_hook" in vars(_callback.__class__)
and _callback.__class__.async_pre_call_hook != CustomLogger.async_pre_call_hook
):
if call_type == "call_mcp_tool" and user_api_key_dict is None:
@ -1614,7 +1616,9 @@ class ProxyLogging:
break
@staticmethod
async def _run_guardrail_with_metrics(callback: Any, coro: Awaitable[Any], hook_type: str) -> Any:
async def _run_guardrail_with_metrics(
callback: CustomLogger, coro: Awaitable[_GuardrailResultT], hook_type: str
) -> _GuardrailResultT:
"""
Await `coro`, recording its latency and status to the
`litellm_guardrail_latency_seconds` metric under `hook_type`, and
@ -1646,7 +1650,7 @@ class ProxyLogging:
@staticmethod
async def _wrap_streaming_iterator_with_enrichment(
callback: Any, gen: AsyncGenerator[Any, None]
callback: CustomLogger, gen: AsyncGenerator[Any, None]
) -> AsyncGenerator[Any, None]:
"""
Yield from `gen`; if iteration raises an HTTPException with dict detail,
@ -1691,12 +1695,12 @@ class ProxyLogging:
has_streaming_chunk_override = False
has_guardrail = False
has_pre_call_override = False
iterator_overrides: List[Tuple[Any, str]] = [] # (callback, kind)
resolved_callbacks: List[Any] = []
iterator_overrides: List[Tuple[CustomLogger, str]] = [] # (callback, kind)
resolved_callbacks: List[CustomLogger] = []
for callback in callbacks:
if isinstance(callback, str):
resolved: Any = litellm.litellm_core_utils.litellm_logging.get_custom_logger_compatible_class(
resolved = litellm.litellm_core_utils.litellm_logging.get_custom_logger_compatible_class(
cast(_custom_logger_compatible_callbacks_literal, callback)
)
else:
@ -2795,7 +2799,7 @@ _DEPRECATED_KEY_CACHE_TTL_SECONDS = 60
async def _lookup_deprecated_key(
db: Any,
db: Union[PrismaWrapper, RoutingPrismaWrapper],
hashed_token: str,
) -> Optional[str]:
"""
@ -2875,7 +2879,7 @@ def _unpack_config_row(cached: Any) -> Optional[_ConfigRow]:
return None
async def get_config_param(prisma_client: Any, param_name: str) -> Optional[Any]:
async def get_config_param(prisma_client: "PrismaClient", param_name: str) -> Any | None:
"""Cached read of a LiteLLM_Config row; returns row, _ConfigRow shim, or None."""
cache_key = _config_cache_key(param_name)
cached = await litellm_config_cache.async_get_cache(cache_key)
@ -2883,7 +2887,7 @@ async def get_config_param(prisma_client: Any, param_name: str) -> Optional[Any]
return _unpack_config_row(cached)
row = await prisma_client.get_generic_data(key="param_name", value=param_name, table_name="config")
cache_value: Any = _pack_config_row(row) if row is not None else _CONFIG_CACHE_MISS
cache_value = _pack_config_row(row) if row is not None else _CONFIG_CACHE_MISS
await litellm_config_cache.async_set_cache(cache_key, cache_value, ttl=LITELLM_CONFIG_CACHE_TTL_SECONDS)
return row
@ -2893,7 +2897,7 @@ async def invalidate_config_param(param_name: str) -> None:
await litellm_config_cache.async_delete_cache(_config_cache_key(param_name))
async def prefetch_config_params(prisma_client: Any, param_names: List[str]) -> None:
async def prefetch_config_params(prisma_client: "PrismaClient", param_names: List[str]) -> None:
"""Batch-load LiteLLM_Config rows into the cache with one find_many."""
if not param_names:
return
@ -2910,7 +2914,7 @@ async def prefetch_config_params(prisma_client: Any, param_names: List[str]) ->
by_name = {row.param_name: row for row in rows}
for name in param_names:
row = by_name.get(name)
cache_value: Any = _pack_config_row(row) if row is not None else _CONFIG_CACHE_MISS
cache_value = _pack_config_row(row) if row is not None else _CONFIG_CACHE_MISS
await litellm_config_cache.async_set_cache(
_config_cache_key(name), cache_value, ttl=LITELLM_CONFIG_CACHE_TTL_SECONDS
)

View file

@ -1,6 +1,6 @@
{
"ANN001": {
"limit": 3118
"limit": 3106
},
"ANN002": {
"limit": 69
@ -24,7 +24,7 @@
"limit": 130
},
"ANN401": {
"limit": 2013
"limit": 1821
},
"ASYNC230": {
"limit": 14
@ -222,7 +222,7 @@
"limit": 38
},
"RET504": {
"limit": 716
"limit": 713
},
"RUF010": {
"limit": 874
@ -306,7 +306,7 @@
"limit": 9
},
"TID251": {
"limit": 2652
"limit": 2649
},
"TRY002": {
"limit": 547
@ -324,7 +324,7 @@
"limit": 883
},
"UP006": {
"limit": 12146
"limit": 12144
},
"UP007": {
"limit": 2526
@ -363,6 +363,6 @@
"limit": 105
},
"UP045": {
"limit": 17806
"limit": 17788
}
}

View file

@ -1,6 +1,6 @@
{
"LIT001": {
"limit": 23267
"limit": 23265
},
"LIT002": {
"limit": 27434