"""Store outages model.""" from typing import Dict, List from ddtrace import tracer from sound_recordings import config, features from sound_recordings.config import FEED_IDS from sound_recordings.connectors import snowflake from sound_recordings.constants import cache as constants from sound_recordings.utils.cache import cache_in_redis SQLLoader = snowflake.SQLLoader(__file__) FIELD_STREAMS = "streams" FIELD_SKIPS_SAVES = "skips_saves" FIELD_STORE_ID = "store_id" FIELD_FEED_ID = "feed_id" FIELD_TYPES = "types" STORE_OUTAGE_FIELDS = [FIELD_FEED_ID, FIELD_STORE_ID, FIELD_STREAMS, FIELD_SKIPS_SAVES] CACHE_TTL = constants.SECONDS_PER_THIRTY_MINUTES @tracer.wrap(name="get_feed_outages") @cache_in_redis(ttl=CACHE_TTL, key=constants.REDIS_KEY_FEED_OUTAGES) def get_feed_outages() -> Dict[int, dict]: """Get feeds which are currently experiencing outages. Returns: map of: feed id -> dict - containing feed outage information """ sql = SQLLoader.load_query("store_outages") records = snowflake.fetchall(sql, {"feed_ids": FEED_IDS}) return { r[0]: { "feed_id": r[0], "store_id": r[1], "has_streams_outage": r[2], "has_skips_saves_outage": r[3], } for r in records if r[2] or r[3] } def _get_outage_types(feed_outage: dict) -> List[str]: types = [] if feed_outage.get("has_streams_outage"): types.append(FIELD_STREAMS) if feed_outage.get("has_skips_saves_outage"): types.append(FIELD_SKIPS_SAVES) return types def get_feeds_with_outages() -> Dict[int, dict]: """Return config.FEEDS with appended outage data.""" available_feeds = dict(config.FEEDS) feed_outages = get_feed_outages() feeds = {} for feed_id, feed in available_feeds.items(): feed_outage = feed_outages.get(feed_id) if feed_outage: feeds[feed_id] = { **feed, "error": { "types": _get_outage_types(feed_outage), "code": "unreliable", }, } else: feeds[feed_id] = {**feed} return feeds @tracer.wrap(name="get_outage_stores") @cache_in_redis(ttl=constants.ONE_HOUR, key=constants.REDIS_KEY_STORE_OUTAGES) def get_outage_stores(): """Get stores which are currently experiencing outages. Returns: list: List of unreliable stores. """ sql = SQLLoader.load_query("store_outages") records = snowflake.fetchall(sql, {"feed_ids": FEED_IDS}) def _map_to_store(record): store = dict(zip(STORE_OUTAGE_FIELDS, record)) types = [] if store.get(FIELD_STREAMS): types.append(FIELD_STREAMS) if store.get(FIELD_SKIPS_SAVES): types.append(FIELD_SKIPS_SAVES) if not types: return None return { FIELD_STORE_ID: store.get(FIELD_STORE_ID), FIELD_FEED_ID: store.get(FIELD_FEED_ID), FIELD_TYPES: types, } return [store for store in map(_map_to_store, records) if store is not None] @tracer.wrap(name="get_max_avaialble_date") @cache_in_redis(ttl=CACHE_TTL, key=constants.REDIS_KEY_MAX_AVAILABLE_DATE) def get_max_available_date(): """Get max available date for stores. Returns: Datetime date object """ sql = SQLLoader.load_query("store_high_water_mark") max_available_date = snowflake.fetchall(sql, {"feed_ids": FEED_IDS}) return max_available_date[0][0].strftime("%Y-%m-%d") @tracer.wrap(name="get_downloads_max_available_date") @cache_in_redis(ttl=CACHE_TTL, key=constants.REDIS_KEY_DOWNLOADS_MAX_AVAILABLE_DATE) def get_downloads_max_available_date(): """Get max available date for stores. Returns: Datetime date object """ sql = SQLLoader.load_query("downloads_stores_high_water_mark") max_available_date = snowflake.fetchall(sql, {"feed_ids": FEED_IDS}) return max_available_date[0][0].strftime("%Y-%m-%d")