""" MySQL Connector. Manages interactions with MySQL for art_relations and ppb_collections databse. """ from contextlib import contextmanager import functools from oto import response from sqlalchemy import create_engine from sqlalchemy import exc from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.sql import text from prs import config from prs.connectors import sentry def get_engine_connection(db_url): """ Create a database connection engine and the session. Args: db_url (str): The database url. Returns: db_engine: The created database engine. db_session: The created database session. """ db_engine = create_engine(db_url, poolclass=config.POOL_CLASS) db_session = sessionmaker(bind=db_engine) return db_engine, db_session ar_db_engine, ar_database_session = get_engine_connection(config.AR_DB_URL) ppb_db_engine, ppb_database_session = get_engine_connection(config.PPB_DB_URL) ppb_model = declarative_base() ar_model = declarative_base() def session_transaction(session): """ Provide transactional scope around series of operations. Taken from http://docs.sqlalchemy.org/en/latest/orm/session_basics.html. This handles rollback and closing of session, so there is no need to do that throughout the code. Args: session : The database session. """ try: yield session session.commit() except: session.rollback() raise finally: session.close() @contextmanager def ar_db_session(): """ Transactional scope around series of operations for art_relations. Usage: with ar_db_session() as session: session.execute(query) """ session = ar_database_session() return session_transaction(session) @contextmanager def ppb_db_session(): """ Transactional scope around a series of operations for ppb_collections. Usage: with ppb_db_session() as session: session.execute(query) """ session = ppb_database_session() return session_transaction(session) def wrap_db_errors(function): """Decorate the given function with logic to handle SQLAlchemy errors. If a SQLAlchemy exception is thrown, it will be caught and logged and the function will return a fatal response. Args: function (func): the function to decorate Returns: func: function decorated with error-handling logic """ @functools.wraps(function) def call_function_with_error_handling(*args, **kwargs): try: function_return = function(*args, **kwargs) except exc.SQLAlchemyError as exception: sentry.sentry_client.captureMessage(exception, stack=True) return response.create_fatal_response() return function_return return call_function_with_error_handling @wrap_db_errors def raw_sql_execute_query(connection, query, query_params): """Execute the raw SQL query for the given connection. Args: connection (object): Database session object. query (str): The raw SQL query with placeholders. query_params (dict): Query placeholder values. Returns: List: Query resultset rows. """ query = text(query) result = connection.execute(query, **query_params) return result.fetchall()