"""Logic related to retrieval and processing of information about countries.""" import json import os from oto import response from oto import status from owsrequest import request from sentry_sdk import capture_exception from availability import config from availability.connectors import loggly from availability.connectors import redis from availability.constants import endpoints from availability.constants import error from availability.constants import header from availability.constants import models from availability.constants import service_name from availability.constants import stores logger = loggly.get_current_logger() # Currently our datasource for countries is just json file in project root. def _get_all_countries(): with open('countries.json') as f: countries = json.load(f) return {int(k): v for k, v in countries.items()} _all_countries = _get_all_countries() def get_all_countries(): """Get countries from datasource. Returns: dict: Dict of countries where key is ID and value is country code. Key can be considered as country importance. The lower is key - the more important is country. """ return _all_countries def get_spotify_unlaunched_markets(): """Get ISO 3166-1 alpha-2 country codes where Spotify not launched. Returns: list: ISO 3166-1 alpha-2 country codes. """ markets_filepath = os.path.join( config.BASE_DIR, '..', 'spotify_unlaunched_markets.json') with open(markets_filepath) as f: unlaunched_markets = list(json.load(f).values()) return unlaunched_markets def get_ordered_countries_list(countries): """Get list of country codes sorted by country ID. Args: countries (dict): dict of countries where key is country importance and value is country code. Returns: list: List of countries ordered by priority. Lower priority comes first. """ return [countries[k] for k in sorted(countries.keys())] all_countries_ordered = get_ordered_countries_list(_all_countries) def get_excluded_countries(upc, correlation_id): """Get countries that should be excluded from polling for given release. Args: upc (str): UPC of release. correlation_id (str): Correlation-Id header of request. Returns: oto.response.Response: .message with list of countries on success, .errors on failure. """ logging_context = dict( upc=upc, correlation_id=correlation_id) logger.info( 'Getting excluded countries', resources=logging_context) request_url = endpoints.CARVEOUTS_ENDPOINT_TEMPLATE.format(upc) logging_context['request_url'] = request_url logger.info( 'Checking excluded countries in a cached result from ows-carveouts', resources=logging_context) try: countries = redis.redis_client.get(request_url) except Exception as e: logging_context['error'] = e logger.error( 'Error while getting excluded countries from cache, ' 'trying ows-carveouts', resources=logging_context) countries = None if countries is not None: return response.Response(json.loads(countries)) request_headers = {header.CORRELATION_ID: correlation_id} logging_context.update(request_headers) logger.info( 'Making a request to ows-carveouts', resources=logging_context) try: carveouts_response = request.get( service_name.OWS_CARVEOUTS, request_url, headers=request_headers) except Exception as e: capture_exception(e) return response.create_fatal_response() logging_context['carveouts_response_content'] = carveouts_response.content if not carveouts_response.status_code == status.OK: logger.error( 'Request to ows-carveouts has failed', resources=logging_context) return response.create_error_response( code=error.ERROR_CODE_GENERIC, message=carveouts_response.text, status=carveouts_response.status_code) try: countries_dict = carveouts_response.json() if not isinstance(countries_dict, dict): # when there are no countries to exclude, ows-carveouts returns [] # which might cause error later on countries_dict = {} countries = list(countries_dict.values()) if not all(isinstance(country, str) for country in countries): raise ValueError('Invalid data received.') except ValueError as e: logger.error( 'Failed to parse ows-carveouts response', resources=logging_context) capture_exception(e) return response.create_fatal_response() try: redis.redis_client.set( request_url, value=json.dumps(countries), ex=config.CARVEOUTS_RESPONSE_CACHE_TTL) except Exception: logger.error( 'Unable to set ows-carveouts response in cache', resources=logging_context) logging_context['excluded_countries'] = countries logger.info( 'Returning the list of excluded countries', resources=logging_context) return response.Response(countries) def get_expected_countries(upc, released_countries, store_id, correlation_id): """Return countries that are expected but not live yet. Args: upc (str): UPC of release. released_countries (str): Countries where product is already released. This is string of comma-separated country codes as it is stored in DB. store_id (int): Id of specific store. correlation_id (str): Correlation-Id header of request. Returns: response.Response: Response object where message attribute contains list of countries where release is expected. List is ordered by preference (high to low). In case of failure, error attribute contains error description. """ logging_context = dict( upc=upc, store_id=store_id, correlation_id=correlation_id) logger.info( 'Getting expected countries', resources=logging_context) # TODO: try to get rid of such checks if we ever need to add another # similar condition for any future store. # One possibility would be to register per-store filter functions for each # store. if store_id == stores.STORE_ID_SPOTIFY: # Get excluded countries for Spotify. excluded_response = get_excluded_countries(upc, correlation_id) if not excluded_response: logging_context['status'] = excluded_response.status logging_context['errors'] = excluded_response.errors logger.error( 'Got an error response from the get_excluded_countries()', resources=logging_context) return excluded_response excluded_countries = set(excluded_response.message) unlaunched_countries = set(get_spotify_unlaunched_markets()) excluded_countries = excluded_countries | unlaunched_countries else: # Not able to exclude for any other store. excluded_countries = set() released_countries = set( released_countries.split(models.COUNTRIES_SEPARATOR)) countries_to_poll = [c for c in all_countries_ordered if c not in released_countries | excluded_countries] logging_context.update(dict( excluded_countries=excluded_countries, released_countries=released_countries, countries_to_poll=countries_to_poll)) logger.info('Got countries to poll', resources=logging_context) return response.Response(countries_to_poll)