Files
Northern-Thailand-Ping-Rive…/scripts/setup_postgres.py
grabowski 6c7c128b4d Major refactor: Migrate to uv, add PostgreSQL support, and comprehensive tooling
- **Migration to uv package manager**: Replace pip/requirements with modern pyproject.toml
  - Add pyproject.toml with complete dependency management
  - Update all scripts and Makefile to use uv commands
  - Maintain backward compatibility with existing workflows

- **PostgreSQL integration and migration tools**:
  - Enhanced config.py with automatic password URL encoding
  - Complete PostgreSQL setup scripts and documentation
  - High-performance SQLite to PostgreSQL migration tool (91x speed improvement)
  - Support for both connection strings and individual components

- **Executable distribution system**:
  - PyInstaller integration for standalone .exe creation
  - Automated build scripts with batch file generation
  - Complete packaging system for end-user distribution

- **Enhanced data management**:
  - Fix --fill-gaps command with proper method implementation
  - Add gap detection and historical data backfill capabilities
  - Implement data update functionality for existing records
  - Add comprehensive database adapter methods

- **Developer experience improvements**:
  - Password encoding tools for special characters
  - Interactive setup wizards for PostgreSQL configuration
  - Comprehensive documentation and migration guides
  - Automated testing and validation tools

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 15:10:10 +07:00

175 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""
PostgreSQL setup script for Northern Thailand Ping River Monitor
This script helps you configure and test your PostgreSQL connection
"""
import os
import sys
import logging
from typing import Optional
from urllib.parse import urlparse
def setup_logging():
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
def test_postgres_connection(connection_string: str) -> bool:
"""Test connection to PostgreSQL database"""
try:
from sqlalchemy import create_engine, text
# Test connection
engine = create_engine(connection_string, pool_pre_ping=True)
with engine.connect() as conn:
result = conn.execute(text("SELECT version()"))
version = result.fetchone()[0]
logging.info(f"✅ Connected to PostgreSQL successfully!")
logging.info(f"Database version: {version}")
return True
except ImportError:
logging.error("❌ psycopg2-binary not installed. Run: uv add psycopg2-binary")
return False
except Exception as e:
logging.error(f"❌ Connection failed: {e}")
return False
def parse_connection_string(connection_string: str) -> dict:
"""Parse PostgreSQL connection string into components"""
try:
parsed = urlparse(connection_string)
return {
'host': parsed.hostname,
'port': parsed.port or 5432,
'database': parsed.path[1:] if parsed.path else None,
'username': parsed.username,
'password': parsed.password,
}
except Exception as e:
logging.error(f"Failed to parse connection string: {e}")
return {}
def create_database_if_not_exists(connection_string: str, database_name: str) -> bool:
"""Create database if it doesn't exist"""
try:
from sqlalchemy import create_engine, text
# Connect to default postgres database to create our database
parsed = urlparse(connection_string)
admin_connection = connection_string.replace(f"/{parsed.path[1:]}", "/postgres")
engine = create_engine(admin_connection, pool_pre_ping=True)
with engine.connect() as conn:
# Check if database exists
result = conn.execute(text(
"SELECT 1 FROM pg_database WHERE datname = :db_name"
), {"db_name": database_name})
if result.fetchone():
logging.info(f"✅ Database '{database_name}' already exists")
return True
else:
# Create database
conn.execute(text("COMMIT")) # End transaction
conn.execute(text(f'CREATE DATABASE "{database_name}"'))
logging.info(f"✅ Created database '{database_name}'")
return True
except Exception as e:
logging.error(f"❌ Failed to create database: {e}")
return False
def initialize_tables(connection_string: str) -> bool:
"""Initialize database tables"""
try:
# Import the database adapter to create tables
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from database_adapters import SQLAdapter
adapter = SQLAdapter(connection_string=connection_string, db_type='postgresql')
if adapter.connect():
logging.info("✅ Database tables initialized successfully")
return True
else:
logging.error("❌ Failed to initialize tables")
return False
except Exception as e:
logging.error(f"❌ Failed to initialize tables: {e}")
return False
def interactive_setup():
"""Interactive setup wizard"""
print("🐘 PostgreSQL Setup Wizard for Ping River Monitor")
print("=" * 50)
# Get connection details
host = input("PostgreSQL host (e.g., 192.168.1.100): ").strip()
port = input("PostgreSQL port [5432]: ").strip() or "5432"
database = input("Database name [water_monitoring]: ").strip() or "water_monitoring"
username = input("Username: ").strip()
password = input("Password: ").strip()
# Optional SSL
use_ssl = input("Use SSL connection? (y/N): ").strip().lower() == 'y'
ssl_params = "?sslmode=require" if use_ssl else ""
connection_string = f"postgresql://{username}:{password}@{host}:{port}/{database}{ssl_params}"
print(f"\nGenerated connection string:")
print(f"POSTGRES_CONNECTION_STRING={connection_string}")
return connection_string
def main():
setup_logging()
print("🚀 Northern Thailand Ping River Monitor - PostgreSQL Setup")
print("=" * 60)
# Check if connection string is provided via environment
connection_string = os.getenv('POSTGRES_CONNECTION_STRING')
if not connection_string:
print("No POSTGRES_CONNECTION_STRING found in environment.")
print("Starting interactive setup...\n")
connection_string = interactive_setup()
# Suggest adding to .env file
print(f"\n💡 Add this to your .env file:")
print(f"DB_TYPE=postgresql")
print(f"POSTGRES_CONNECTION_STRING={connection_string}")
# Parse connection details
config = parse_connection_string(connection_string)
if not config.get('host'):
logging.error("Invalid connection string format")
return False
print(f"\n🔗 Connecting to PostgreSQL at {config['host']}:{config['port']}")
# Test connection
if not test_postgres_connection(connection_string):
return False
# Try to create database
database_name = config.get('database', 'water_monitoring')
if database_name:
create_database_if_not_exists(connection_string, database_name)
# Initialize tables
if not initialize_tables(connection_string):
return False
print("\n🎉 PostgreSQL setup completed successfully!")
print("\nNext steps:")
print("1. Update your .env file with the connection string")
print("2. Run: make run-test")
print("3. Run: make run-api")
return True
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)