"""Instant Grats Persister. Handles doing CRUD operations on the track_instant_grat and related tables. """ from oto import response from oto import status as response_code import sqlalchemy from backend.connectors import mysql from backend.constants import error from backend.constants import instant_grat_field from backend.models.instant_grats import InstantGrat from backend.utils import api as api_utils class InstantGratPersister: """Handles high level operations for Instant Grats.""" @classmethod @mysql.db_session_wrap def fetch_grats_by_list_of_tuids(cls, tuids, session, active_only=True): """Fetch all instant Grats by list of tuids. Args: tuids (list): List of track ids having Grats we are looking for. active_only (bool): The flag shows whether should fetch all the grats (includes deleted ones) or active only. Returns: response.Response """ grats = [] if tuids: query = cls._get_grats_by_tuids(tuids, active_only, session) grats = [grat.to_dict() for grat in query] if not grats: return response.create_not_found_response( error.INSTANT_GRATS_NOT_FOUND_MSG) return response.Response(grats) @classmethod @mysql.db_session_wrap def bulk_create_grats(cls, grats_data_list, session): """Bulk create new instant grats. Args: grats_data_list (list): List of grats to add. session (object): SQLAlchemy database session. Returns: response.Response: Contains status code and payload. """ for grat_data in grats_data_list: grat_keys = set(grat_data.keys()) if grat_keys.difference(instant_grat_field.CREATE_MODEL_FIELDS): superfluous_fields = grat_keys.difference( instant_grat_field.CREATE_MODEL_FIELDS) error_msg = ( error.VALIDATION_ERROR_SUPERFLUOUS_FIELD_MSG.format( superfluous_fields.pop())) return api_utils.create_validation_error_response(error_msg) try: created_grats = [] for grat_data in grats_data_list: grat = InstantGrat(**grat_data) session.add(grat) created_grats.append(grat) session.commit() except sqlalchemy.exc.IntegrityError as e: session.rollback() return api_utils.create_validation_error_response(str(e.orig)) # Convert results to array of dicts items = [grat.to_dict() for grat in created_grats] return api_utils.create_get_list_response( items, status=response_code.CREATED) @classmethod def _get_grats_by_tuids(cls, tuids, active_only, session): filters = [InstantGrat.tuid.in_(tuids)] if active_only: filters.append(InstantGrat.active == 'Y') qry = session.query(InstantGrat).filter(*filters) return qry @classmethod @mysql.db_session_wrap def delete_grats_by_track_ids(cls, track_ids, store_id=None, session=None): """Delete instant grat based on a track_id and store_id. Args: track_ids (list): List of track ids to find instant grats. store_id (int): The store primary key (optional). session (object): SQLAlchemy database session (optional) Returns: response.Response: result of deletion """ query = session.query(InstantGrat).filter( InstantGrat.tuid.in_(track_ids), InstantGrat.active == 'Y') if store_id is not None: query = query.filter(InstantGrat.store_id == store_id) grats = query.with_for_update().all() if not grats: return response.create_not_found_response( error.INSTANT_GRATS_NOT_FOUND_MSG) for grat in grats: grat.active = 'N' return response.Response({'message': 'Deleted'}) @classmethod @mysql.db_session_wrap def bulk_update_grats(cls, tuids, grats_update_data_list, session): """Update multiple grats. Args: tuids (list): List of tuids existing in a product. grats_update_data_list (list): List of dicts with grats updates. session (object): SQLAlchemy database session (optional). Returns: response.Response: Contains status code and payload """ existing_grats = cls._get_grats_by_tuids( tuids=tuids, active_only=True, session=session) grats_update_lookup = { (grat['tuid'], grat['store_id']): grat for grat in grats_update_data_list} result_grats = [] for grat in existing_grats: update_data_for_grat = grats_update_lookup.pop( (grat.tuid, grat.store_id), None) # Apply update to existing grat provided in request data. if update_data_for_grat: update_res = cls._update_grat( grat, update_data_for_grat, session=session) if not update_res: session.rollback() return update_res result_grats.append(update_res.message.to_dict()) # Delete grat which wasn't listed in request data. else: delete_res = cls.delete_grats_by_track_ids( track_ids=[grat.tuid], store_id=grat.store_id, session=session) if not delete_res: session.rollback() return delete_res # If some grats from request don't exist in DB - create new entities. if grats_update_lookup: create_result = cls.bulk_create_grats( list(grats_update_lookup.values()), session=session) if not create_result: session.rollback() return create_result result_grats.extend(create_result.message.get('items', [])) return api_utils.create_get_list_response(result_grats) @classmethod def _update_grat(cls, grat, data, session): """Apply data update to grat. Args: grat (object): InstantGrat model object data (dict): Data to apply session (object): SQLAlchemy database session. Return: response.Response: Contains status code and any error messages """ try: for key, val in data.items(): if key not in instant_grat_field.CREATE_MODEL_FIELDS: raise TypeError( error.VALIDATION_ERROR_SUPERFLUOUS_FIELD_MSG.format( key)) setattr(grat, key, val) session.add(grat) return response.Response(grat) except (TypeError, ValueError) as e: return api_utils.create_validation_error_response(str(e)) except sqlalchemy.exc.IntegrityError as e: return api_utils.create_validation_error_response(str(e.orig))