This commit is contained in:
Krrish Dholakia 2026-10-27 02:25:20 +00:00 • committed by GitHub
commit 311a03dffa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 43 additions and 7 deletions

View file

@ -54,6 +54,16 @@ class LiteLLMKeyType(str, enum.Enum):
DEFAULT = "default" # Uses default allowed routes
def _is_mapping(value: object) -> TypeIs[Mapping[object, object]]: # guard-ok: raw before-validator payload
return isinstance(value, Mapping)
def _reject_expires(values: object) -> object:
if _is_mapping(values) and "expires" in values:
raise ValueError('expires is not supported on key requests. Set duration instead, e.g. "3600s" or "30d"')
return values
class GenerateKeyRequest(KeyRequestBase):
tpm_limit: int | None = Field(default=None, ge=0)
rpm_limit: int | None = Field(default=None, ge=0)
@ -71,6 +81,11 @@ class GenerateKeyRequest(KeyRequestBase):
organization_id: str | None = None
project_id: str | None = None
@model_validator(mode="before")
@classmethod
def reject_expires(cls, values: object) -> object:
return _reject_expires(values)
@field_validator("team_id", "organization_id", "project_id", mode="before")
@classmethod
def treat_cleared_id_as_unset(cls, v: object) -> object:
@ -79,10 +94,6 @@ class GenerateKeyRequest(KeyRequestBase):
return v
def _is_mapping(value: object) -> TypeIs[Mapping[object, object]]: # guard-ok: raw before-validator payload
return isinstance(value, Mapping)
class UpdateKeyRequest(KeyRequestBase):
tpm_limit: int | None = Field(default=None, ge=0)
rpm_limit: int | None = Field(default=None, ge=0)
@ -103,6 +114,11 @@ class UpdateKeyRequest(KeyRequestBase):
description="Omit to retain the project, or send null to detach. Assigning a different project is not supported.",
)
@model_validator(mode="before")
@classmethod
def reject_expires(cls, values: object) -> object:
return _reject_expires(values)
@model_validator(mode="before")
@classmethod
def drop_blank_team_id(cls, values: object) -> object:

View file

@ -15,7 +15,7 @@ from fastapi.testclient import TestClient
from unittest.mock import AsyncMock, MagicMock, patch
from fastapi import HTTPException
from pydantic import JsonValue
from pydantic import JsonValue, ValidationError
import inspect
@ -108,6 +108,28 @@ from litellm.types.proxy.management_endpoints.key_management_endpoints import (
client = TestClient(app)
def test_key_request_models_reject_expires() -> None:
for request_model, request_values in (
(GenerateKeyRequest, {"expires": "2026-01-01T00:00:00Z"}),
(UpdateKeyRequest, {"key": "sk-x", "expires": "2026-01-01T00:00:00Z"}),
(RegenerateKeyRequest, {"expires": "2026-01-01T00:00:00Z"}),
(GenerateKeyRequest, {"expires": None}),
):
with pytest.raises(ValidationError) as exc_info:
request_model.model_validate(request_values)
assert "duration" in str(exc_info.value)
def test_generate_key_response_preserves_expires() -> None:
expires: Final = datetime(2026, 1, 1, tzinfo=timezone.utc)
assert GenerateKeyResponse(key="sk-x", expires=expires).expires == expires
def test_generate_key_request_accepts_duration_without_expires() -> None:
assert GenerateKeyRequest.model_validate({"duration": "1h"}).duration == "1h"
@pytest.mark.asyncio
async def test_list_keys():
mock_prisma_client = AsyncMock()
@ -23305,5 +23327,3 @@ def test_key_info_database_not_connected_sanitized_500(
finally:
proxy_server.app.dependency_overrides.pop(user_api_key_auth, None)