Merge pull request #22956 from BerriAI/litellm_key_null_duration
[Fix] Key Expiry Default Duration
This commit is contained in:
commit
8523bb6b48
@ -534,8 +534,8 @@ async def _common_key_generation_helper( # noqa: PLR0915
|
||||
upperbound_duration = duration_in_seconds(
|
||||
duration=upperbound_value
|
||||
)
|
||||
# Handle special case where duration is "-1" (never expires)
|
||||
if value == "-1":
|
||||
# Handle special case where duration is None or "-1" (never expires)
|
||||
if value is None or value == "-1":
|
||||
user_duration = float("inf") # Infinite duration
|
||||
else:
|
||||
user_duration = duration_in_seconds(duration=value)
|
||||
@ -1462,7 +1462,7 @@ async def prepare_key_update_data(
|
||||
|
||||
if "duration" in non_default_values:
|
||||
duration = non_default_values.pop("duration")
|
||||
if duration == "-1":
|
||||
if duration is None or duration == "-1":
|
||||
# Set expires to None to indicate the key never expires
|
||||
non_default_values["expires"] = None
|
||||
elif duration and (isinstance(duration, str)) and len(duration) > 0:
|
||||
@ -1786,7 +1786,7 @@ async def update_key_fn(
|
||||
- tpm_limit_type: Optional[str] - TPM rate limit type - "best_effort_throughput", "guaranteed_throughput", or "dynamic"
|
||||
- rpm_limit_type: Optional[str] - RPM rate limit type - "best_effort_throughput", "guaranteed_throughput", or "dynamic"
|
||||
- allowed_cache_controls: Optional[list] - List of allowed cache control values
|
||||
- duration: Optional[str] - Key validity duration ("30d", "1h", etc.) or "-1" to never expire
|
||||
- duration: Optional[str] - Key validity duration ("30d", "1h", etc.), null to never expire, or "-1" to never expire (deprecated, use null)
|
||||
- permissions: Optional[dict] - Key-specific permissions
|
||||
- send_invite_email: Optional[bool] - Send invite email to user_id
|
||||
- guardrails: Optional[List[str]] - List of active guardrails for the key
|
||||
|
||||
@ -1087,6 +1087,34 @@ async def test_prepare_key_update_data_duration_never_expires():
|
||||
assert result["expires"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_key_update_data_duration_none_never_expires():
|
||||
"""Test that duration=None sets expires to None (never expires)."""
|
||||
from litellm.proxy._types import UpdateKeyRequest
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
prepare_key_update_data,
|
||||
)
|
||||
|
||||
existing_key = LiteLLM_VerificationToken(
|
||||
token="test-token",
|
||||
key_alias="test-key",
|
||||
models=["gpt-3.5-turbo"],
|
||||
user_id="test-user",
|
||||
team_id=None,
|
||||
auto_rotate=False,
|
||||
rotation_interval=None,
|
||||
metadata={},
|
||||
)
|
||||
|
||||
update_request = UpdateKeyRequest(key="test-token", duration=None)
|
||||
|
||||
result = await prepare_key_update_data(
|
||||
data=update_request, existing_key_row=existing_key
|
||||
)
|
||||
|
||||
assert result["expires"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_team_id_used_in_service_account_request_requires_team_id():
|
||||
"""
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { Select, Tooltip, Divider, Switch } from "antd";
|
||||
import { Select, Tooltip, Divider, Switch, Checkbox } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { TextInput } from "@tremor/react";
|
||||
|
||||
@ -12,6 +12,8 @@ interface KeyLifecycleSettingsProps {
|
||||
rotationInterval: string;
|
||||
onRotationIntervalChange: (interval: string) => void;
|
||||
isCreateMode?: boolean; // If true, shows "leave empty to never expire" instead of "-1 to never expire"
|
||||
neverExpire?: boolean;
|
||||
onNeverExpireChange?: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
const KeyLifecycleSettings: React.FC<KeyLifecycleSettingsProps> = ({
|
||||
@ -21,6 +23,8 @@ const KeyLifecycleSettings: React.FC<KeyLifecycleSettingsProps> = ({
|
||||
rotationInterval,
|
||||
onRotationIntervalChange,
|
||||
isCreateMode = false,
|
||||
neverExpire = false,
|
||||
onNeverExpireChange,
|
||||
}) => {
|
||||
// Predefined intervals
|
||||
const predefinedIntervals = ["7d", "30d", "90d", "180d", "365d"];
|
||||
@ -67,21 +71,38 @@ const KeyLifecycleSettings: React.FC<KeyLifecycleSettingsProps> = ({
|
||||
<label className="text-sm font-medium text-gray-700 flex items-center space-x-1">
|
||||
<span>Expire Key</span>
|
||||
<Tooltip
|
||||
title={
|
||||
isCreateMode
|
||||
? "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to never expire."
|
||||
: "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Use -1 to never expire."
|
||||
}
|
||||
title="Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged."
|
||||
>
|
||||
<InfoCircleOutlined className="text-gray-400 cursor-help text-xs" />
|
||||
</Tooltip>
|
||||
{!isCreateMode && onNeverExpireChange && (
|
||||
<Checkbox
|
||||
checked={neverExpire}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
onNeverExpireChange(checked);
|
||||
if (checked) {
|
||||
setDurationValue("");
|
||||
if (form && typeof form.setFieldValue === "function") {
|
||||
form.setFieldValue("duration", "");
|
||||
} else if (form && typeof form.setFieldsValue === "function") {
|
||||
form.setFieldsValue({ duration: "" });
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="ml-2 text-sm font-normal text-gray-600"
|
||||
>
|
||||
Never Expire
|
||||
</Checkbox>
|
||||
)}
|
||||
</label>
|
||||
<TextInput
|
||||
name="duration"
|
||||
placeholder={isCreateMode ? "e.g., 30d or leave empty to never expire" : "e.g., 30d or -1 to never expire"}
|
||||
placeholder={isCreateMode ? "e.g., 30d or leave empty to never expire" : "e.g., 30d"}
|
||||
className="w-full"
|
||||
value={durationValue}
|
||||
onValueChange={handleDurationChange}
|
||||
disabled={!isCreateMode && neverExpire}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -98,6 +98,7 @@ export function KeyEditView({
|
||||
);
|
||||
const [autoRotationEnabled, setAutoRotationEnabled] = useState<boolean>(keyData.auto_rotate || false);
|
||||
const [rotationInterval, setRotationInterval] = useState<string>(keyData.rotation_interval || "");
|
||||
const [neverExpire, setNeverExpire] = useState<boolean>(!keyData.expires);
|
||||
const [isKeySaving, setIsKeySaving] = useState(false);
|
||||
const { data: projects } = useProjects();
|
||||
const { data: uiSettingsData } = useUISettings();
|
||||
@ -265,6 +266,10 @@ export function KeyEditView({
|
||||
}
|
||||
// If it's already an array (shouldn't happen, but handle it), keep as is
|
||||
|
||||
if (neverExpire) {
|
||||
values.duration = null;
|
||||
}
|
||||
|
||||
await onSubmit(values);
|
||||
} finally {
|
||||
setIsKeySaving(false);
|
||||
@ -660,6 +665,8 @@ export function KeyEditView({
|
||||
onAutoRotationChange={setAutoRotationEnabled}
|
||||
rotationInterval={rotationInterval}
|
||||
onRotationIntervalChange={setRotationInterval}
|
||||
neverExpire={neverExpire}
|
||||
onNeverExpireChange={setNeverExpire}
|
||||
/>
|
||||
<Form.Item name="duration" hidden initialValue="">
|
||||
<Input />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user