Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions app/database/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from app.logger import log_startup_warning
from utils.install_util import get_missing_requirements_message
from filelock import FileLock, Timeout
from comfy.cli_args import args
from comfy.cli_args import args, database_default_path

_DB_AVAILABLE = False
Session = None
Expand Down Expand Up @@ -57,19 +57,66 @@ def get_alembic_config():

config = Config(config_path)
config.set_main_option("script_location", scripts_path)
config.set_main_option("sqlalchemy.url", args.database_url)
config.set_main_option("sqlalchemy.url", get_database_url())

return config


def get_database_url():
if args.database_url is not None:
return args.database_url

import folder_paths

db_path = os.path.join(folder_paths.get_user_directory(), "comfyui.db")
return f"sqlite:///{db_path}"


def get_legacy_default_db_path():
return database_default_path


def get_db_path():
url = args.database_url
url = get_database_url()
if url.startswith("sqlite:///"):
return url.split("///")[1]
return url.split("///", 1)[1]
else:
raise ValueError(f"Unsupported database URL '{url}'.")


def copy_legacy_default_db(db_path):
if args.database_url is not None:
return

legacy_db_path = get_legacy_default_db_path()
if legacy_db_path is None:
return

if os.path.abspath(legacy_db_path) == os.path.abspath(db_path):
return

if os.path.exists(db_path) or not os.path.exists(legacy_db_path):
return

backup_path = legacy_db_path + ".bak"
if os.path.exists(backup_path):
return

os.replace(legacy_db_path, backup_path)
shutil.copy(backup_path, db_path)
logging.info(
f"Renamed legacy database '{legacy_db_path}' to '{backup_path}' and copied it to '{db_path}'"
)


def prepare_file_db_path(db_path):
db_dir = os.path.dirname(db_path)
if db_dir:
os.makedirs(db_dir, exist_ok=True)

copy_legacy_default_db(db_path)


_db_lock = None

def _acquire_file_lock(db_path):
Expand Down Expand Up @@ -97,7 +144,7 @@ def _is_memory_db(db_url):


def init_db():
db_url = args.database_url
db_url = get_database_url()
logging.debug(f"Database URL: {db_url}")

if _is_memory_db(db_url):
Expand Down Expand Up @@ -134,6 +181,7 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
def _init_file_db(db_url):
"""Initialize a file-backed SQLite database using Alembic migrations."""
db_path = get_db_path()
prepare_file_db_path(db_path)
db_exists = os.path.exists(db_path)

config = get_alembic_config()
Expand Down
2 changes: 1 addition & 1 deletion comfy/cli_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def is_valid_directory(path: str) -> str:
database_default_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "user", "comfyui.db")
)
parser.add_argument("--database-url", type=str, default=f"sqlite:///{database_default_path}", help="Specify the database URL, e.g. for an in-memory database you can use 'sqlite:///:memory:'.")
parser.add_argument("--database-url", type=str, default=None, help="Specify the database URL, e.g. for an in-memory database you can use 'sqlite:///:memory:'. Defaults to 'comfyui.db' in the effective user directory.")
parser.add_argument("--enable-assets", action="store_true", help="Enable the assets system (API routes, database synchronization, and background scanning).")
parser.add_argument("--enable-asset-hashing", action="store_true", help="Compute blake3 content hashes when scanning assets. Hashing enables future asset-portability features (deduplication, cross-machine model resolution) but adds startup cost and per-output cost on large models directories. Off by default; enable to opt in.")
parser.add_argument("--feature-flag", type=str, action='append', default=[], metavar="KEY[=VALUE]", help="Set a server feature flag. Use KEY=VALUE to set an explicit value, or bare KEY to set it to true. Can be specified multiple times. Boolean values (true/false) and numbers are auto-converted. Examples: --feature-flag show_signin_button=true or --feature-flag show_signin_button")
Expand Down
2 changes: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,8 @@ async def start_all():

if sys.version_info.major == 3 and sys.version_info.minor < 10:
logging.warning("WARNING: You are using a python version older than 3.10, please upgrade to a newer one. 3.12 and above is recommended.")
if sys.version_info.major == 3 and sys.version_info.minor == 10:
logging.warning("WARNING: Python 3.10 will be EOL on October 31 2026, please consider upgrading to a newer version.")

