Add Neo4j as an optional graph-native storage layer (ingestion profile). Introduces infra/neo4j with a singleton async driver wrapper and an idempotent bootstrap of constraints + indexes, wired into the FastAPI lifespan. Integration tests skip when no live Neo4j is reachable. Refs #186
48 lines
1.2 KiB
Python
48 lines
1.2 KiB
Python
"""Async Neo4j driver wrapper.
|
|
|
|
Owns a single `AsyncDriver` per process. Callers acquire it via
|
|
`get_driver()` and must call `close_driver()` at shutdown.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass
|
|
|
|
from neo4j import AsyncDriver, AsyncGraphDatabase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Neo4jDriver:
|
|
driver: AsyncDriver
|
|
database: str = "neo4j"
|
|
|
|
|
|
_instance: Neo4jDriver | None = None
|
|
|
|
|
|
async def get_driver(uri: str, user: str, password: str, database: str = "neo4j") -> Neo4jDriver:
|
|
"""Return the process-wide driver, creating it on first call.
|
|
|
|
Verifies connectivity once at creation — raises if the server is unreachable.
|
|
"""
|
|
global _instance
|
|
if _instance is not None:
|
|
return _instance
|
|
|
|
driver = AsyncGraphDatabase.driver(uri, auth=(user, password))
|
|
await driver.verify_connectivity()
|
|
logger.info("Neo4j driver connected to %s (db=%s)", uri, database)
|
|
_instance = Neo4jDriver(driver=driver, database=database)
|
|
return _instance
|
|
|
|
|
|
async def close_driver() -> None:
|
|
global _instance
|
|
if _instance is None:
|
|
return
|
|
await _instance.driver.close()
|
|
_instance = None
|
|
logger.info("Neo4j driver closed")
|