diff --git a/memori/core/database.py b/memori/core/database.py index dfe1485..7083d22 100644 --- a/memori/core/database.py +++ b/memori/core/database.py @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from pathlib import Path from typing import Any +from traceback import format_exc from loguru import logger @@ -95,6 +96,9 @@ def initialize_schema(self): logger.warning( f"Schema execution issue: {e}, falling back to statement-by-statement" ) + logger.warning( + f"Schema execution error details: {format_exc()}" + ) # Fallback to statement-by-statement execution self._execute_schema_statements(conn, schema_sql) @@ -104,6 +108,9 @@ def initialize_schema(self): except Exception as e: logger.error(f"Failed to initialize schema: {e}") + logger.error(f"Schema initialization error details: {format_exc()}") + logger.info("Falling back to basic schema creation") + # Fallback to basic schema self._create_basic_schema() @@ -151,76 +158,84 @@ def _execute_schema_statements(self, conn: sqlite3.Connection, schema_sql: str): try: conn.execute(statement) except sqlite3.Error as e: - logger.debug(f"Schema statement warning: {e}") + logger.debug(f"Schema statement error: {e}") + logger.debug(f"Schema statement error details: {format_exc()}") conn.commit() def _create_basic_schema(self): """Create basic schema if SQL file is not available""" - with self._get_connection() as conn: - cursor = conn.cursor() + try: + with self._get_connection() as conn: + cursor = conn.cursor() - # Basic tables for fallback - cursor.execute( + # Basic tables for fallback + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS chat_history ( + chat_id TEXT PRIMARY KEY, + user_input TEXT NOT NULL, + ai_output TEXT NOT NULL, + model TEXT NOT NULL, + timestamp TIMESTAMP NOT NULL, + session_id TEXT NOT NULL, + namespace TEXT NOT NULL DEFAULT 'default', + tokens_used INTEGER DEFAULT 0, + metadata TEXT DEFAULT '{}' + ) """ - CREATE TABLE IF NOT EXISTS chat_history ( - chat_id TEXT PRIMARY KEY, - user_input TEXT NOT NULL, - ai_output TEXT NOT NULL, - model TEXT NOT NULL, - timestamp TIMESTAMP NOT NULL, - session_id TEXT NOT NULL, - namespace TEXT NOT NULL DEFAULT 'default', - tokens_used INTEGER DEFAULT 0, - metadata TEXT DEFAULT '{}' ) - """ - ) - cursor.execute( + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS short_term_memory ( + memory_id TEXT PRIMARY KEY, + chat_id TEXT, + processed_data TEXT NOT NULL, + importance_score REAL NOT NULL DEFAULT 0.5, + category_primary TEXT NOT NULL, + retention_type TEXT NOT NULL DEFAULT 'short_term', + namespace TEXT NOT NULL DEFAULT 'default', + created_at TIMESTAMP NOT NULL, + expires_at TIMESTAMP, + access_count INTEGER DEFAULT 0, + last_accessed TIMESTAMP, + searchable_content TEXT NOT NULL, + summary TEXT NOT NULL + ) """ - CREATE TABLE IF NOT EXISTS short_term_memory ( - memory_id TEXT PRIMARY KEY, - chat_id TEXT, - processed_data TEXT NOT NULL, - importance_score REAL NOT NULL DEFAULT 0.5, - category_primary TEXT NOT NULL, - retention_type TEXT NOT NULL DEFAULT 'short_term', - namespace TEXT NOT NULL DEFAULT 'default', - created_at TIMESTAMP NOT NULL, - expires_at TIMESTAMP, - access_count INTEGER DEFAULT 0, - last_accessed TIMESTAMP, - searchable_content TEXT NOT NULL, - summary TEXT NOT NULL ) - """ - ) - cursor.execute( + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS long_term_memory ( + memory_id TEXT PRIMARY KEY, + original_chat_id TEXT, + processed_data TEXT NOT NULL, + importance_score REAL NOT NULL DEFAULT 0.5, + category_primary TEXT NOT NULL, + retention_type TEXT NOT NULL DEFAULT 'long_term', + namespace TEXT NOT NULL DEFAULT 'default', + created_at TIMESTAMP NOT NULL, + access_count INTEGER DEFAULT 0, + last_accessed TIMESTAMP, + searchable_content TEXT NOT NULL, + summary TEXT NOT NULL, + novelty_score REAL DEFAULT 0.5, + relevance_score REAL DEFAULT 0.5, + actionability_score REAL DEFAULT 0.5 + ) """ - CREATE TABLE IF NOT EXISTS long_term_memory ( - memory_id TEXT PRIMARY KEY, - original_chat_id TEXT, - processed_data TEXT NOT NULL, - importance_score REAL NOT NULL DEFAULT 0.5, - category_primary TEXT NOT NULL, - retention_type TEXT NOT NULL DEFAULT 'long_term', - namespace TEXT NOT NULL DEFAULT 'default', - created_at TIMESTAMP NOT NULL, - access_count INTEGER DEFAULT 0, - last_accessed TIMESTAMP, - searchable_content TEXT NOT NULL, - summary TEXT NOT NULL, - novelty_score REAL DEFAULT 0.5, - relevance_score REAL DEFAULT 0.5, - actionability_score REAL DEFAULT 0.5 ) - """ - ) - conn.commit() - logger.info("Basic database schema created") + conn.commit() + logger.info("Basic database schema created") + except Exception as e: + error_message = f"Failed to create basic schema: {e}" + + logger.error(error_message) + logger.error(f"Basic schema creation error details: {format_exc()}") + raise DatabaseError(error_message) def store_chat_history( self, diff --git a/tests/schema_exception_test.py b/tests/schema_exception_test.py new file mode 100644 index 0000000..38c7d3f --- /dev/null +++ b/tests/schema_exception_test.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +""" +A very simple test for schema initialization exception +logging and fallback to the basic schema +""" + +from loguru import logger +import sys +import tempfile +from pathlib import Path + +# Add the memori package to the Python path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def test_schema_exception_logging(): + """Test that schema initialization exceptions are logged with traceback and fallback to the basic schema""" + print("๐Ÿงช Testing schema exception logging and fallback to the basic schema...") + + temp_path = None + + try: + from memori.core.database import DatabaseManager + + # Create temp database + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + temp_path = f.name + + # Create a list to store the log messages + captured_logs = [] + + # Create a handler that captures the log messages + def capture_log(message): + captured_logs.append(message.strip()) + + # Add the custom handler to the logger + logger.add(capture_log, level="TRACE") + + # Test with invalid template (this will trigger the fallback) + db_manager = DatabaseManager( + database_connect=f"sqlite:///{temp_path}", template="basic" + ) + + # Initialize schema (it should fail and log errors then fallback to the basic schema) + db_manager.initialize_schema() + + # Verify it worked by checking tables exist + with db_manager._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = [row[0] for row in cursor.fetchall()] + + assert "chat_history" in tables, "Fallback schema creation did NOT work" + + assert len(captured_logs) > 0, "No logs captured" + + assert any( + "Schema execution issue" in log for log in captured_logs + ), "Schema execution issue log not found" + assert any( + "Schema execution error details: Traceback" in log for log in captured_logs + ), "Schema execution error details: Traceback log not found" + + assert any( + "Schema statement error" in log for log in captured_logs + ), "Schema statement error log not found" + assert any( + "Schema statement error details: Traceback" in log for log in captured_logs + ), "Schema statement error details: Traceback log not found" + + print("โœ… Schema exception logging and fallback works") + return True + + except Exception as e: + print(f"โŒ Test failed: {e}") + return False + finally: + if temp_path and Path(temp_path).exists(): + Path(temp_path).unlink(missing_ok=True) + + +def main(): + """Main test function""" + print("๐Ÿš€ Schema Exception Logging Test") + print("=" * 40) + + success = test_schema_exception_logging() + + if success: + print("\nโœ… Schema exception logging test passed!") + return 0 + else: + print("\nโŒ Schema exception logging test failed!") + return 1 + + +if __name__ == "__main__": + sys.exit(main())