if args.disable_dynamic_vram:
logging.warning(
Expand Down
134 changes: 134 additions & 0 deletions tests-unit/app_test/database_path_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import os

from app.database import db
from comfy.cli_args import database_default_path


def test_default_database_url_uses_effective_user_directory(monkeypatch, tmp_path):
user_dir = tmp_path / "custom_user"
user_dir.mkdir()

monkeypatch.setattr(db.args, "database_url", None)
monkeypatch.setattr("folder_paths.get_user_directory", lambda: str(user_dir))

assert db.get_database_url() == f"sqlite:///{user_dir / 'comfyui.db'}"


def test_default_db_path_matches_legacy_default_without_custom_user_directory(monkeypatch):
import folder_paths

monkeypatch.setattr(db.args, "database_url", None)
monkeypatch.setattr(
"folder_paths.get_user_directory",
lambda: os.path.join(folder_paths.base_path, "user"),
)

assert os.path.abspath(db.get_db_path()) == database_default_path


def test_explicit_database_url_is_preserved(monkeypatch):
database_url = "sqlite:///:memory:"

monkeypatch.setattr(db.args, "database_url", database_url)

assert db.get_database_url() == database_url


def test_explicit_url_at_legacy_default_location_is_honoured(monkeypatch):
url = f"sqlite:///{database_default_path}"

monkeypatch.setattr(db.args, "database_url", url)

assert db.get_database_url() == url
assert db.get_db_path() == database_default_path


def test_explicit_database_url_skips_legacy_copy(monkeypatch, tmp_path):
legacy_db = tmp_path / "install" / "user" / "comfyui.db"
target_db = tmp_path / "target" / "comfyui.db"
legacy_db.parent.mkdir(parents=True)
target_db.parent.mkdir(parents=True)
legacy_db.write_bytes(b"legacy db")

monkeypatch.setattr(db.args, "database_url", f"sqlite:///{target_db}")
monkeypatch.setattr(db, "get_legacy_default_db_path", lambda: str(legacy_db))

db.copy_legacy_default_db(str(target_db))

assert not target_db.exists()


def test_legacy_default_database_is_copied_to_effective_user_directory(monkeypatch, tmp_path):
legacy_db = tmp_path / "install" / "user" / "comfyui.db"
user_dir = tmp_path / "custom_user"
legacy_db.parent.mkdir(parents=True)
user_dir.mkdir()
legacy_db.write_bytes(b"legacy db")

monkeypatch.setattr(db.args, "database_url", None)
monkeypatch.setattr("folder_paths.get_user_directory", lambda: str(user_dir))
monkeypatch.setattr(db, "get_legacy_default_db_path", lambda: str(legacy_db))

db.copy_legacy_default_db(str(user_dir / "comfyui.db"))

assert (user_dir / "comfyui.db").read_bytes() == b"legacy db"
assert not legacy_db.exists()
assert legacy_db.with_suffix(".db.bak").read_bytes() == b"legacy db"


def test_legacy_copy_skipped_when_backup_already_exists(monkeypatch, tmp_path):
legacy_db = tmp_path / "install" / "user" / "comfyui.db"
backup_db = tmp_path / "install" / "user" / "comfyui.db.bak"
user_db = tmp_path / "custom_user" / "comfyui.db"
legacy_db.parent.mkdir(parents=True)
user_db.parent.mkdir(parents=True)
legacy_db.write_bytes(b"live db")
backup_db.write_bytes(b"old backup")

monkeypatch.setattr(db.args, "database_url", None)
monkeypatch.setattr(db, "get_legacy_default_db_path", lambda: str(legacy_db))

db.copy_legacy_default_db(str(user_db))

assert not user_db.exists()
assert legacy_db.read_bytes() == b"live db"
assert backup_db.read_bytes() == b"old backup"


def test_legacy_default_database_does_not_overwrite_existing_effective_db(monkeypatch, tmp_path):
legacy_db = tmp_path / "install" / "user" / "comfyui.db"
user_db = tmp_path / "custom_user" / "comfyui.db"
legacy_db.parent.mkdir(parents=True)
user_db.parent.mkdir(parents=True)
legacy_db.write_bytes(b"legacy db")
user_db.write_bytes(b"user db")

monkeypatch.setattr(db.args, "database_url", None)
monkeypatch.setattr(db, "get_legacy_default_db_path", lambda: str(legacy_db))

db.copy_legacy_default_db(str(user_db))

assert user_db.read_bytes() == b"user db"
assert legacy_db.read_bytes() == b"legacy db"


def test_prepare_file_database_creates_parent_directory(monkeypatch, tmp_path):
db_path = tmp_path / "nested" / "comfyui.db"

monkeypatch.setattr(db.args, "database_url", None)
monkeypatch.setattr(db, "copy_legacy_default_db", lambda path: None)

db.prepare_file_db_path(str(db_path))

assert db_path.parent.is_dir()


def test_prepare_file_database_accepts_relative_database_path(monkeypatch, tmp_path):
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(db.args, "database_url", "sqlite:///relative.db")
monkeypatch.setattr(db, "copy_legacy_default_db", lambda path: None)

db.prepare_file_db_path("relative.db")

assert os.getcwd() == str(tmp_path)
assert not list(tmp_path.iterdir())
Loading