KIG / kig_core /graph_client.py
heymenn's picture
Update kig_core/graph_client.py
3cf99bc verified
from neo4j import GraphDatabase, Driver, exceptions
from .config import settings
import logging
from typing import List, Dict, Any, Optional
logger = logging.getLogger(__name__)
class Neo4jClient:
_driver: Optional[Driver] = None
def _get_driver(self) -> Driver:
"""Initializes and returns the Neo4j driver instance."""
if self._driver is None or self._driver.close():
logger.info(f"Initializing Neo4j Driver for URI: {settings.neo4j_uri}")
try:
self._driver = GraphDatabase.driver(
settings.neo4j_uri,
auth=(settings.neo4j_username, settings.neo4j_password.get_secret_value())
)
# Verify connectivity during initialization
self._driver.verify_connectivity()
logger.info("Neo4j Driver initialized and connection verified.")
except exceptions.AuthError as e:
logger.error(f"Neo4j Authentication Error: {e}", exc_info=True)
raise ConnectionError("Neo4j Authentication Failed. Check credentials.") from e
except exceptions.ServiceUnavailable as e:
logger.error(f"Neo4j Service Unavailable: {e}", exc_info=True)
raise ConnectionError(f"Could not connect to Neo4j at {settings.neo4j_uri}. Ensure DB is running and reachable.") from e
except Exception as e:
logger.error(f"Unexpected error initializing Neo4j Driver: {e}", exc_info=True)
raise ConnectionError("An unexpected error occurred connecting to Neo4j.") from e
return self._driver
def close(self):
"""Closes the Neo4j driver connection."""
if self._driver and not self._driver.close():
logger.info("Closing Neo4j Driver.")
self._driver.close()
self._driver = None
def query(self, cypher_query: str, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
"""Executes a Cypher query and returns the results."""
driver = self._get_driver()
logger.debug(f"Executing Cypher: {cypher_query} with params: {params}")
try:
# Use session/transaction for robust execution
with driver.session() as session:
result = session.run(cypher_query, params or {})
# Convert Neo4j Records to dictionaries
data = [record.data() for record in result]
logger.debug(f"Query returned {len(data)} records.")
return data
except (exceptions.ServiceUnavailable, exceptions.SessionExpired) as e:
logger.error(f"Neo4j connection error during query: {e}", exc_info=True)
# Attempt to close the potentially broken driver so it reconnects next time
self.close()
raise ConnectionError("Neo4j connection error during query execution.") from e
except exceptions.CypherSyntaxError as e:
logger.error(f"Neo4j Cypher Syntax Error: {e}\nQuery: {cypher_query}", exc_info=True)
raise ValueError("Invalid Cypher query syntax.") from e
except Exception as e:
logger.error(f"Unexpected error during Neo4j query: {e}", exc_info=True)
raise RuntimeError("An unexpected error occurred during the Neo4j query.") from e
def get_schema(self, force_refresh: bool = False) -> Dict[str, Any]:
""" Fetches the graph schema. Placeholder - Langchain community graph has better schema fetching."""
# For simplicity, returning empty. Implement actual schema fetching if needed.
# Consider using langchain_community.graphs.Neo4jGraph for schema handling if complex interactions are needed.
logger.warning("Neo4jClient.get_schema() is a placeholder. Implement if schema needed.")
return {} # Placeholder
def get_concepts(self) -> List[str]:
"""Fetches all Concept names from the graph."""
cypher = "MATCH (c:Concept) RETURN c.name AS name ORDER BY name"
results = self.query(cypher)
return [record['name'] for record in results if 'name' in record]
def get_concept_description(self, concept_name: str) -> Optional[str]:
"""Fetches the description for a specific concept."""
cypher = "MATCH (c:Concept {name: $name}) RETURN c.description AS description LIMIT 1"
params = {"name": concept_name}
results = self.query(cypher, params)
return results[0]['description'] if results and 'description' in results[0] else None
# Create a single instance for the application to use
neo4j_client = Neo4jClient()
# Ensure the client is closed gracefully when the application exits
import atexit
atexit.register(neo4j_client.close)