This commit is contained in:
Sneha Khoreja 2026-08-27 19:11:09 -05:00 committed by GitHub
commit cb8939a4f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 64 additions and 4 deletions

View file

@ -9,6 +9,21 @@ from litellm.proxy._types import (
)
from litellm.secret_managers.main import str_to_bool
try:
import prisma # optional dependency, only installed when the proxy is generated against a DATABASE_URL
_prisma_available = True
except ImportError:
# Master-key-only deployments run without a DATABASE_URL and never install
# prisma. The classifiers below must not crash the auth-failure path in that
# mode, so they short-circuit to False when prisma is unavailable.
_prisma_available = False
# Assigned once so the ALL_CAPS name is not redefined across the try/except
# branches; the module-level ``import prisma`` above is what the classifiers
# below dereference (``prisma.errors.*``) when this flag is True.
PRISMA_AVAILABLE = _prisma_available
# Bounds the __cause__/__context__ walk in is_database_service_unavailable_error_in_chain.
# Real exception chains are a few links deep; the cap also makes the walk cycle-safe.
_MAX_EXCEPTION_CHAIN_DEPTH: Final = 20
@ -55,6 +70,11 @@ class PrismaDBExceptionHandler:
Reporting decisions want the opposite breadth; use
``is_database_infrastructure_error`` for those.
"""
if not PRISMA_AVAILABLE:
# No DATABASE_URL / prisma not installed: there is no DB layer to
# be unavailable, so this cannot be a DB-connectivity failure.
return False
import prisma.engine.errors
if isinstance(e, DB_CONNECTION_ERROR_TYPES):
@ -79,7 +99,10 @@ class PrismaDBExceptionHandler:
``RecordNotFoundError``, etc.) are excluded the DB IS reachable and
the request itself is what failed.
"""
import prisma
if not PRISMA_AVAILABLE:
# No DATABASE_URL / prisma not installed: there is no DB layer to
# be unavailable, so this cannot be a DB-connectivity failure.
return False
data_layer_errors: Final = (
prisma.errors.DataError,
@ -119,7 +142,8 @@ class PrismaDBExceptionHandler:
per-row data rejection has to additionally consult
``is_database_service_unavailable_error`` before acting on a True here.
"""
import prisma
if not PRISMA_AVAILABLE:
return False
return type(e) is prisma.errors.DataError
@ -132,7 +156,8 @@ class PrismaDBExceptionHandler:
Use this for reconnect logic data-layer errors like UniqueViolationError
mean the DB IS reachable, so reconnecting would be pointless.
"""
import prisma
if not PRISMA_AVAILABLE:
return False
if isinstance(e, DB_CONNECTION_ERROR_TYPES):
return True
@ -200,7 +225,10 @@ class PrismaDBExceptionHandler:
are already classified by type/keyword above, and data-layer ones
(the DB IS reachable) must stay 401.
"""
import prisma
if not PRISMA_AVAILABLE:
# No prisma engine in play, so no prisma-engine-internal error is
# possible.
return False
if isinstance(e, prisma.errors.PrismaError):
return False

View file

@ -428,6 +428,38 @@ def test_handle_db_exception_with_non_db_error():
PrismaDBExceptionHandler.handle_db_exception(regular_error)
# Regression test for https://github.com/BerriAI/litellm/issues/35457
#
# On a master-key-only proxy (no DATABASE_URL) the optional `prisma` dependency
# is never installed. These classifiers are reached on EVERY auth failure via
# `_user_api_key_auth_builder`, so an unconditional `import prisma` inside them
# raised ModuleNotFoundError and surfaced a 500 where a 401 was expected. With
# prisma unavailable they must instead return False (not a DB error) and not
# raise, so auth failures stay 401.
@pytest.mark.parametrize(
"classifier",
[
PrismaDBExceptionHandler.is_database_connection_error,
PrismaDBExceptionHandler.is_prisma_data_error,
PrismaDBExceptionHandler.is_database_transport_error,
PrismaDBExceptionHandler.is_prisma_engine_internal_error,
],
)
def test_classifiers_return_false_when_prisma_unavailable(monkeypatch, classifier):
"""
When prisma is not installed (master-key-only deployment), the classifiers
must return False without raising ModuleNotFoundError.
"""
monkeypatch.setattr(
"litellm.proxy.db.exception_handler.PRISMA_AVAILABLE", False
)
# A plain auth failure carries no prisma types; the classifier must handle
# it without touching the (absent) prisma module.
auth_error = Exception("Authentication Error, invalid api key")
assert classifier(auth_error) is False
def _permanent_prisma_faults():
"""Every prisma error class that is not a transient outage and not a
data-layer error, built by enumeration so the list cannot drift out of sync