From bffd6e32342f8ac5b6a6844b744c4ca7c3592258 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 10 Oct 2026 08:01:11 +0000 Subject: [PATCH 1/3] refactor: read typed credential fields directly in per-user Copilot auth - github_copilot_auth_mode reads credential.credential_values directly instead of getattr+isinstance+cast - _new_async_github_client uses plain attribute access for get_async_httpx_client - _per_user_oauth_configured and _all_per_user_credential_names iterate typed CredentialItem fields - list_user_connections builds login/connected_at maps in one shot instead of seeded mutable dicts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/llms/github_copilot/per_user_auth.py | 10 ++-------- .../proxy/credential_endpoints/endpoints.py | 18 ++++++------------ litellm/proxy/litellm_pre_call_utils.py | 14 +++----------- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/litellm/llms/github_copilot/per_user_auth.py b/litellm/llms/github_copilot/per_user_auth.py index 10e5e12dec3..3dc8c12fe06 100644 --- a/litellm/llms/github_copilot/per_user_auth.py +++ b/litellm/llms/github_copilot/per_user_auth.py @@ -328,7 +328,7 @@ def _new_async_github_client() -> _AsyncGitHubClient: return cast( # cast-ok: pins the untyped litellm client factory to the local Protocol Callable[[str], _AsyncGitHubClient], - getattr(http_handler, "get_async_httpx_client"), # noqa: B009 # untyped factory + http_handler.get_async_httpx_client, )(_LLM_PROVIDER) @@ -548,13 +548,7 @@ def github_copilot_auth_mode(litellm_credential_name: object, auth_type_value: o credential: Final = CredentialAccessor.find_credential(litellm_credential_name) if credential is not None: - values: Final[object] = getattr(credential, "credential_values", None) - return isinstance(values, Mapping) and ( - cast( # cast-ok: value is Mapping-checked or dict-shaped at runtime - Mapping[object, object], values - ).get(GITHUB_COPILOT_AUTH_TYPE_KEY) - == GITHUB_COPILOT_PER_USER_AUTH_TYPE - ) + return credential.credential_values.get(GITHUB_COPILOT_AUTH_TYPE_KEY) == GITHUB_COPILOT_PER_USER_AUTH_TYPE return auth_type_value == GITHUB_COPILOT_PER_USER_AUTH_TYPE if auth_type_value == GITHUB_COPILOT_PER_USER_AUTH_TYPE: raise BadRequestError( diff --git a/litellm/proxy/credential_endpoints/endpoints.py b/litellm/proxy/credential_endpoints/endpoints.py index 83af63bf24a..a1ddcc6b99e 100644 --- a/litellm/proxy/credential_endpoints/endpoints.py +++ b/litellm/proxy/credential_endpoints/endpoints.py @@ -4,7 +4,6 @@ CRUD endpoints for storing reusable credentials. import time from collections.abc import Mapping -from datetime import datetime from typing import ( Annotated, Final, @@ -540,17 +539,12 @@ async def list_user_connections( detail={"error": CommonProxyErrors.db_not_connected_error.value}, ) rows: Final = await list_user_provider_credentials(prisma_client, user_id) - github_logins: Final[dict[str, str]] = {} # mutable-ok: accumulates one entry per credential row - connected_at: Final[dict[str, str]] = {} # mutable-ok: accumulates one entry per credential row - for row in rows: - if row.provider != _GITHUB_COPILOT_PROVIDER: - continue - payload = decode_user_provider_credential(row.credential_b64) - if payload is not None: - github_logins[row.credential_name] = payload.github_login - updated = getattr(row, "updated_at", None) - if isinstance(updated, datetime): - connected_at[row.credential_name] = updated.isoformat() + copilot_rows: Final = tuple(row for row in rows if row.provider == _GITHUB_COPILOT_PROVIDER) + payloads: Final = { + row.credential_name: decode_user_provider_credential(row.credential_b64) for row in copilot_rows + } + github_logins: Final = {name: payload.github_login for name, payload in payloads.items() if payload is not None} + connected_at: Final = {row.credential_name: row.updated_at.isoformat() for row in copilot_rows} return UserProviderConnectionsResponse( connections=[ UserProviderConnection( diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 913310952cd..0df19066082 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -2718,11 +2718,7 @@ def _per_user_oauth_configured() -> bool: ) return any( - isinstance(values := getattr(credential, "credential_values", None), Mapping) - and cast("Mapping[object, object]", values).get( # cast-ok: credential_values is a plain dict at runtime - GITHUB_COPILOT_AUTH_TYPE_KEY - ) - == GITHUB_COPILOT_PER_USER_AUTH_TYPE + credential.credential_values.get(GITHUB_COPILOT_AUTH_TYPE_KEY) == GITHUB_COPILOT_PER_USER_AUTH_TYPE for credential in (litellm.credential_list or ()) ) @@ -2739,12 +2735,8 @@ def _all_per_user_credential_names() -> tuple[str, ...]: return tuple( credential.credential_name for credential in (litellm.credential_list or ()) - if getattr(credential, "credential_name", None) - and isinstance(values := getattr(credential, "credential_values", None), Mapping) - and cast("Mapping[object, object]", values).get( # cast-ok: credential_values is a plain dict at runtime - GITHUB_COPILOT_AUTH_TYPE_KEY - ) - == GITHUB_COPILOT_PER_USER_AUTH_TYPE + if credential.credential_name + and credential.credential_values.get(GITHUB_COPILOT_AUTH_TYPE_KEY) == GITHUB_COPILOT_PER_USER_AUTH_TYPE ) From ae37be318dcc50b90c4d844076e6a5fe505e6c71 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 08:06:46 +0000 Subject: [PATCH 2/3] refactor: read typed fields directly in team member budget auth and pass-through route registration Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/_lazy_features.py | 11 +++++++---- litellm/proxy/auth/user_api_key_auth.py | 18 +++++++++++++++--- .../pass_through_endpoints.py | 9 +++------ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/litellm/proxy/_lazy_features.py b/litellm/proxy/_lazy_features.py index 1da42fae6b6..f4d80833dbb 100644 --- a/litellm/proxy/_lazy_features.py +++ b/litellm/proxy/_lazy_features.py @@ -384,7 +384,7 @@ def _lazy_slots(app: "FastAPI") -> Mapping[str, BaseRoute | None]: return app.state.lazy_slots if hasattr(app.state, "lazy_slots") else MappingProxyType({}) -def _lazy_routes(app: "FastAPI") -> Mapping[str, tuple[BaseRoute, ...]]: +def lazy_feature_routes(app: "FastAPI") -> Mapping[str, tuple[BaseRoute, ...]]: return app.state.lazy_routes if hasattr(app.state, "lazy_routes") else MappingProxyType({}) @@ -499,7 +499,7 @@ def _register_feature(app: "FastAPI", feat: LazyFeature, module: object, feature before: Final = len(app.router.routes) feat.register_fn(app, module) lazy_routes: Final[Mapping[str, tuple[BaseRoute, ...]]] = MappingProxyType( - {**_lazy_routes(app), feat.module_path: tuple(app.router.routes[before:])} + {**lazy_feature_routes(app), feat.module_path: tuple(app.router.routes[before:])} ) app.state.lazy_routes = lazy_routes # rebind-ok: the app owns the record of which routes each feature added app.router.routes[:] = _route_table( # rebind-ok: the app owns its route table @@ -565,7 +565,10 @@ def _register_all_on_startup(inner: "Lifespan[FastAPI]", features: tuple[LazyFea def _restore_registry_order(app: "FastAPI", features: tuple[LazyFeature, ...]) -> None: present: Final = frozenset(id(route) for route in app.router.routes) still_routed: Final = MappingProxyType( - {module_path: tuple(r for r in routes if id(r) in present) for module_path, routes in _lazy_routes(app).items()} + { + module_path: tuple(r for r in routes if id(r) in present) + for module_path, routes in lazy_feature_routes(app).items() + } ) app.router.routes[:] = _route_table( # rebind-ok: the app owns its route table app, app.router.routes, still_routed, features @@ -621,7 +624,7 @@ def lazy_owned_routes(app: "FastAPI") -> frozenset[int]: """ids of the routes lazy features have registered on this app. A route added later at one of their paths (a config pass-through at /v1/decisions) goes ahead of them, the precedence lazy mode gives it when the feature has not loaded by the time the config is read.""" - return frozenset(id(route) for route in chain.from_iterable(_lazy_routes(app).values())) + return frozenset(id(route) for route in chain.from_iterable(lazy_feature_routes(app).values())) def loaded_lazy_modules(app: "FastAPI") -> frozenset[str]: diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 2231706b282..399d1f4e676 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -3350,7 +3350,13 @@ async def _load_team_member_default_model_budget( skip_budget_checks: bool, proxy_logging_obj: ProxyLogging | None, ) -> None: - team_metadata_value: Final[object] = getattr(team_object, "metadata", None) if team_object is not None else None + team_metadata_value: Final[object] = ( + cast( # cast-ok: team metadata is a string-keyed JSON object + object, team_object.metadata + ) + if team_object is not None + else None + ) team_metadata: Final[Mapping[str, object] | None] = ( cast( # cast-ok: team metadata is a string-keyed JSON object Mapping[str, object], team_metadata_value @@ -3378,7 +3384,11 @@ async def _load_team_member_default_model_budget( ) member_budget_row: Final = membership.litellm_budget_table if membership is not None else None member_budget_value: Final[object] = ( - getattr(member_budget_row, "model_max_budget", None) if member_budget_row is not None else None + cast( # cast-ok: budget table stores a string-keyed JSON object + object, member_budget_row.model_max_budget + ) + if member_budget_row is not None + else None ) if ( membership is not None @@ -3406,7 +3416,9 @@ async def _load_team_member_default_model_budget( return default_budget_value: Final[object] = ( - getattr(team_member_default_budget, "model_max_budget", None) + cast( # cast-ok: budget table stores a string-keyed JSON object + object, team_member_default_budget.model_max_budget + ) if team_member_default_budget is not None else None ) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index d6358c638e7..51dccd1b4b3 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -73,7 +73,7 @@ from litellm.llms.base_llm.managed_resources.utils import ( from litellm.llms.custom_httpx.http_handler import get_async_httpx_client from litellm.llms.oss_decision import validate_oss_request from litellm.passthrough import BasePassthroughUtils -from litellm.proxy._lazy_features import lazy_owned_routes +from litellm.proxy._lazy_features import lazy_feature_routes, lazy_owned_routes from litellm.proxy._types import ( ConfigFieldInfo, ConfigFieldUpdate, @@ -3089,11 +3089,8 @@ class SafeRouteAdder: app.router.routes[:] = _placed_ahead( # rebind-ok: the app owns its route table app.router.routes, app.router.routes[-1], shadowed[0] ) - lazy_routes: Final[Mapping[str, tuple[BaseRoute, ...]] | None] = getattr( - getattr(app, "state", None), "lazy_routes", None - ) - if lazy_routes is not None and not builtin_pass_through_routes_first(): - builtin: Final = tuple(chain.from_iterable(lazy_routes.values())) + if not builtin_pass_through_routes_first(): + builtin: Final = tuple(chain.from_iterable(lazy_feature_routes(app).values())) if shadowed_by_builtin_claim(path, builtin): app.router.routes[:] = configured_pass_through_routes_first( # rebind-ok: the app owns its route table app.router.routes, builtin From 5814fe554d6efaaeae9f1da7b3b38e12fb228171 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 08:07:33 +0000 Subject: [PATCH 3/3] Revert "refactor: read typed fields directly in team member budget auth and pass-through route registration" This reverts commit ae37be318dcc50b90c4d844076e6a5fe505e6c71. --- litellm/proxy/_lazy_features.py | 11 ++++------- litellm/proxy/auth/user_api_key_auth.py | 18 +++--------------- .../pass_through_endpoints.py | 9 ++++++--- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/litellm/proxy/_lazy_features.py b/litellm/proxy/_lazy_features.py index f4d80833dbb..1da42fae6b6 100644 --- a/litellm/proxy/_lazy_features.py +++ b/litellm/proxy/_lazy_features.py @@ -384,7 +384,7 @@ def _lazy_slots(app: "FastAPI") -> Mapping[str, BaseRoute | None]: return app.state.lazy_slots if hasattr(app.state, "lazy_slots") else MappingProxyType({}) -def lazy_feature_routes(app: "FastAPI") -> Mapping[str, tuple[BaseRoute, ...]]: +def _lazy_routes(app: "FastAPI") -> Mapping[str, tuple[BaseRoute, ...]]: return app.state.lazy_routes if hasattr(app.state, "lazy_routes") else MappingProxyType({}) @@ -499,7 +499,7 @@ def _register_feature(app: "FastAPI", feat: LazyFeature, module: object, feature before: Final = len(app.router.routes) feat.register_fn(app, module) lazy_routes: Final[Mapping[str, tuple[BaseRoute, ...]]] = MappingProxyType( - {**lazy_feature_routes(app), feat.module_path: tuple(app.router.routes[before:])} + {**_lazy_routes(app), feat.module_path: tuple(app.router.routes[before:])} ) app.state.lazy_routes = lazy_routes # rebind-ok: the app owns the record of which routes each feature added app.router.routes[:] = _route_table( # rebind-ok: the app owns its route table @@ -565,10 +565,7 @@ def _register_all_on_startup(inner: "Lifespan[FastAPI]", features: tuple[LazyFea def _restore_registry_order(app: "FastAPI", features: tuple[LazyFeature, ...]) -> None: present: Final = frozenset(id(route) for route in app.router.routes) still_routed: Final = MappingProxyType( - { - module_path: tuple(r for r in routes if id(r) in present) - for module_path, routes in lazy_feature_routes(app).items() - } + {module_path: tuple(r for r in routes if id(r) in present) for module_path, routes in _lazy_routes(app).items()} ) app.router.routes[:] = _route_table( # rebind-ok: the app owns its route table app, app.router.routes, still_routed, features @@ -624,7 +621,7 @@ def lazy_owned_routes(app: "FastAPI") -> frozenset[int]: """ids of the routes lazy features have registered on this app. A route added later at one of their paths (a config pass-through at /v1/decisions) goes ahead of them, the precedence lazy mode gives it when the feature has not loaded by the time the config is read.""" - return frozenset(id(route) for route in chain.from_iterable(lazy_feature_routes(app).values())) + return frozenset(id(route) for route in chain.from_iterable(_lazy_routes(app).values())) def loaded_lazy_modules(app: "FastAPI") -> frozenset[str]: diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 399d1f4e676..2231706b282 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -3350,13 +3350,7 @@ async def _load_team_member_default_model_budget( skip_budget_checks: bool, proxy_logging_obj: ProxyLogging | None, ) -> None: - team_metadata_value: Final[object] = ( - cast( # cast-ok: team metadata is a string-keyed JSON object - object, team_object.metadata - ) - if team_object is not None - else None - ) + team_metadata_value: Final[object] = getattr(team_object, "metadata", None) if team_object is not None else None team_metadata: Final[Mapping[str, object] | None] = ( cast( # cast-ok: team metadata is a string-keyed JSON object Mapping[str, object], team_metadata_value @@ -3384,11 +3378,7 @@ async def _load_team_member_default_model_budget( ) member_budget_row: Final = membership.litellm_budget_table if membership is not None else None member_budget_value: Final[object] = ( - cast( # cast-ok: budget table stores a string-keyed JSON object - object, member_budget_row.model_max_budget - ) - if member_budget_row is not None - else None + getattr(member_budget_row, "model_max_budget", None) if member_budget_row is not None else None ) if ( membership is not None @@ -3416,9 +3406,7 @@ async def _load_team_member_default_model_budget( return default_budget_value: Final[object] = ( - cast( # cast-ok: budget table stores a string-keyed JSON object - object, team_member_default_budget.model_max_budget - ) + getattr(team_member_default_budget, "model_max_budget", None) if team_member_default_budget is not None else None ) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 51dccd1b4b3..d6358c638e7 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -73,7 +73,7 @@ from litellm.llms.base_llm.managed_resources.utils import ( from litellm.llms.custom_httpx.http_handler import get_async_httpx_client from litellm.llms.oss_decision import validate_oss_request from litellm.passthrough import BasePassthroughUtils -from litellm.proxy._lazy_features import lazy_feature_routes, lazy_owned_routes +from litellm.proxy._lazy_features import lazy_owned_routes from litellm.proxy._types import ( ConfigFieldInfo, ConfigFieldUpdate, @@ -3089,8 +3089,11 @@ class SafeRouteAdder: app.router.routes[:] = _placed_ahead( # rebind-ok: the app owns its route table app.router.routes, app.router.routes[-1], shadowed[0] ) - if not builtin_pass_through_routes_first(): - builtin: Final = tuple(chain.from_iterable(lazy_feature_routes(app).values())) + lazy_routes: Final[Mapping[str, tuple[BaseRoute, ...]] | None] = getattr( + getattr(app, "state", None), "lazy_routes", None + ) + if lazy_routes is not None and not builtin_pass_through_routes_first(): + builtin: Final = tuple(chain.from_iterable(lazy_routes.values())) if shadowed_by_builtin_claim(path, builtin): app.router.routes[:] = configured_pass_through_routes_first( # rebind-ok: the app owns its route table app.router.routes, builtin