"""MySQL Connector. Manages interactions with MySQL. """ from contextlib import contextmanager import functools from oto import response from sentry_sdk import capture_exception from sqlalchemy import create_engine from sqlalchemy import exc from sqlalchemy.orm import declarative_base from sqlalchemy.orm import sessionmaker from territories import config # please don't use the following private variables directly; # use db_session _db_engine = create_engine( config.DB_CONNECTION_STRING, poolclass=config.DB_POOLCLASS ) _db_session = sessionmaker(_db_engine, expire_on_commit=False) class BaseModel(object): """Base model for territory data models.""" @property def columns(self): """Get list of column names from the underlying table. Returns: list(str): list of column names from the table schema """ return [column.name for column in self.__table__.columns] @property def columnitems(self): """Get list of columns from the underlying table. Returns: dict(column): dict of columns from the table schema """ return dict( [(column, getattr(self, column)) for column in self.columns]) def __repr__(self): """Get string representation of a model. Returns: str: string representation of a model """ return '{}({})'.format(self.__class__.__name__, self.columnitems) def to_dict(self, include_primary_key=False, primary_key='id'): """Convert model to it's JSON representation. Args: include_primary_key (bool): should the primary key column be also included in generated JSON. primary_key (str): primary key column name Returns: dict: JSON representation of the model data """ if include_primary_key: filtered = self.columnitems else: filtered = { k: v for k, v in self.columnitems.items() if k != primary_key} return filtered BaseModel = declarative_base(cls=BaseModel) @contextmanager def db_session(): """Provide a transactional scope around a 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. Usage: with db_session() as session: session.execute(query) """ session = _db_session() try: yield session session.commit() except Exception: session.rollback() raise finally: session.close() 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: capture_exception(exception) return response.create_fatal_response() return function_return return call_function_with_error_handling