From d879dcdcef6e1baebde3b1cd3b8834cb1b8da217 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 21 Jan 2026 17:19:32 +0530 Subject: [PATCH] Revert "Fix/prisma schema permission (#19391)" This reverts commit 75ee0d126c957fd226bba259ee62962cc31cce2c. --- .../litellm_proxy_extras/utils.py | 95 ++++----------- litellm/proxy/db/prisma_client.py | 38 +----- litellm/proxy/prisma_migration.py | 27 ++--- litellm/proxy/proxy_cli.py | 6 +- .../proxy/test_migration_failure_handling.py | 114 ------------------ 5 files changed, 42 insertions(+), 238 deletions(-) delete mode 100644 tests/test_litellm/proxy/test_migration_failure_handling.py diff --git a/litellm-proxy-extras/litellm_proxy_extras/utils.py b/litellm-proxy-extras/litellm_proxy_extras/utils.py index 1aed555c5a..7ffbe95be1 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/utils.py +++ b/litellm-proxy-extras/litellm_proxy_extras/utils.py @@ -18,15 +18,14 @@ def str_to_bool(value: Optional[str]) -> bool: return value.lower() in ("true", "1", "t", "y", "yes") + def _get_prisma_env() -> dict: """Get environment variables for Prisma, handling offline mode if configured.""" prisma_env = os.environ.copy() if str_to_bool(os.getenv("PRISMA_OFFLINE_MODE")): # These env vars prevent Prisma from attempting downloads prisma_env["NPM_CONFIG_PREFER_OFFLINE"] = "true" - prisma_env["NPM_CONFIG_CACHE"] = os.getenv( - "NPM_CONFIG_CACHE", "/app/.cache/npm" - ) + prisma_env["NPM_CONFIG_CACHE"] = os.getenv("NPM_CONFIG_CACHE", "/app/.cache/npm") return prisma_env @@ -35,28 +34,29 @@ def _get_prisma_command() -> str: if str_to_bool(os.getenv("PRISMA_OFFLINE_MODE")): # Primary location where Prisma Python package installs the CLI default_cli_path = "/app/.cache/prisma-python/binaries/node_modules/.bin/prisma" - + # Check if custom path is provided (for flexibility) custom_cli_path = os.getenv("PRISMA_CLI_PATH") if custom_cli_path and os.path.exists(custom_cli_path): logger.info(f"Using custom Prisma CLI at {custom_cli_path}") return custom_cli_path - + # Check the default location if os.path.exists(default_cli_path): logger.info(f"Using cached Prisma CLI at {default_cli_path}") return default_cli_path - + # If not found, log warning and fall back logger.warning( f"Prisma CLI not found at {default_cli_path}. " "Falling back to Python wrapper (may attempt downloads)" ) - + # Fall back to the Python wrapper (will work in online mode) return "prisma" + class ProxyExtrasDBManager: @staticmethod def _get_prisma_dir() -> str: @@ -119,7 +119,7 @@ class ProxyExtrasDBManager: stdout=open(migration_file, "w"), check=True, timeout=30, - env=prisma_env, + env=prisma_env ) # 3. Mark the migration as applied since it represents current state @@ -134,7 +134,7 @@ class ProxyExtrasDBManager: ], check=True, timeout=30, - env=prisma_env, + env=prisma_env ) return True @@ -159,20 +159,14 @@ class ProxyExtrasDBManager: @staticmethod def _roll_back_migration(migration_name: str): """Mark a specific migration as rolled back""" - # Set up environment for offline mode if configured + # Set up environment for offline mode if configured prisma_env = _get_prisma_env() subprocess.run( - [ - _get_prisma_command(), - "migrate", - "resolve", - "--rolled-back", - migration_name, - ], + [_get_prisma_command(), "migrate", "resolve", "--rolled-back", migration_name], timeout=60, check=True, capture_output=True, - env=prisma_env, + env=prisma_env ) @staticmethod @@ -184,7 +178,7 @@ class ProxyExtrasDBManager: timeout=60, check=True, capture_output=True, - env=prisma_env, + env=prisma_env ) @staticmethod @@ -254,7 +248,7 @@ class ProxyExtrasDBManager: if not database_url: logger.error("DATABASE_URL not set") return - + diff_dir = ( Path(migrations_dir) / "migrations" @@ -289,7 +283,7 @@ class ProxyExtrasDBManager: check=True, timeout=60, stdout=f, - env=_get_prisma_env(), + env=_get_prisma_env() ) except subprocess.CalledProcessError as e: logger.warning(f"Failed to generate migration diff: {e.stderr}") @@ -319,7 +313,7 @@ class ProxyExtrasDBManager: check=True, capture_output=True, text=True, - env=_get_prisma_env(), + env=_get_prisma_env() ) logger.info(f"prisma db execute stdout: {result.stdout}") logger.info("✅ Migration diff applied successfully") @@ -337,18 +331,12 @@ class ProxyExtrasDBManager: try: logger.info(f"Resolving migration: {migration_name}") subprocess.run( - [ - _get_prisma_command(), - "migrate", - "resolve", - "--applied", - migration_name, - ], + [_get_prisma_command(), "migrate", "resolve", "--applied", migration_name], timeout=60, check=True, capture_output=True, text=True, - env=_get_prisma_env(), + env=_get_prisma_env() ) logger.debug(f"Resolved migration: {migration_name}") except subprocess.CalledProcessError as e: @@ -387,7 +375,7 @@ class ProxyExtrasDBManager: check=True, capture_output=True, text=True, - env=_get_prisma_env(), + env=_get_prisma_env() ) logger.info(f"prisma migrate deploy stdout: {result.stdout}") @@ -425,7 +413,7 @@ class ProxyExtrasDBManager: check=True, capture_output=True, text=True, - env=_get_prisma_env(), + env=_get_prisma_env() ) logger.info( f"✅ Migration {failed_migration} marked as rolled back... retrying" @@ -521,43 +509,12 @@ class ProxyExtrasDBManager: raise else: # Use prisma db push with increased timeout - try: - subprocess.run( - [_get_prisma_command(), "db", "push", "--accept-data-loss"], - timeout=60, - check=True, - capture_output=True, # capture output to check for errors - text=True, - env=_get_prisma_env(), - ) - return True - except subprocess.CalledProcessError as e: - if ( - "Permission denied" in e.stderr - and "schema.prisma" in e.stderr - ): - logger.warning( - f"Permission denied during prisma generate: {e.stderr}. Retrying with --skip-generate..." - ) - # Retry with --skip-generate - subprocess.run( - [ - _get_prisma_command(), - "db", - "push", - "--accept-data-loss", - "--skip-generate", - ], - timeout=60, - check=True, - capture_output=True, - text=True, - env=_get_prisma_env(), - ) - logger.info("✅ prisma db push --skip-generate completed") - return True - else: - raise e + subprocess.run( + [_get_prisma_command(), "db", "push", "--accept-data-loss"], + timeout=60, + check=True, + ) + return True except subprocess.TimeoutExpired: logger.info(f"Attempt {attempt + 1} timed out") time.sleep(random.randrange(5, 15)) diff --git a/litellm/proxy/db/prisma_client.py b/litellm/proxy/db/prisma_client.py index 95800e9658..c9c0cfe8f6 100644 --- a/litellm/proxy/db/prisma_client.py +++ b/litellm/proxy/db/prisma_client.py @@ -386,39 +386,11 @@ class PrismaManager: return ProxyExtrasDBManager.setup_database(use_migrate=use_migrate) else: # Use prisma db push with increased timeout - try: - subprocess.run( - ["prisma", "db", "push", "--accept-data-loss"], - timeout=60, - check=True, - capture_output=True, - text=True, - ) - except subprocess.CalledProcessError as e: - if ( - "Permission denied" in e.stderr - and "schema.prisma" in e.stderr - ): - verbose_proxy_logger.warning( - f"Permission denied during prisma generate: {e.stderr}. Retrying with --skip-generate..." - ) - # Retry with --skip-generate - subprocess.run( - [ - "prisma", - "db", - "push", - "--accept-data-loss", - "--skip-generate", - ], - timeout=60, - check=True, - capture_output=True, - text=True, - ) - return True - else: - raise e + subprocess.run( + ["prisma", "db", "push", "--accept-data-loss"], + timeout=60, + check=True, + ) return True except subprocess.TimeoutExpired: verbose_proxy_logger.warning(f"Attempt {attempt + 1} timed out") diff --git a/litellm/proxy/prisma_migration.py b/litellm/proxy/prisma_migration.py index 2fe12b1439..251d1e5628 100644 --- a/litellm/proxy/prisma_migration.py +++ b/litellm/proxy/prisma_migration.py @@ -15,21 +15,14 @@ from litellm.proxy.proxy_cli import run_server # Call the Click command with standalone_mode=False run_server(["--skip_server_startup"], standalone_mode=False) -# Run prisma generate +# run prisma generate verbose_proxy_logger.info("Running 'prisma generate'...") -try: - result = subprocess.run(["prisma", "generate"], capture_output=True, text=True) - if result.returncode != 0: - if "Permission denied" in result.stderr: - verbose_proxy_logger.warning( - f"Permission denied during 'prisma generate'. Skipping generation, assuming client is pre-generated. Error: {result.stderr}" - ) - else: - verbose_proxy_logger.info( - f"'prisma generate' failed with exit code {result.returncode}." - ) - verbose_proxy_logger.error( - f"'prisma generate' stderr: {result.stderr}" - ) # Log stderr -except Exception as e: - verbose_proxy_logger.error(f"Error running prisma generate: {e}") +result = subprocess.run(["prisma", "generate"], capture_output=True, text=True) +verbose_proxy_logger.info(f"'prisma generate' stdout: {result.stdout}") # Log stdout +exit_code = result.returncode + +if exit_code != 0: + verbose_proxy_logger.info(f"'prisma generate' failed with exit code {exit_code}.") + verbose_proxy_logger.error( + f"'prisma generate' stderr: {result.stderr}" + ) # Log stderr diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 028aad9a2e..2059246674 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -797,11 +797,7 @@ def run_server( # noqa: PLR0915 ): check_prisma_schema_diff(db_url=None) else: - if not PrismaManager.setup_database( - use_migrate=not use_prisma_db_push - ): - print("LiteLLM: Database setup failed. Exiting...") # noqa - sys.exit(1) + PrismaManager.setup_database(use_migrate=not use_prisma_db_push) else: print( # noqa f"Unable to connect to DB. DATABASE_URL found in environment, but prisma package not found." # noqa diff --git a/tests/test_litellm/proxy/test_migration_failure_handling.py b/tests/test_litellm/proxy/test_migration_failure_handling.py deleted file mode 100644 index 426049007b..0000000000 --- a/tests/test_litellm/proxy/test_migration_failure_handling.py +++ /dev/null @@ -1,114 +0,0 @@ -import sys -import os -import subprocess -from unittest.mock import MagicMock, patch -from click.testing import CliRunner - -# Add parent directory to path to allow importing litellm -sys.path.insert(0, os.path.abspath("../../..")) - -from litellm.proxy.db.prisma_client import PrismaManager -from litellm.proxy.proxy_cli import run_server - - -class TestMigrationFailureHandling: - @patch("subprocess.run") - def test_prisma_client_permission_error_retry(self, mock_subprocess_run): - """ - Regression Test: Verifies that PrismaManager.setup_database - catches PermissionError during 'prisma db push' and retries with '--skip-generate'. - """ - # Mock behavior: - # call 1: raises CalledProcessError with "Permission denied" and "schema.prisma" - # call 2 (retry): succeeds - - error_output = "Error: Permission denied writing to ... schema.prisma" - - mock_process_error = subprocess.CalledProcessError( - returncode=1, cmd=["prisma", "db", "push"], stderr=error_output - ) - - mock_subprocess_run.side_effect = [ - mock_process_error, # 1st attempt fails with permission error - MagicMock(returncode=0), # 2nd attempt (retry) succeeds - ] - - # Ensure we run the 'db push' path (use_migrate=False) - # We also need to mock should_update_prisma_schema to return True - - with patch( - "litellm.proxy.db.prisma_client.should_update_prisma_schema", - return_value=True, - ): - # Run setup_database with use_migrate=False to trigger 'prisma db push' path - result = PrismaManager.setup_database(use_migrate=False) - - # Assert success - assert result is True - - # Verify calls - assert mock_subprocess_run.call_count == 2 - - # Check 1st call arguments (standard push) - args1, _ = mock_subprocess_run.call_args_list[0] - assert "push" in args1[0] - assert "--skip-generate" not in args1[0] - - # Check 2nd call arguments (retry with skip-generate) - args2, _ = mock_subprocess_run.call_args_list[1] - assert "push" in args2[0] - assert "--skip-generate" in args2[0] - - def test_proxy_cli_exit_on_migration_fail(self): - """ - Regression Test: Verifies that proxy_cli.run_server exits with NON-ZERO status - if PrismaManager.setup_database returns False. - """ - runner = CliRunner() - - # Mock setup_database to return False (Simulating failure) - # Mock should_update_prisma_schema to return True (Ensure we hit the DB setup logic) - with patch( - "litellm.proxy.db.prisma_client.PrismaManager.setup_database", - return_value=False, - ), patch( - "litellm.proxy.db.prisma_client.should_update_prisma_schema", - return_value=True, - ): - # Mock dependencies to prevent actual server startup and handle imports - mock_app = MagicMock() - mock_proxy_config = MagicMock() - - # Patch sys.modules to prevent ImportErrors for proxy_server - with patch.dict( - "sys.modules", - { - "proxy_server": MagicMock( - app=mock_app, ProxyConfig=mock_proxy_config - ) - }, - ): - with patch( - "litellm.proxy.proxy_cli.ProxyInitializationHelpers._get_default_unvicorn_init_args" - ) as mock_get_args: - mock_get_args.return_value = { - "app": "app", - "host": "localhost", - "port": 8000, - } - - # Set DATABASE_URL to trigger DB logic - with patch.dict( - os.environ, - {"DATABASE_URL": "postgresql://user:pass@localhost:5432/db"}, - ): - # Execute: Run server with --local and --skip_server_startup - result = runner.invoke( - run_server, ["--local", "--skip_server_startup"] - ) - - # Assert: Exit code should be non-zero (failure) - assert ( - result.exit_code != 0 - ), f"Expected non-zero exit code, got {result.exit_code}. Output: {result.output}" - assert "Database setup failed. Exiting..." in result.output