Skip to content

Commit 6b46143

Browse files
fix(storage): show human-readable error for uninitialized sqlite databases
1 parent 7d35653 commit 6b46143

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

optuna_dashboard/_storage_url.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import sys
4+
import optuna
35
import os.path
46
from pathlib import Path
57
import re
@@ -80,7 +82,35 @@ def guess_storage_from_url(storage_url: str) -> BaseStorage:
8082

8183

8284
def get_rdb_storage(storage_url: str) -> RDBStorage:
83-
return RDBStorage(storage_url, skip_compatibility_check=True, skip_table_creation=True)
85+
"""Create an RDBStorage instance for the dashboard (read-only viewer).
86+
87+
Raises SystemExit with a clear message if the database schema has not been
88+
initialized (i.e., no Optuna study has ever been run against this storage).
89+
"""
90+
try:
91+
return RDBStorage(
92+
storage_url,
93+
skip_compatibility_check=True,
94+
skip_table_creation=True,
95+
)
96+
except optuna.exceptions.StorageInternalError as e:
97+
# This typically happens when the database exists but has no Optuna schema
98+
# (e.g., a brand-new empty SQLite file). Provide a human-readable error
99+
# instead of a raw SQLAlchemy traceback.
100+
err_msg = str(e).lower()
101+
cause_msg = str(e.__cause__).lower() if e.__cause__ else ""
102+
if "no such table" in err_msg or "version_info" in err_msg or "no such table" in cause_msg or "version_info" in cause_msg:
103+
print(
104+
f"Error: The database at '{storage_url}' does not contain an Optuna schema.\n"
105+
"Please run an Optuna study first to initialize the database:\n\n"
106+
" import optuna\n"
107+
" study = optuna.create_study(storage='<your-storage-url>')\n"
108+
" study.optimize(objective, n_trials=1)\n\n"
109+
"Then re-run optuna-dashboard.",
110+
file=sys.stderr,
111+
)
112+
sys.exit(1)
113+
raise # Re-raise unexpected StorageInternalError variants unchanged
84114

85115

86116
def get_journal_file_storage(file_path: str) -> JournalStorage:

python_tests/test_storage_url.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

33
import tempfile
4+
import os
5+
import pytest
46
from unittest import TestCase
57
import warnings
68

@@ -9,6 +11,7 @@
911
from optuna.storages import JournalStorage
1012
from optuna.storages import RDBStorage
1113
from optuna_dashboard._storage_url import get_storage
14+
from optuna_dashboard._storage_url import get_rdb_storage
1215
import sqlalchemy.exc
1316

1417

@@ -65,3 +68,13 @@ def test_get_journal_file_storage_invalid(self) -> None:
6568
with tempfile.NamedTemporaryFile() as file:
6669
with self.assertRaises(FileNotFoundError):
6770
get_storage(f"sqlite:///{file.name}", storage_class="JournalFileStorage")
71+
72+
def test_get_rdb_storage_empty_db_exits_cleanly(self) -> None:
73+
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
74+
empty_db_path = f.name
75+
try:
76+
with pytest.raises(SystemExit) as exc_info:
77+
get_rdb_storage(f"sqlite:///{empty_db_path}")
78+
assert exc_info.value.code == 1
79+
finally:
80+
os.unlink(empty_db_path)

0 commit comments

Comments
 (0)