Initial commit
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
load_dotenv(find_dotenv())
|
||||
import os
|
||||
import time
|
||||
from typing import Optional, Tuple, Generator
|
||||
from modules.logger_tool import initialise_logger
|
||||
from neo4j import GraphDatabase as gd, Driver, Session
|
||||
from contextlib import contextmanager
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
def _retry_with_backoff(
|
||||
func,
|
||||
max_attempts: int = 10, # Increased from 3 to 10
|
||||
initial_delay: float = 2.0, # Increased from 1 to 2 seconds
|
||||
max_total_wait: float = 60.0, # Maximum total time to wait (60 seconds)
|
||||
max_delay: float = 10.0 # Maximum delay between retries
|
||||
) -> any:
|
||||
"""
|
||||
Helper function to retry operations with exponential backoff.
|
||||
|
||||
Args:
|
||||
func: Function to retry
|
||||
max_attempts: Maximum number of retry attempts
|
||||
initial_delay: Initial delay between retries in seconds
|
||||
max_total_wait: Maximum total time to wait before giving up
|
||||
max_delay: Maximum delay between retries
|
||||
"""
|
||||
attempt = 0
|
||||
delay = initial_delay
|
||||
start_time = time.time()
|
||||
|
||||
while attempt < max_attempts:
|
||||
try:
|
||||
return func()
|
||||
except Exception as e:
|
||||
attempt += 1
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
# Check if we've exceeded the maximum total wait time
|
||||
if elapsed_time >= max_total_wait:
|
||||
logger.error(f"Exceeded maximum total wait time of {max_total_wait} seconds")
|
||||
raise
|
||||
|
||||
if attempt == max_attempts:
|
||||
logger.error(f"Final attempt {attempt} failed: {e}")
|
||||
raise
|
||||
|
||||
# Calculate next delay with exponential backoff, but cap it
|
||||
delay = min(delay * 2, max_delay)
|
||||
|
||||
# If we're in a container initialization scenario, provide more context
|
||||
if "Connection refused" in str(e):
|
||||
logger.warning(
|
||||
f"Attempt {attempt} failed: Connection refused. "
|
||||
f"This might indicate that Neo4j is still starting up. "
|
||||
f"Retrying in {delay:.1f} seconds... "
|
||||
f"(Total elapsed: {elapsed_time:.1f}s)"
|
||||
)
|
||||
else:
|
||||
logger.warning(f"Attempt {attempt} failed: {e}. Retrying in {delay:.1f} seconds...")
|
||||
|
||||
time.sleep(delay)
|
||||
|
||||
def get_driver(db_name: Optional[str] = None, url: Optional[str] = None, auth: Optional[Tuple[str, str]] = None) -> Optional[Driver]:
|
||||
if url is None:
|
||||
url = os.getenv("APP_BOLT_URL")
|
||||
username = os.getenv("USER_NEO4J")
|
||||
password = os.getenv("PASSWORD_NEO4J")
|
||||
if not username or not password:
|
||||
logger.error("Neo4j credentials not found in environment")
|
||||
return None
|
||||
auth = (username, password)
|
||||
|
||||
if auth is None:
|
||||
logger.error("No authentication credentials provided")
|
||||
return None
|
||||
|
||||
def create_driver():
|
||||
logger.info(f"Attempting to connect to Neo4j at {url}")
|
||||
driver = gd.driver(url, auth=auth)
|
||||
driver.verify_connectivity()
|
||||
logger.info(f"Connected to Neo4j at {url}")
|
||||
return driver
|
||||
|
||||
try:
|
||||
# Use more lenient retry parameters for initial connection
|
||||
driver = _retry_with_backoff(
|
||||
create_driver,
|
||||
max_attempts=10,
|
||||
initial_delay=2.0,
|
||||
max_total_wait=60.0,
|
||||
max_delay=10.0
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to establish Neo4j connection after all retries: {e}")
|
||||
return None
|
||||
|
||||
# Test the connection with the specific database
|
||||
if db_name and driver:
|
||||
def verify_database():
|
||||
with driver.session(database=db_name) as session:
|
||||
result = session.run("RETURN 'Connection successful' AS message")
|
||||
record = result.single()
|
||||
if not record or not record.get("message"):
|
||||
raise Exception(f"Failed to verify database {db_name} connection")
|
||||
logger.info(f"Connection to Neo4j at {url} with database {db_name} successful")
|
||||
|
||||
try:
|
||||
# Use more lenient retry parameters for database verification
|
||||
_retry_with_backoff(
|
||||
verify_database,
|
||||
max_attempts=10,
|
||||
initial_delay=2.0,
|
||||
max_total_wait=60.0,
|
||||
max_delay=10.0
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to database {db_name} after all retries: {e}")
|
||||
driver.close()
|
||||
return None
|
||||
|
||||
return driver
|
||||
|
||||
def close_driver(driver: Optional[Driver]) -> None:
|
||||
if driver:
|
||||
logger.info("Closing driver")
|
||||
driver.close()
|
||||
|
||||
# Global driver instance
|
||||
_driver: Optional[Driver] = None
|
||||
|
||||
def get_global_driver() -> Optional[Driver]:
|
||||
"""Get or create the global Neo4j driver instance."""
|
||||
global _driver
|
||||
if _driver is None:
|
||||
_driver = get_driver()
|
||||
return _driver
|
||||
|
||||
@contextmanager
|
||||
def get_session(database: Optional[str] = None) -> Generator[Session, None, None]:
|
||||
"""Get a Neo4j session using the global driver."""
|
||||
driver = get_global_driver()
|
||||
if driver is None:
|
||||
raise Exception("Failed to get Neo4j driver")
|
||||
|
||||
session = None
|
||||
try:
|
||||
session = driver.session(database=database)
|
||||
yield session
|
||||
finally:
|
||||
if session:
|
||||
session.close()
|
||||
Reference in New Issue
Block a user