import logging import os from pathlib import Path from typing import Any from dotenv import load_dotenv from neo4j import GraphDatabase # Configure logging at module level logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') class CypherRunner: """A simple Neo4j query runner that uses environment variables for configuration.""" def __init__(self, queries_dir: str = 'cypher'): load_dotenv() self.logger = logging.getLogger(__name__) self.queries_dir = Path(queries_dir) if not self.queries_dir.exists(): self.queries_dir.mkdir(parents=True) self.logger.info(f'Created queries directory: {self.queries_dir}') self.uri = os.getenv('NEO4J_URL') self.user = os.getenv('NEO4J_USERNAME') self.password = os.getenv('NEO4J_PASSWORD') self.max_retry_time = int(os.getenv('NEO4J_MAX_RETRY_TIME', '30')) if not all([self.uri, self.user, self.password]): raise ValueError('Missing required environment variables. Please check your .env file.') self.driver = GraphDatabase.driver( self.uri, auth=(self.user, self.password), max_transaction_retry_time=self.max_retry_time, ) try: self.driver.verify_connectivity() self.logger.info('Successfully connected to Neo4j database') except Exception as e: self.logger.error(f'Failed to connect to Neo4j database: {e}') raise def close(self) -> None: """Close the database driver.""" self.driver.close() def load_query_file(self, filename: str) -> str: """ Load a Cypher query from a file in the queries directory. Args: filename: Name of the query file (with or without .cypher extension) Returns: The query string from the file """ if not filename.endswith('.cypher'): filename = f'{filename}.cypher' query_path = self.queries_dir / filename print(query_path) try: with open(query_path, 'r') as f: query = f.read().strip() self.logger.info(f'Successfully loaded query from {filename}') return query except FileNotFoundError: raise FileNotFoundError(f'Query file not found: {filename}') except Exception as e: self.logger.error(f'Error loading query file {filename}: {e}') raise def run_query( self, query: str, parameters: dict[str, Any] | None = None, ) -> list[dict[str, Any]]: """ Execute a Cypher query and return the results. Args: query: The Cypher query to execute parameters: Optional dictionary of query parameters Returns: List of dictionaries containing the query results """ parameters = parameters or {} try: with self.driver.session() as session: result = session.run(query, parameters) records = [dict(record) for record in result] return records except Exception as e: self.logger.error(f'Query execution failed: {e}') raise def __enter__(self) -> 'CypherRunner': return self def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: self.close()