diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 1fb0133f50b..500a39d9455 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -2000,7 +2000,7 @@ async def _fetch_key_object_from_db_with_reconnect( proxy_logging_obj=proxy_logging_obj, ) except Exception as e: - if PrismaDBExceptionHandler.is_database_connection_error(e): + if PrismaDBExceptionHandler.is_database_transport_error(e): did_reconnect = False if hasattr(prisma_client, "attempt_db_reconnect"): auth_reconnect_timeout = getattr( diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py index bbc1564a487..cf24b516a5f 100644 --- a/litellm/proxy/db/exception_handler.py +++ b/litellm/proxy/db/exception_handler.py @@ -32,7 +32,28 @@ class PrismaDBExceptionHandler: @staticmethod def is_database_connection_error(e: Exception) -> bool: """ - Returns True if the exception is from a database outage / connection error + Returns True if the exception is from a database outage / connection error. + Any PrismaError qualifies — the DB failed to serve the request. + Used by allow_requests_on_db_unavailable logic. + """ + import prisma + + if isinstance(e, DB_CONNECTION_ERROR_TYPES): + return True + if isinstance(e, prisma.errors.PrismaError): + return True + if isinstance(e, ProxyException) and e.type == ProxyErrorTypes.no_db_connection: + return True + return False + + @staticmethod + def is_database_transport_error(e: Exception) -> bool: + """ + Returns True only for transport/connectivity failures where a reconnect + attempt makes sense (e.g. DB is unreachable, connection dropped). + + Use this for reconnect logic — data-layer errors like UniqueViolationError + mean the DB IS reachable, so reconnecting would be pointless. """ import prisma @@ -44,8 +65,6 @@ class PrismaDBExceptionHandler: return True if isinstance(e, prisma.errors.PrismaError): error_message = str(e).lower() - # Treat generic PrismaError as connection error only when its text - # clearly indicates transport/connectivity failure. connection_keywords = ( "can't reach database server", "cannot reach database server", diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 06297ec1b35..15f9b02c9e0 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -3578,8 +3578,8 @@ class PrismaClient: """ try: engine = self.db._original_prisma._engine # type: ignore[attr-defined] - if engine is not None and engine.process is not None: - return engine.process.pid + if engine is not None and engine.process is not None: # type: ignore[union-attr] + return engine.process.pid # type: ignore[union-attr] except (AttributeError, TypeError): pass @@ -4125,7 +4125,7 @@ class PrismaClient: except Exception as e: if isinstance( e, asyncio.TimeoutError - ) or PrismaDBExceptionHandler.is_database_connection_error(e): + ) or PrismaDBExceptionHandler.is_database_transport_error(e): await self.attempt_db_reconnect( reason="db_health_watchdog_connection_error", timeout_seconds=self._db_watchdog_reconnect_timeout_seconds, diff --git a/tests/test_litellm/proxy/db/test_exception_handler.py b/tests/test_litellm/proxy/db/test_exception_handler.py index 8c07b2a19e6..9dcf5df4aeb 100644 --- a/tests/test_litellm/proxy/db/test_exception_handler.py +++ b/tests/test_litellm/proxy/db/test_exception_handler.py @@ -72,8 +72,9 @@ def test_is_database_connection_error_prisma_connection_errors(prisma_error): ), ], ) -def test_is_database_connection_error_non_connection_prisma_errors(prisma_error): - assert PrismaDBExceptionHandler.is_database_connection_error(prisma_error) == False +def test_is_database_transport_error_non_connection_prisma_errors(prisma_error): + """Data-layer errors should not trigger reconnect — DB is reachable when these occur.""" + assert PrismaDBExceptionHandler.is_database_transport_error(prisma_error) == False def test_is_database_connection_generic_errors(): diff --git a/tests/test_litellm/proxy/db/test_prisma_self_heal.py b/tests/test_litellm/proxy/db/test_prisma_self_heal.py index 3a07a37ecea..be16d0bbb61 100644 --- a/tests/test_litellm/proxy/db/test_prisma_self_heal.py +++ b/tests/test_litellm/proxy/db/test_prisma_self_heal.py @@ -215,7 +215,7 @@ async def test_db_health_watchdog_should_trigger_reconnect_on_db_error(mock_prox "litellm.proxy.utils.asyncio.sleep", AsyncMock(side_effect=[None, asyncio.CancelledError()]), ), patch( - "litellm.proxy.db.exception_handler.PrismaDBExceptionHandler.is_database_connection_error", + "litellm.proxy.db.exception_handler.PrismaDBExceptionHandler.is_database_transport_error", return_value=True, ): await client._db_health_watchdog_loop()