refactor(types): move user request models into litellm.types.proxy.management_endpoints.user_requests (#45813)

* refactor(types): move LitellmUserRoles and KeyManagementRoutes into litellm.types.proxy.auth.user_roles

Part of LIT-9423

* refactor(types): annotate GenerateRequestBase containers and check_max_budget

Part of LIT-9423

* refactor(types): move GenerateRequestBase and LiteLLM_ObjectPermissionBase into litellm.types.proxy.management_endpoints.request_base

Part of LIT-9423

* refactor(types): annotate user request metadata and check_user_info validators

Part of LIT-9423

* refactor(types): move user request models into litellm.types.proxy.management_endpoints.user_requests

Part of LIT-9423

* fix(types): restore main's _types.py content alongside the request_base move

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(sdk): resolve litellm.proxy lazily on attribute access

The layer cleanup stopped import litellm from loading any proxy module, so litellm.proxy was no longer bound and litellm.proxy._types.X raised AttributeError. The module __getattr__ now imports litellm.proxy on first access, which keeps import litellm free of proxy._types and fixes the docs and code-quality checks on main

* fix(sdk): resolve litellm.proxy lazily on attribute access

The layer cleanup stopped import litellm from loading any proxy module, so litellm.proxy was no longer bound and litellm.proxy._types.X raised AttributeError. The module __getattr__ now imports litellm.proxy on first access, which keeps import litellm free of proxy._types and fixes the docs and code-quality checks on main

* style(types): merge request_base re-exports into one import block

Part of LIT-9423

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
nate-berri 2026-10-10 13:58:11 -07:00 • committed by GitHub
parent c9d706adb5
commit 513e7a89ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 100 additions and 72 deletions

View file

@ -1684,41 +1684,12 @@ class ListSkillsRequest(LiteLLMPydanticObjectBase):
offset: int | None = 0
class NewUserRequestTeam(LiteLLMPydanticObjectBase):
team_id: str
max_budget_in_team: float | None = None
user_role: Literal["user", "admin"] = "user"
class NewUserRequest(GenerateRequestBase):
max_budget: float | None = None
user_email: str | None = None
user_alias: str | None = None
user_role: (
Literal[
LitellmUserRoles.PROXY_ADMIN,
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
LitellmUserRoles.INTERNAL_USER,
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
]
| None
) = None
teams: list[str] | list[NewUserRequestTeam] | None = None
auto_create_key: bool = True # flag used for returning a key as part of the /user/new response
send_invite_email: bool | None = None
sso_user_id: str | None = None
organizations: list[str] | None = None
password: str | None = None
@field_validator("password")
@classmethod
def password_not_supported(cls, value: str | None) -> str | None:
if value is not None:
raise ValueError(
"password cannot be set via /user/new. Users set their own password through an "
"invitation link (POST /invitation/new)."
)
return value
from litellm.types.proxy.management_endpoints.user_requests import ( # noqa: E402 # re-export after the definitions above
NewUserRequest as NewUserRequest, # noqa: PLC0414 # public re-export
)
from litellm.types.proxy.management_endpoints.user_requests import ( # noqa: E402 # re-export after the definitions above
NewUserRequestTeam as NewUserRequestTeam, # noqa: PLC0414 # public re-export
)
class NewUserResponse(GenerateKeyResponse):
@ -1740,36 +1711,12 @@ class NewUserResponse(GenerateKeyResponse):
updated_at: datetime | None = None
class UpdateUserRequestNoUserIDorEmail(GenerateRequestBase): # shared with BulkUpdateUserRequest
# repr=False keeps the plaintext out of management-endpoint alerts, which str() the request model
password: str | None = Field(default=None, repr=False)
spend: float | None = None
metadata: dict[object, object] | None = None
user_alias: str | None = None
user_role: (
Literal[
LitellmUserRoles.PROXY_ADMIN,
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
LitellmUserRoles.INTERNAL_USER,
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
]
| None
) = None
max_budget: float | None = None
class UpdateUserRequest(UpdateUserRequestNoUserIDorEmail):
# Note: the defaults of all Params here MUST BE NONE
# else they will get overwritten
user_id: str | None = None
user_email: str | None = None
@model_validator(mode="before")
@classmethod
def check_user_info(cls, values: Mapping[str, object]) -> Mapping[str, object]:
if values.get("user_id") is None and values.get("user_email") is None:
raise ValueError("Either user id or user email must be provided")
return values
from litellm.types.proxy.management_endpoints.user_requests import ( # noqa: E402 # re-export after the definitions above
UpdateUserRequest as UpdateUserRequest, # noqa: PLC0414 # public re-export
)
from litellm.types.proxy.management_endpoints.user_requests import ( # noqa: E402 # re-export after the definitions above
UpdateUserRequestNoUserIDorEmail as UpdateUserRequestNoUserIDorEmail, # noqa: PLC0414 # public re-export
)
class ChangePasswordRequest(LiteLLMPydanticObjectBase):
@ -3632,10 +3579,6 @@ class LiteLLM_UserTableFiltered(LiteLLMBaseModel): # done to avoid exposing sen
user_email: str | None = None
class LiteLLM_UserTableWithKeyCount(LiteLLM_UserTable):
key_count: int = 0
from litellm.models.access_group import ( # noqa: E402
LiteLLM_AccessGroupTable as LiteLLM_AccessGroupTable,
)
@ -3649,6 +3592,9 @@ from litellm.models.spend_logs import ( # noqa: E402
LiteLLM_SpendLogs as LiteLLM_SpendLogs,
)
from litellm.models.tag import LiteLLM_TagTable as LiteLLM_TagTable # noqa: E402
from litellm.types.proxy.management_endpoints.user_requests import ( # noqa: E402 # re-export after the definitions above
LiteLLM_UserTableWithKeyCount as LiteLLM_UserTableWithKeyCount, # noqa: PLC0414 # public re-export
)
AUDIT_ACTIONS = Literal["created", "updated", "deleted", "blocked", "unblocked", "rotated", "kill_switch_fired"]

View file

@ -4,14 +4,14 @@ from typing import Any, Final, Literal
from pydantic import ConfigDict, Field, field_validator
from typing_extensions import ReadOnly, TypedDict
from litellm.proxy._types import (
from litellm.types.llms.base import LiteLLMBaseModel
from litellm.types.proxy.management_endpoints.management_v1 import ResourceResponse
from litellm.types.proxy.management_endpoints.user_requests import (
LiteLLM_UserTableWithKeyCount,
NewUserRequest,
UpdateUserRequest,
UpdateUserRequestNoUserIDorEmail,
)
from litellm.types.llms.base import LiteLLMBaseModel
from litellm.types.proxy.management_endpoints.management_v1 import ResourceResponse
MAX_BULK_DELETE_USERS: Final = 500

View file

@ -0,0 +1,82 @@
from collections.abc import Mapping
from typing import Literal
from pydantic import Field, field_validator, model_validator
from litellm.models.user import LiteLLM_UserTable
from litellm.types.proxy.auth.user_roles import LitellmUserRoles
from litellm.types.proxy.management_endpoints.request_base import GenerateRequestBase
from litellm.types.utils import LiteLLMPydanticObjectBase
class NewUserRequestTeam(LiteLLMPydanticObjectBase):
team_id: str
max_budget_in_team: float | None = None
user_role: Literal["user", "admin"] = "user"
class NewUserRequest(GenerateRequestBase):
max_budget: float | None = None
user_email: str | None = None
user_alias: str | None = None
user_role: (
Literal[
LitellmUserRoles.PROXY_ADMIN,
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
LitellmUserRoles.INTERNAL_USER,
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
]
| None
) = None
teams: list[str] | list[NewUserRequestTeam] | None = None
auto_create_key: bool = True # flag used for returning a key as part of the /user/new response
send_invite_email: bool | None = None
sso_user_id: str | None = None
organizations: list[str] | None = None
password: str | None = None
@field_validator("password")
@classmethod
def password_not_supported(cls, value: str | None) -> str | None:
if value is not None:
raise ValueError(
"password cannot be set via /user/new. Users set their own password through an "
"invitation link (POST /invitation/new)."
)
return value
class UpdateUserRequestNoUserIDorEmail(GenerateRequestBase): # shared with BulkUpdateUserRequest
# repr=False keeps the plaintext out of management-endpoint alerts, which str() the request model
password: str | None = Field(default=None, repr=False)
spend: float | None = None
metadata: dict[object, object] | None = None
user_alias: str | None = None
user_role: (
Literal[
LitellmUserRoles.PROXY_ADMIN,
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
LitellmUserRoles.INTERNAL_USER,
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
]
| None
) = None
max_budget: float | None = None
class UpdateUserRequest(UpdateUserRequestNoUserIDorEmail):
# Note: the defaults of all Params here MUST BE NONE
# else they will get overwritten
user_id: str | None = None
user_email: str | None = None
@model_validator(mode="before")
@classmethod
def check_user_info(cls, values: Mapping[str, object]) -> Mapping[str, object]:
if values.get("user_id") is None and values.get("user_email") is None:
raise ValueError("Either user id or user email must be provided")
return values
class LiteLLM_UserTableWithKeyCount(LiteLLM_UserTable):
key_count: int = 0