"""pubsalesacc/connectors/neo4j.py — Neo4j graph database connector. Driver construction is deferred to first use (not at import time) so that a missing or incorrect .env does not crash the application on startup. Hostnames and environment names are read from config.NEO4J_HOSTS so they can be updated without touching connector code. """ import logging import os from dotenv import load_dotenv from neo4j import GraphDatabase, Driver load_dotenv() logger = logging.getLogger(__name__) _VALID_ENVS = ("dev", "qa", "prod") def _get_auth(env: str) -> tuple[str, str]: """Return (username, password) for the given Neo4j environment.""" username = os.getenv("NEO4JUSER") if not username: raise EnvironmentError("NEO4JUSER is not set in .env") env_key_map = { "dev": "NEO4JDEVPW", "qa": "NEO4JQAPW", "prod": "NEO4JPRODPW", } pw_key = env_key_map.get(env) if pw_key is None: raise ValueError( f"Unknown Neo4j environment '{env}'. Must be one of: {_VALID_ENVS}" ) password = os.getenv(pw_key) if not password: raise EnvironmentError(f"{pw_key} is not set in .env") return username, password def get_driver(env: str) -> Driver: """Return a Neo4j Driver for the specified environment ('dev', 'qa', 'prod'). The caller is responsible for closing the driver (use as a context manager): with get_driver('prod') as driver: result = driver.execute_query(...) """ # Import here to avoid circular import; config is safe to import at call time from config import NEO4J_HOSTS if env not in _VALID_ENVS: raise ValueError( f"Unknown Neo4j environment '{env}'. Must be one of: {_VALID_ENVS}" ) host = NEO4J_HOSTS[env] uri = f"neo4j+s://{host}" auth = _get_auth(env) logger.debug("Connecting to Neo4j %s at %s", env, host) return GraphDatabase.driver(uri, auth=auth) def connectivity_check(env: str) -> None: """Verify connectivity to the given Neo4j environment. Raises on failure.""" with get_driver(env) as driver: driver.verify_connectivity() logger.info("Neo4j %s connectivity check passed.", env)