"""mysql connector.""" import functools import time from flask import g from oto import response from oto import status from pricing import config from pricing.constants import error from sentry_sdk import capture_exception as sentry_capture_exception from sqlalchemy import create_engine from sqlalchemy import event from sqlalchemy.engine import Engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker if config.ENVIRONMENT == config.TEST_ENVIRONMENT: # For the test environment the database used is an in-memory sqlite db. # This db gets dropped immediately after the execution of the tests. _db_engine = create_engine('sqlite://') else: _db_engine = create_engine(config.DB_URL, poolclass=config.POOL) BaseModel = declarative_base() # A session() instance establishes all conversations with the database # and represents a "staging zone" for all the objects loaded into the # database session object. Any change made against the objects in the # session won't be persisted into the database until you call # session.commit(). If you're not happy about the changes, you can # revert all of them back to the last commit by calling # session.rollback() # # Description taken from: # pythoncentral.io/introductory-tutorial-python-sqlalchemy/ _db_session = sessionmaker(bind=_db_engine) def autosession(capture_exception=True): """Automatically creates a session that can be used within a method. Args: capture_exception (bool): capture exceptions to sentry. Returns: callable: the encapsulated wrapper. """ return functools.partial( autosession_decorate, capture_exception=capture_exception) def autosession_decorate(function, capture_exception=True): """Decorate the function with a session context. Args: function (callable): the function to decorate. capture_exception (bool): capture exceptions to sentry. Returns: callable: decorated method. """ return functools.partial( autosession_context, function=function, capture_exception=capture_exception) def autosession_context( *args, function=None, capture_exception=True, **kwargs): """Create a session context. Args: args (tuple): tuple of arguments to pass down to the function. function (callable): the method to call after creating the session. capture_exception (bool): capture exceptions to sentry. kwargs (dict): dictionary of arguments to provide to the function. Returns: mixed: the result of the operation. In case of an exception, the error is logged into sentry and an error response is returned """ session = _db_session() try: kwargs.update(session=session) data = function(*args, **kwargs) session.commit() return data except Exception as exception: session.rollback() if capture_exception: sentry_capture_exception(exception) return response.create_error_response( code=error.ERROR_CODE_MYSQL, message=error.ERROR_MESSAGE_DB_ISSUE, status=status.INTERNAL_ERROR) finally: session.close() @event.listens_for(Engine, 'before_cursor_execute') def before_cursor_execute( conn, cursor, statement, parameters, context, executemany): """Log SQL query before it starts executing. Args: statement (string): the SQL statement to execute. """ if config.ENVIRONMENT == config.TEST_ENVIRONMENT: return if getattr(g, 'performance_profile', None) is None: g.performance_profile = {} g.performance_profile[statement] = { 'start_time': time.time() } @event.listens_for(Engine, 'after_cursor_execute') def after_cursor_execute( conn, cursor, statement, parameters, context, executemany): """Log SQL query after it finishes executing. Args: statement (string): the SQL statement to execute. """ if config.ENVIRONMENT == config.TEST_ENVIRONMENT: return g.performance_profile[statement]['duration'] = \ time.time() - g.performance_profile[statement]['start_time']