* 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:
@@ -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
@@ -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
@@ -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
@@ -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"]
|
||||
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user