import collections import logging import itertools import requests from django.conf import settings from core_images.cache import ServiceCacheMixin from core_images.consts import ( SMALL_RESOLUTION, MEDIUM_RESOLUTION, LARGE_RESOLUTION, ) from core_images.cached_either import Right, Left from core_images.consts import NO_IMAGE_CACHE_VALUE from core_images.image_types import is_placeholder from core_images.services.internal_data.service import ( get_internal_data_service, ) logger = logging.getLogger(__name__) class SCUBAService(ServiceCacheMixin): NO_IMAGE_MSG = "No image available" ERROR_MSG = "Error requesting artist image" RESOLUTION_MAPPING = { SMALL_RESOLUTION: "thumbUrl", MEDIUM_RESOLUTION: "fullUrl", LARGE_RESOLUTION: "fullUrl", } def __init__(self, api_host="", api_key="", internal_data_service=None): self._base_url = f"https://{api_host}/v2" self._api_key = api_key self._internal_data_service = internal_data_service def artist_image( self, gras_artist_id, resolution, with_placeholders=False, ): cache_key = [ "artist_image", gras_artist_id, resolution, self.placeholders_key(with_placeholders), ] url = self.cache_get(cache_key) if url == NO_IMAGE_CACHE_VALUE: return Left(self.NO_IMAGE_MSG, True) elif url: return Right(url, True) res = self.get( "gras/artist-images", params={"participantNo": gras_artist_id} ) if not res.ok: if res.status_code == 404: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) else: logger.error(f"{self.ERROR_MSG}: {res.text}") return Left(self.ERROR_MSG) resolution_size = self.RESOLUTION_MAPPING.get(resolution, "fullUrl") try: url = res.json()["data"][0][resolution_size] except Exception: # nosec url = None if not url: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) if not with_placeholders and is_placeholder(url): self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) self.cache_set(cache_key, url) return Right(url) def invalidate_artist_cache(self, gras_artist_id): placeholders = (False, True) resolutions = set(self.RESOLUTION_MAPPING.keys()) combinations = list(itertools.product(placeholders, resolutions)) for placeholder, resolution in combinations: cache_key = [ "artist_image", gras_artist_id, resolution, self.placeholders_key(placeholder), ] self.cache_delete(cache_key) def artist_images( self, gras_artist_ids, resolution, with_placeholders=False, ): gras_artist_ids_list = [ item.strip() for item in gras_artist_ids.split(",") ] cache_key = [ "artist_images", resolution, self.key_for_iterable(gras_artist_ids_list), self.placeholders_key(with_placeholders), ] urls = self.cache_get(cache_key) if urls == NO_IMAGE_CACHE_VALUE: return Left(self.NO_IMAGE_MSG, True) elif urls: return Right(urls, True) res = self.get( "gras/artist-images-by-grasids", params={"participantNos": gras_artist_ids}, ) if not res.ok: if res.status_code == 404: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) else: logger.error(f"{self.ERROR_MSG}: {res.text}") return Left(self.ERROR_MSG) resolution_size = self.RESOLUTION_MAPPING.get(resolution, "fullUrl") try: urls = { item["grasParticipantNo"]: item[resolution_size] for item in res.json()["data"] } except Exception: # nosec urls = None fallbacks = self._internal_data_service.get_fallback_map().value missed_ids = set() current_fallbacks = { k: v for k, v in fallbacks.items() if k in gras_artist_ids_list } if urls: urls.update( {k: v for k, v in current_fallbacks.items() if k not in urls} ) missed_ids = set(gras_artist_ids_list) - set(urls.keys()) elif current_fallbacks: urls = current_fallbacks if urls and not with_placeholders: missed_ids.update(k for k, v in urls.items() if is_placeholder(v)) urls = {k: v for k, v in urls.items() if not is_placeholder(v)} if not urls: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) self.cache_set(cache_key, urls) # caching data for same single item method self.cache_save_many( "artist_image", urls, missed_ids, resolution, with_placeholders ) return Right(urls) def product_image( self, gras_product_id, resolution, with_placeholders=False ): cache_key = [ "product_image", gras_product_id, resolution, self.placeholders_key(with_placeholders), ] url = self.cache_get(cache_key) if url == NO_IMAGE_CACHE_VALUE: return Left(self.NO_IMAGE_MSG, True) elif url: return Right(url, True) res = self.get( "gras/product-images", params={"productNo": gras_product_id} ) if not res.ok: if res.status_code == 404: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) else: logger.error(f"{self.ERROR_MSG}: {res.text}") return Left(self.ERROR_MSG) resolution_size = self.RESOLUTION_MAPPING.get(resolution, "fullUrl") try: url = res.json()["data"][0][resolution_size] except Exception: # nosec url = None if not url: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) if not with_placeholders and is_placeholder(url): self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) self.cache_set(cache_key, url) return Right(url) def product_images( self, gras_product_ids, resolution, with_placeholders=False ): gras_product_ids_list = [ item.strip() for item in gras_product_ids.split(",") ] cache_key = [ "product_images", resolution, self.key_for_iterable(gras_product_ids_list), self.placeholders_key(with_placeholders), ] urls = self.cache_get(cache_key) if urls == NO_IMAGE_CACHE_VALUE: return Left(self.NO_IMAGE_MSG, True) elif urls: return Right(urls, True) res = self.get( "gras/product-images-by-grasids", params={"productNos": gras_product_ids}, ) if not res.ok: if res.status_code == 404: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) else: logger.error(f"{self.ERROR_MSG}: {res.text}") return Left(self.ERROR_MSG) resolution_size = self.RESOLUTION_MAPPING.get(resolution, "fullUrl") try: urls = { item["grasProductNo"]: item[resolution_size] for item in res.json()["data"] } missed_ids = set(gras_product_ids_list) - set(urls.keys()) except Exception: # nosec urls = None if urls and not with_placeholders: missed_ids.update(k for k, v in urls.items() if is_placeholder(v)) urls = {k: v for k, v in urls.items() if not is_placeholder(v)} if not urls: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) self.cache_set(cache_key, urls) # caching data for same single item method self.cache_save_many( "product_image", urls, missed_ids, resolution, with_placeholders ) return Right(urls) def product_track_image(self, gras_product_id, gras_track_id): cache_key = ["product_track_image", gras_product_id, gras_track_id] url = self.cache_get(cache_key) if url == NO_IMAGE_CACHE_VALUE: return Left(self.NO_IMAGE_MSG, True) elif url: return Right(url, True) res = self.get( "gras/product-track-images", params={"productNo": gras_product_id, "trackNo": gras_track_id}, ) if not res.ok: if res.status_code == 404: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) else: logger.error(f"{self.ERROR_MSG}: {res.text}") return Left(self.ERROR_MSG) try: url = res.json()["data"][0]["fullUrl"] except Exception: # nosec url = None if not url: self.cache_set(cache_key, NO_IMAGE_CACHE_VALUE) return Left(self.NO_IMAGE_MSG) self.cache_set(cache_key, url) return Right(url) def get(self, url, params, **kwargs): return requests.get( f"{self._base_url}/{url}", params, headers={"x-api-key": self._api_key}, **kwargs, ) def cache_save_many( self, key, urls, missed_ids, resolution, with_placeholders ): self.cache_set_many( collections.ChainMap( { ( key, k, resolution, self.placeholders_key(with_placeholders), ): v for k, v in urls.items() }, { ( key, k, resolution, self.placeholders_key(with_placeholders), ): NO_IMAGE_CACHE_VALUE for k in missed_ids }, ) ) @staticmethod def placeholders_key(with_placeholders): return ( "with_placeholders" if with_placeholders else "without_placeholders" ) @staticmethod def key_for_iterable(iterable): return str(hash(tuple(iterable))) def get_scuba_service(): return SCUBAService( settings.SCUBA_API_HOST, settings.SCUBA_API_KEY, get_internal_data_service(), )