From 917b28588caf832028166a6fabd15ad6cac3a756 Mon Sep 17 00:00:00 2001 From: Ashwin Upadhyay Date: Sun, 17 May 2026 00:48:06 +0530 Subject: [PATCH] fix(proxy): degrade to flat groups when membership table isn't ready 8 existing tests broke because they pass a plain MagicMock as the prisma_client and the new awaited find_many trips on await. same can hit prod - the proxy can boot before litellm-proxy-extras finishes prisma migrate deploy, so the model is briefly missing from the prisma client. wrap find_many in a narrow try/except (AttributeError, TypeError), return {} on miss. auth path falls back to today's flat-group behavior instead of 500'ing. two regression tests so we don't forget. refs #28032 --- ...model_access_group_management_endpoints.py | 17 +++++++++++- .../auth/test_nested_access_groups_db.py | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py b/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py index 0c69e8f124d..af3bf4e03ae 100644 --- a/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py @@ -86,8 +86,23 @@ async def get_group_memberships_from_db( """ Build parent_group -> [child_groups] map from the membership table. Single query, in-memory bucketing - no N+1. + + Resilient by design: if the table isn't available (Prisma client predates + this migration, the proxy started before `prisma migrate deploy` finished, + or the membership Prisma model was stripped from a downstream build) we + return an empty map. The auth path then falls back to today's flat-group + semantics instead of 500-ing the whole request. """ - rows = await prisma_client.db.litellm_accessgroupmembership.find_many() + try: + rows = await prisma_client.db.litellm_accessgroupmembership.find_many() + except (AttributeError, TypeError) as e: + verbose_proxy_logger.debug( + "litellm_accessgroupmembership unavailable - " + "skipping nested group resolution: %s", + e, + ) + return {} + memberships: Dict[str, List[str]] = {} for row in rows: memberships.setdefault(row.parent_group, []).append(row.child_group) diff --git a/tests/test_litellm/proxy/auth/test_nested_access_groups_db.py b/tests/test_litellm/proxy/auth/test_nested_access_groups_db.py index 9864f4c68b4..36ff5ebea05 100644 --- a/tests/test_litellm/proxy/auth/test_nested_access_groups_db.py +++ b/tests/test_litellm/proxy/auth/test_nested_access_groups_db.py @@ -86,6 +86,33 @@ async def test_get_group_memberships_empty_table_returns_empty_dict(): assert await get_group_memberships_from_db(prisma_client=prisma) == {} +@pytest.mark.asyncio +async def test_get_group_memberships_returns_empty_when_table_unavailable(): + """ + Defensive fallback: if the prisma client predates the migration (or any + other reason find_many raises AttributeError/TypeError), we return {} so + auth-path callers fall back to today's flat-group behavior. + """ + # Plain MagicMock - prisma_client.db.litellm_accessgroupmembership.find_many() + # returns a non-awaitable MagicMock, raising TypeError on `await`. + plain = MagicMock() + assert await get_group_memberships_from_db(prisma_client=plain) == {} + + +@pytest.mark.asyncio +async def test_get_group_memberships_returns_empty_when_table_attribute_missing(): + """ + AttributeError on the table accessor (model stripped from a downstream + Prisma client build) also falls back to empty. + """ + + class NoMembershipTable: + class db: + pass + + assert await get_group_memberships_from_db(prisma_client=NoMembershipTable()) == {} + + # --------------------------------------------------------------------------- # upsert_group_memberships # ---------------------------------------------------------------------------