from typing import Any from neo4j import GraphDatabase from .secrets import get_secret class Neo4jConnection: def __init__( self, username_secret: str, password_secret: str, hostname_secret: str ) -> None: """Connect to Neo4j using credentials from Secrets Manager. Each argument is the name of a Secrets Manager secret containing a plain string. If the hostname secret does not include a URI scheme, bolt:// is prepended automatically. """ username = get_secret(username_secret) password = get_secret(password_secret) hostname = get_secret(hostname_secret) if not isinstance(username, str): raise ValueError( f"Secret '{username_secret}' must be a plain string, got {type(username).__name__}" ) if not isinstance(password, str): raise ValueError( f"Secret '{password_secret}' must be a plain string, got {type(password).__name__}" ) if not isinstance(hostname, str): raise ValueError( f"Secret '{hostname_secret}' must be a plain string, got {type(hostname).__name__}" ) if '://' in hostname: uri = hostname elif ':' in hostname: uri = f'bolt://{hostname}' else: uri = f'bolt://{hostname}:7687' self._driver = GraphDatabase.driver(uri, auth=(username, password)) def __enter__(self) -> 'Neo4jConnection': return self def __exit__(self, *_: Any) -> None: self.close() def run(self, query: str, **params: Any) -> list[dict[str, Any]]: with self._driver.session() as session: result = session.run(query, **params) return [record.data() for record in result] def close(self) -> None: self._driver.close()