Initial Commit

This commit is contained in:
Eric Gullickson
2025-09-17 16:09:15 -05:00
parent 0cdb9803de
commit a052040e3a
373 changed files with 437090 additions and 6773 deletions

View File

@@ -0,0 +1,152 @@
import pyodbc
import psycopg2
from psycopg2.extras import RealDictCursor
import asyncpg
import redis
from contextlib import contextmanager
import logging
import time
from typing import Optional
from .config import config
logger = logging.getLogger(__name__)
class DatabaseConnections:
"""Manage database connections with retry logic and timeouts"""
def __init__(self):
self.mssql_conn = None
self.postgres_conn = None
self.redis_client = None
self.pg_pool = None
self.max_retries = 3
self.retry_delay = 2 # seconds
def _retry_connection(self, connection_func, connection_type: str, max_retries: Optional[int] = None):
"""Retry connection with exponential backoff"""
max_retries = max_retries or self.max_retries
for attempt in range(max_retries):
try:
return connection_func()
except Exception as e:
if attempt == max_retries - 1:
logger.error(f"Failed to connect to {connection_type} after {max_retries} attempts: {e}")
raise
wait_time = self.retry_delay * (2 ** attempt)
logger.warning(f"{connection_type} connection failed (attempt {attempt + 1}/{max_retries}): {e}")
logger.info(f"Retrying in {wait_time} seconds...")
time.sleep(wait_time)
@contextmanager
def mssql_connection(self):
"""Context manager for MS SQL connection using pyodbc with retry logic"""
def _connect():
connection_string = (
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
f"SERVER={config.MSSQL_HOST},{config.MSSQL_PORT};"
f"DATABASE={config.MSSQL_DATABASE};"
f"UID={config.MSSQL_USER};"
f"PWD={config.MSSQL_PASSWORD};"
f"TrustServerCertificate=yes;"
f"Connection Timeout=30;"
f"Command Timeout=300;"
)
return pyodbc.connect(connection_string)
conn = self._retry_connection(_connect, "MSSQL")
try:
yield conn
finally:
try:
conn.close()
except Exception as e:
logger.warning(f"Error closing MSSQL connection: {e}")
@contextmanager
def postgres_connection(self):
"""Context manager for PostgreSQL connection with retry logic"""
def _connect():
return psycopg2.connect(
host=config.POSTGRES_HOST,
port=config.POSTGRES_PORT,
database=config.POSTGRES_DATABASE,
user=config.POSTGRES_USER,
password=config.POSTGRES_PASSWORD,
cursor_factory=RealDictCursor,
connect_timeout=30,
options='-c statement_timeout=300000' # 5 minutes
)
conn = self._retry_connection(_connect, "PostgreSQL")
try:
yield conn
finally:
try:
conn.close()
except Exception as e:
logger.warning(f"Error closing PostgreSQL connection: {e}")
async def create_pg_pool(self):
"""Create async PostgreSQL connection pool"""
self.pg_pool = await asyncpg.create_pool(
host=config.POSTGRES_HOST,
port=config.POSTGRES_PORT,
database=config.POSTGRES_DATABASE,
user=config.POSTGRES_USER,
password=config.POSTGRES_PASSWORD,
min_size=10,
max_size=20
)
return self.pg_pool
def get_redis_client(self):
"""Get Redis client"""
if not self.redis_client:
self.redis_client = redis.Redis(
host=config.REDIS_HOST,
port=config.REDIS_PORT,
db=config.REDIS_DB,
decode_responses=True
)
return self.redis_client
def test_connections():
"""Test all database connections for health check"""
try:
# Test MSSQL connection (use master DB to avoid failures before restore)
db = DatabaseConnections()
mssql_master_conn_str = (
f"DRIVER={{ODBC Driver 17 for SQL Server}};"
f"SERVER={config.MSSQL_HOST},{config.MSSQL_PORT};"
f"DATABASE=master;"
f"UID={config.MSSQL_USER};"
f"PWD={config.MSSQL_PASSWORD};"
f"TrustServerCertificate=yes;"
)
import pyodbc as _pyodbc
with _pyodbc.connect(mssql_master_conn_str) as conn:
cursor = conn.cursor()
cursor.execute("SELECT 1")
cursor.fetchone()
logger.info("MSSQL connection successful (master)")
# Test PostgreSQL connection
with db.postgres_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT 1")
cursor.fetchone()
logger.info("PostgreSQL connection successful")
# Test Redis connection
redis_client = db.get_redis_client()
redis_client.ping()
logger.info("Redis connection successful")
return True
except Exception as e:
logger.error(f"Connection test failed: {e}")
return False
db_connections = DatabaseConnections()