fix: database path config option now works (#119) (#130)

* fix: resolve database path from config.json (#119)

PromptDatabase() was hardcoded to "prompts.db" relative to CWD,
ignoring the DEFAULT_DB_PATH setting from config.json. Added
_resolve_db_path() that reads from PromptManagerConfig when available
and resolves relative paths against the extension root directory.
This fixes Docker deployments where the CWD differs from the extension.

* test: add unit tests for database path resolution (#119)

Cover explicit absolute/relative paths, config fallback, relative
config paths resolved to extension root, and PromptDatabase constructor
integration with _resolve_db_path.

* docs: update database path configuration instructions (#119)

Replace outdated advice to edit py/config.py with correct config.json
approach. Add Docker-specific guidance for absolute paths.

* chore: bump version to 3.1.5

* fix: remove unused import and variables from test (#119)

Address code review: remove unused MagicMock import and unused db
local variables in constructor tests.
This commit is contained in:
Vito
2026-03-18 07:29:58 -07:00
committed by GitHub
parent 0e95dc3431
commit ad32a51497
5 changed files with 180 additions and 9 deletions
+17 -4
View File
@@ -657,11 +657,24 @@ ComfyUI_PromptManager/
### Database Settings
You can customize the database path by modifying the configuration:
You can customize the database path by creating a `config.json` file in the extension root:
```python
# In py/config.py
DATABASE_PATH = "custom_path/prompts.db"
```json
{
"database": {
"default_path": "/path/to/your/prompts.db"
}
}
```
Relative paths are resolved against the extension directory. Absolute paths are used as-is, which is recommended for Docker deployments where the working directory may not persist:
```json
{
"database": {
"default_path": "/data/custom_nodes/ComfyUI_PromptManager/prompts.db"
}
}
```
### Gallery & Monitoring Settings
+2 -2
View File
@@ -21,12 +21,12 @@ except ImportError:
class PromptModel:
"""Database model for prompt storage and schema management."""
def __init__(self, db_path: str = "prompts.db"):
def __init__(self, db_path: str):
"""
Initialize the database model.
Args:
db_path: Path to the SQLite database file
db_path: Absolute path to the SQLite database file
"""
self.logger = get_logger("prompt_manager.database.models")
self.logger.debug(f"Initializing database model with path: {db_path}")
+39 -2
View File
@@ -29,17 +29,54 @@ TAG_SUBQUERY = (
)
def _resolve_db_path(db_path: Optional[str] = None) -> str:
"""Resolve the database path from config, falling back to defaults.
When no explicit path is given, reads PromptManagerConfig.DEFAULT_DB_PATH
(set via config.json). Relative paths are resolved against the extension
root directory so the database location is predictable regardless of the
process working directory (important for Docker deployments).
Args:
db_path: Explicit database path, or None to use config/default.
Returns:
Absolute path to the SQLite database file.
"""
extension_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if db_path is None:
try:
from py.config import PromptManagerConfig
db_path = PromptManagerConfig.DEFAULT_DB_PATH
except Exception:
db_path = "prompts.db"
if not os.path.isabs(db_path):
db_path = os.path.join(extension_root, db_path)
# Ensure parent directory exists for custom paths
parent = os.path.dirname(db_path)
if parent:
os.makedirs(parent, exist_ok=True)
return db_path
class PromptDatabase:
"""Database operations class for managing prompts."""
def __init__(self, db_path: str = "prompts.db"):
def __init__(self, db_path: Optional[str] = None):
"""
Initialize the database operations.
Args:
db_path: Path to the SQLite database file
db_path: Path to the SQLite database file. If None, resolves from
config.json or defaults to prompts.db in the extension root.
"""
self.logger = get_logger("prompt_manager.database")
db_path = _resolve_db_path(db_path)
self.logger.debug(f"Initializing database operations with path: {db_path}")
self.model = PromptModel(db_path)
self.logger.debug("Database operations initialized successfully")
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "promptmanager"
description = "A powerful ComfyUI custom node that extends the standard text encoder with persistent prompt storage, advanced search capabilities, and an automatic image gallery system using SQLite."
version = "3.1.4"
version = "3.1.5"
license = {file = "LICENSE"}
dependencies = ["# Core dependencies for PromptManager", "# Note: Most dependencies are already included with ComfyUI", "# Already included with Python standard library:", "# - sqlite3", "# - hashlib", "# - json", "# - datetime", "# - os", "# - typing", "# - threading", "# - uuid", "# Required for gallery functionality:", "watchdog>=2.1.0 # For file system monitoring", "Pillow>=8.0.0 # For image metadata extraction (usually included with ComfyUI)", "# Optional dependencies for enhanced search functionality:", "# fuzzywuzzy[speedup]>=0.18.0 # For fuzzy string matching (optional)", "# sqlalchemy>=1.4.0 # For advanced ORM features (optional)", "# Development dependencies (optional):", "# pytest>=6.0.0 # For running tests", "# black>=22.0.0 # For code formatting", "# flake8>=4.0.0 # For linting", "# mypy>=0.910 # For type checking"]
+121
View File
@@ -0,0 +1,121 @@
"""
Tests for issue #119: Database path configuration.
Verifies that _resolve_db_path() correctly reads from config, resolves
relative paths against the extension root, and handles absolute paths.
"""
import os
import sys
import tempfile
import types
import unittest
from unittest.mock import patch
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database.operations import _resolve_db_path
EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class TestResolveDbPath(unittest.TestCase):
"""Test _resolve_db_path() database path resolution."""
def test_explicit_absolute_path_used_as_is(self):
"""An explicit absolute path should be returned unchanged."""
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "custom.db")
result = _resolve_db_path(path)
self.assertEqual(result, path)
def test_explicit_relative_path_resolved_to_extension_root(self):
"""An explicit relative path should resolve against the extension root."""
result = _resolve_db_path("data/my.db")
expected = os.path.join(EXTENSION_ROOT, "data", "my.db")
self.assertEqual(result, expected)
def test_none_falls_back_to_default_without_config(self):
"""Without config (outside ComfyUI), should default to prompts.db."""
# py.config requires ComfyUI's server module, so import fails naturally
result = _resolve_db_path(None)
expected = os.path.join(EXTENSION_ROOT, "prompts.db")
self.assertEqual(result, expected)
def test_none_reads_config_when_available(self):
"""None should read DEFAULT_DB_PATH from config when importable."""
with tempfile.TemporaryDirectory() as tmpdir:
custom_path = os.path.join(tmpdir, "prompts.db")
fake_config_mod = types.ModuleType("py.config")
fake_pm_config = type(
"PromptManagerConfig", (), {"DEFAULT_DB_PATH": custom_path}
)
fake_config_mod.PromptManagerConfig = fake_pm_config
with patch.dict(sys.modules, {"py.config": fake_config_mod}):
result = _resolve_db_path(None)
self.assertEqual(result, custom_path)
def test_config_relative_path_resolved_to_extension_root(self):
"""A relative path from config should resolve against extension root."""
fake_config_mod = types.ModuleType("py.config")
fake_pm_config = type(
"PromptManagerConfig", (), {"DEFAULT_DB_PATH": "custom_data/prompts.db"}
)
fake_config_mod.PromptManagerConfig = fake_pm_config
with patch.dict(sys.modules, {"py.config": fake_config_mod}):
result = _resolve_db_path(None)
expected = os.path.join(EXTENSION_ROOT, "custom_data", "prompts.db")
self.assertEqual(result, expected)
def test_result_is_always_absolute(self):
"""Result should always be an absolute path."""
result = _resolve_db_path("prompts.db")
self.assertTrue(os.path.isabs(result))
def test_parent_directory_created(self):
"""Parent directories should be created for custom paths."""
with tempfile.TemporaryDirectory() as tmpdir:
nested = os.path.join(tmpdir, "deep", "nested", "prompts.db")
result = _resolve_db_path(nested)
self.assertEqual(result, nested)
self.assertTrue(os.path.isdir(os.path.join(tmpdir, "deep", "nested")))
class TestPromptDatabaseUsesConfig(unittest.TestCase):
"""Test that PromptDatabase() picks up the resolved path."""
@patch("database.operations._resolve_db_path")
def test_constructor_calls_resolve_with_none(self, mock_resolve):
"""PromptDatabase() should call _resolve_db_path with None."""
with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as f:
mock_resolve.return_value = f.name
try:
from database.operations import PromptDatabase
PromptDatabase()
mock_resolve.assert_called_once_with(None)
finally:
os.unlink(f.name)
@patch("database.operations._resolve_db_path")
def test_constructor_passes_explicit_path(self, mock_resolve):
"""PromptDatabase(path) should call _resolve_db_path with that path."""
with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as f:
mock_resolve.return_value = f.name
try:
from database.operations import PromptDatabase
PromptDatabase("/explicit/path.db")
mock_resolve.assert_called_once_with("/explicit/path.db")
finally:
os.unlink(f.name)
if __name__ == "__main__":
unittest.main()