mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
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:
parent
4d54324515
commit
fee80d6c5f
12 changed files with 390 additions and 326 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ class _ProviderChunkParsed:
|
|||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _ProviderChunkEarlyReturn:
|
||||
value: Any
|
||||
value: ModelResponseStream | None
|
||||
|
||||
|
||||
_ProviderChunkResult = Union[_ProviderChunkParsed, _ProviderChunkEarlyReturn]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 ''}",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"LIT001": {
|
||||
"limit": 23267
|
||||
"limit": 23265
|
||||
},
|
||||
"LIT002": {
|
||||
"limit": 27434
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue