fix(proxy): exit when DATABASE_URL is set but the Prisma toolchain is missing (#44207)

With DATABASE_URL set and no way to run the Prisma CLI (not on PATH, not importable), run_server used to print a plain notice and keep booting. The server then crashed later inside the DB exception handler with an unrelated ModuleNotFoundError traceback, and the migration-only entrypoint (--skip_server_startup) exited 0 without migrating. It now exits 1 with a red one-line message naming the missing toolchain and how to install it. The no-DATABASE_URL path is unchanged.

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-02 10:32:04 -07:00 • committed by GitHub
parent 2b85808011
commit 2c9a971171
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 2 deletions

View file

@ -1447,9 +1447,11 @@ def run_server(
sys.exit(1)
else:
print(
"Unable to connect to DB. DATABASE_URL found in environment, but the prisma CLI is neither on "
"PATH nor importable as a package."
"\033[1;31mLiteLLM Proxy: a database URL is set but the prisma CLI is neither on PATH nor importable "
"as a package, so the database cannot be set up. Install it with `pip install 'litellm[extra_proxy]'` "
"or run a shipped LiteLLM image.\033[0m"
)
sys.exit(1)
pgbouncer_settings: Final = PgBouncerSettings()
upstream_database_url: Final = os.getenv("DATABASE_URL")
if pgbouncer_settings.enabled and upstream_database_url is not None:

View file

@ -2265,6 +2265,63 @@ class TestRunServerDbSetup:
assert "prisma CLI is neither on PATH" not in capsys.readouterr().out
mock_setup_database.assert_called_once_with(use_migrate=True, use_v2_resolver=True)
@pytest.mark.parametrize(
("database_url", "exits"),
(("postgresql://test:test@localhost:5432/test", True), (None, False)),
ids=("database-url-set", "no-database-url"),
)
@patch("atexit.register")
def test_startup_exits_when_the_prisma_toolchain_is_missing_only_if_a_database_is_configured(
self,
mock_atexit_register,
database_url,
exits,
tmp_path,
capsys,
):
"""A DATABASE_URL with no way to run the Prisma CLI is fatal; no DATABASE_URL needs no Prisma at all."""
from litellm_proxy_extras import prisma_toolchain
from litellm.proxy.proxy_cli import run_server
empty_bin = tmp_path / "emptybin"
empty_bin.mkdir()
real_find_spec = prisma_toolchain.importlib.util.find_spec
def hide_prisma(name, package=None):
return None if name == "prisma" else real_find_spec(name, package)
mock_proxy_module = MagicMock(
app=MagicMock(),
ProxyConfig=MagicMock(),
KeyManagementSettings=MagicMock(),
save_worker_config=MagicMock(),
)
clean_env = {k: v for k, v in os.environ.items() if k not in ("DATABASE_URL", "DIRECT_URL")}
clean_env["PATH"] = str(empty_bin)
if database_url is not None:
clean_env["DATABASE_URL"] = database_url
with (
patch.dict(os.environ, clean_env, clear=True),
patch.dict(
"sys.modules",
{"proxy_server": mock_proxy_module, "litellm.proxy.proxy_server": mock_proxy_module},
),
patch.object(prisma_toolchain.importlib.util, "find_spec", side_effect=hide_prisma),
pytest.raises(SystemExit) if exits else nullcontext() as exit_info,
):
run_server.main(["--local", "--skip_server_startup"], standalone_mode=False)
out = capsys.readouterr().out
if exits:
assert exit_info.value.code == 1
assert "a database URL is set but the prisma CLI is neither on PATH nor importable" in out
assert "pip install 'litellm[extra_proxy]'" in out
else:
assert "prisma CLI" not in out
assert "Setup complete" in out
@patch("subprocess.run")
@patch("atexit.register")
@patch("litellm.proxy.db.prisma_client.PrismaManager.setup_database")