"""Neo4j connector Client.""" from contextlib import ContextDecorator from flask import g from neo4j import GraphDatabase from neo4j.api import READ_ACCESS from neo4j.api import WRITE_ACCESS from neo4j.exceptions import ServiceUnavailable from neo4j.exceptions import SessionExpired from abacus_contract.config import Config neo4j_driver = None def init_driver(url, username, password): """Initialize neo4j database driver as application level singleton. Args: url (str): location of neo4j server to connect username (str): identifier for neo4j server auth password (str): secret for neo4j server auth Returns: None Raises: DriverAlreadyInitiated: no driver initiated in application context """ global neo4j_driver if neo4j_driver: raise DriverAlreadyInitiated() neo4j_driver = GraphDatabase.driver(url, auth=(username, password)) def get_session(): """Return neo4j session for request to use. Args: None Returns: neo4j.work.simple.Session: request context grouping of neo4j queries and transactions Raises: SessionNotCreated: no session has been created in request context """ try: return g.neo4j_session except AttributeError: raise SessionNotCreated() def set_session(session): """Set neo4j session for request. Args: session (neo4j.work.simple.Session): request context grouping of neo4j queries and transactions Returns: None """ g.neo4j_session = session def create_session(access_mode, retries=Config.NEO4J_CREATE_SESSION_RETRIES): """Create neo4j session with retries. Args: access_mode (str): read or write mode retries (int): attempts left on failure Returns: neo4j.work.simple.Session """ try: if not neo4j_driver: raise DriverNotInitialized() # checkout connection from driver pool session = neo4j_driver.session(default_access_mode=access_mode) # run simple query results = session.run('return 1') # iterate over "results" to verify [x for x in results] return session except (SessionExpired, ServiceUnavailable) as e: if retries > 0: return create_session(access_mode, retries - 1) raise e class DriverNotInitialized(Exception): """Exception during session creation without driver.""" pass class DriverAlreadyInitiated(Exception): """Exception for driver re-instantiation.""" pass class SessionNotCreated(Exception): """Exception during session reference before session open.""" pass class SessionAlreadyOpen(Exception): """Exception when attempting to open session again.""" pass class Neo4jSession(ContextDecorator): """Decorator for neo4j connection and transaction handling.""" def __init__(self, transaction=False): """Create decorator. Args: transaction (bool): Open transaction on __enter__. Returns: None """ self.transaction = transaction self.open_session = None self.open_transaction = None def __enter__(self): """Create neo4j session.""" if not neo4j_driver: init_driver( Config.NEO4J_URL, Config.NEO4J_USERNAME, Config.NEO4J_PASSWORD ) try: get_session() except SessionNotCreated: access_mode = WRITE_ACCESS\ if self.transaction else READ_ACCESS self.open_session = create_session(access_mode) if self.transaction: self.open_transaction = self.open_session.begin_transaction() set_session(self.open_transaction) else: set_session(self.open_session) else: raise SessionAlreadyOpen() def __exit__(self, exc_type, exc_val, exc_tb): """Finalize transaction (if open) and close neo4j session. Args: exc_type (class): type of exception raised exc_val (Exception): instance of exception raised exc_tb (Traceback): traceback of exception raised Returns: None """ try: if self.open_transaction: if not exc_type: # will throw Transaction error if you manually # close/commit the transaction. self.open_transaction.commit() else: self.open_transaction.rollback() finally: self.open_session.close() set_session(None)