""" Database configuration and connection management """ import os from sqlalchemy import create_engine, MetaData from sqlalchemy.orm import sessionmaker from contextlib import contextmanager from app.models import Base import logging class DatabaseConfig: """Database configuration and connection management""" def __init__(self, database_url=None): if database_url is None: # Default to SQLite with improved path self.database_url = f"sqlite:///data/enhanced_monitoring.db" else: self.database_url = database_url self.engine = None self.Session = None self._setup_database() def _setup_database(self): """Initialize database connection and session factory""" # Create data directory if it doesn't exist os.makedirs('data', exist_ok=True) # Create engine with connection pooling for SQLite self.engine = create_engine( self.database_url, echo=False, # Set to True for SQL debugging pool_pre_ping=True, connect_args={"check_same_thread": False} # For SQLite ) # Create session factory self.Session = sessionmaker(bind=self.engine) def create_tables(self): """Create all database tables""" try: Base.metadata.create_all(self.engine) self.ensure_schema() logging.info("Database tables created successfully") return True except Exception as e: logging.error(f"Error creating database tables: {e}") return False def ensure_schema(self): """Idempotently add columns that were introduced after the table was first created. SQLAlchemy's create_all() never ALTERs existing tables, so new columns on an existing SQLite database must be added manually. """ from sqlalchemy import text # Columns added over time: (table, column, SQL type definition) required_columns = [ ('devices', 'wmt_last_seen', 'DATETIME'), ('devices', 'config_synced_at', 'DATETIME'), ('devices', 'custom_chrome_url', 'VARCHAR(500)'), ] try: with self.engine.connect() as conn: for table, column, col_type in required_columns: exists = conn.execute( text("SELECT name FROM sqlite_master WHERE type='table' AND name=:t"), {'t': table}, ).fetchone() if not exists: continue cols = [row[1] for row in conn.execute(text(f"PRAGMA table_info({table})")).fetchall()] if column not in cols: conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}")) conn.commit() logging.info(f"Schema: added column {table}.{column}") except Exception as e: logging.error(f"Error ensuring schema columns: {e}") def drop_tables(self): """Drop all database tables (use with caution!)""" try: Base.metadata.drop_all(self.engine) logging.info("Database tables dropped successfully") return True except Exception as e: logging.error(f"Error dropping database tables: {e}") return False @contextmanager def get_session(self): """Context manager for database sessions""" session = self.Session() try: yield session session.commit() except Exception as e: session.rollback() logging.error(f"Database session error: {e}") raise finally: session.close() def get_session_direct(self): """Get session directly (remember to close it)""" return self.Session() def backup_database(self, backup_path=None): """Create database backup""" if backup_path is None: from datetime import datetime timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = f"data/backups/backup_{timestamp}.db" try: # Create backups directory os.makedirs('data/backups', exist_ok=True) # For SQLite, simple file copy if self.database_url.startswith('sqlite'): import shutil db_file = self.database_url.replace('sqlite:///', '') shutil.copy2(db_file, backup_path) logging.info(f"Database backup created: {backup_path}") return backup_path else: # For other databases, implement proper backup logging.warning("Backup not implemented for non-SQLite databases") return None except Exception as e: logging.error(f"Database backup failed: {e}") return None # Global database instance db_config = None def init_database(database_url=None): """Initialize global database configuration""" global db_config db_config = DatabaseConfig(database_url) return db_config.create_tables() def get_db(): """Get database configuration instance""" global db_config if db_config is None: init_database() return db_config