def test_online_query_product_single_page_results(self): cisco_eox_api = CiscoEoxApi() cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.get_eox_records() == [] assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 jresult = cisco_eox_api.query_product(self.TEST_QUERY, 1) assert "EOXRecord" in jresult assert "PaginationResponseRecord" in jresult assert len(jresult["EOXRecord"]) == 1 assert cisco_eox_api.get_eox_records() == jresult["EOXRecord"] assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 1 assert cisco_eox_api.get_page_record_count() == 1 assert cisco_eox_api.has_api_error() is False assert cisco_eox_api.get_api_error_message() == "no error" assert cisco_eox_api.get_error_description( jresult["EOXRecord"][0]) == "" assert cisco_eox_api.get_error_description( jresult["EOXRecord"][0]) == "" assert self.EXPECTED_VALID_TEST_QUERY_RESPONSE == jresult assert cisco_eox_api.get_eox_records( ) == self.EXPECTED_VALID_TEST_QUERY_RESPONSE["EOXRecord"]
def test_offline_query_product_no_results(self, monkeypatch): class MockSession: def get(self, *args, **kwargs): r = Response() r.status_code = 200 with open("app/ciscoeox/tests/data/cisco_eox_no_result_response.json") as f: r._content = f.read().encode("utf-8") return r monkeypatch.setattr(requests, "Session", MockSession) cisco_eox_api = CiscoEoxApi() monkeypatch.setattr(cisco_eox_api, "create_temporary_access_token", lambda force_new_token=True: mock_access_token_generation()) with pytest.raises(CiscoApiCallFailed): cisco_eox_api.query_product("NOTHING", 1) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 _ = cisco_eox_api.query_product("NOTHING", 1) assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 assert cisco_eox_api.has_api_error() is True assert cisco_eox_api.get_api_error_message() == "EOX information does not exist for the following product " \ "ID(s): NOTHING (SSA_ERR_026)"
def test_offline_query_product_no_results(self, monkeypatch): def mock_response(): r = Response() r.status_code = 200 with open("app/ciscoeox/tests/data/cisco_eox_no_result_response.json") as f: r._content = f.read().encode("utf-8") return r monkeypatch.setattr(requests, "get", lambda x, headers: mock_response()) cisco_eox_api = CiscoEoxApi() monkeypatch.setattr(cisco_eox_api, "create_temporary_access_token", lambda force_new_token=True: mock_access_token_generation()) with pytest.raises(CiscoApiCallFailed): cisco_eox_api.query_product("NOTHING", 1) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 _ = cisco_eox_api.query_product("NOTHING", 1) assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 assert cisco_eox_api.has_api_error() is True assert cisco_eox_api.get_api_error_message() == "EOX information does not exist for the following product " \ "ID(s): NOTHING (SSA_ERR_026)"
def test_offline_query_product_multiple_page_results(self, monkeypatch): class MockSessionPageOne: _first_call = False def get(self, *args, **kwargs): r = Response() r.status_code = 200 if not self._first_call: with open("app/ciscoeox/tests/data/cisco_eox_response_page_1_of_2.json") as f: r._content = f.read().encode("utf-8") self._first_call = True else: with open("app/ciscoeox/tests/data/cisco_eox_response_page_2_of_2.json") as f: r._content = f.read().encode("utf-8") return r monkeypatch.setattr(requests, "Session", MockSessionPageOne) cisco_eox_api = CiscoEoxApi() monkeypatch.setattr(cisco_eox_api, "create_temporary_access_token", lambda force_new_token=True: mock_access_token_generation()) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.get_eox_records() == [] assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 jresult = cisco_eox_api.query_product(self.TEST_QUERY, 1) assert "EOXRecord" in jresult assert "PaginationResponseRecord" in jresult assert len(jresult["EOXRecord"]) == 2 assert cisco_eox_api.get_eox_records() == jresult["EOXRecord"] assert cisco_eox_api.amount_of_pages() == 2 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 3 assert cisco_eox_api.get_page_record_count() == 2 assert cisco_eox_api.has_api_error() is False assert cisco_eox_api.get_api_error_message() == "no error" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == "" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == "" jresult = cisco_eox_api.query_product(self.TEST_QUERY, 2) assert "EOXRecord" in jresult assert "PaginationResponseRecord" in jresult assert len(jresult["EOXRecord"]) == 1 assert cisco_eox_api.get_eox_records() == jresult["EOXRecord"] assert cisco_eox_api.amount_of_pages() == 2 assert cisco_eox_api.get_current_page() == 2 assert cisco_eox_api.amount_of_total_records() == 3 assert cisco_eox_api.get_page_record_count() == 1 assert cisco_eox_api.has_api_error() is False assert cisco_eox_api.get_api_error_message() == "no error" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == "" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == ""
def test_offline_query_product_single_page_results(self, monkeypatch): def mock_response(): r = Response() r.status_code = 200 with open("app/ciscoeox/tests/data/cisco_eox_response_page_1_of_1.json") as f: r._content = f.read().encode("utf-8") return r monkeypatch.setattr(requests, "get", lambda x, headers: mock_response()) cisco_eox_api = CiscoEoxApi() monkeypatch.setattr(cisco_eox_api, "create_temporary_access_token", lambda force_new_token=True: mock_access_token_generation()) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.get_eox_records() == [] assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 jresult = cisco_eox_api.query_product(self.TEST_QUERY, 1) assert "EOXRecord" in jresult assert "PaginationResponseRecord" in jresult assert len(jresult["EOXRecord"]) == 3 assert cisco_eox_api.get_eox_records() == jresult["EOXRecord"] assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 3 assert cisco_eox_api.get_page_record_count() == 3 assert cisco_eox_api.has_api_error() is False assert cisco_eox_api.get_api_error_message() == "no error" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == "" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == ""
def test_online_query_product_single_page_results(self): cisco_eox_api = CiscoEoxApi() cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.get_eox_records() == [] assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 jresult = cisco_eox_api.query_product(self.TEST_QUERY, 1) assert "EOXRecord" in jresult assert "PaginationResponseRecord" in jresult assert len(jresult["EOXRecord"]) == 1 assert cisco_eox_api.get_eox_records() == jresult["EOXRecord"] assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 1 assert cisco_eox_api.get_page_record_count() == 1 assert cisco_eox_api.has_api_error() is False assert cisco_eox_api.get_api_error_message() == "no error" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == "" assert cisco_eox_api.get_error_description(jresult["EOXRecord"][0]) == "" assert self.EXPECTED_VALID_TEST_QUERY_RESPONSE == jresult assert cisco_eox_api.get_eox_records() == self.EXPECTED_VALID_TEST_QUERY_RESPONSE["EOXRecord"]
def test_offline_query_product_single_page_results(self, monkeypatch): def mock_response(): r = Response() r.status_code = 200 with open( "app/ciscoeox/tests/data/cisco_eox_response_page_1_of_1.json" ) as f: r._content = f.read().encode("utf-8") return r monkeypatch.setattr(requests, "get", lambda x, headers: mock_response()) cisco_eox_api = CiscoEoxApi() monkeypatch.setattr( cisco_eox_api, "create_temporary_access_token", lambda force_new_token=True: mock_access_token_generation()) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.get_eox_records() == [] assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 jresult = cisco_eox_api.query_product(self.TEST_QUERY, 1) assert "EOXRecord" in jresult assert "PaginationResponseRecord" in jresult assert len(jresult["EOXRecord"]) == 3 assert cisco_eox_api.get_eox_records() == jresult["EOXRecord"] assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 3 assert cisco_eox_api.get_page_record_count() == 3 assert cisco_eox_api.has_api_error() is False assert cisco_eox_api.get_api_error_message() == "no error" assert cisco_eox_api.get_error_description( jresult["EOXRecord"][0]) == "" assert cisco_eox_api.get_error_description( jresult["EOXRecord"][0]) == ""
def get_raw_api_data(api_query): """ returns all EoX records for a specific query (from all pages) :param api_query: single query that is send to the Cisco EoX API :raises CiscoApiCallFailed: exception raised if Cisco EoX API call failed :return: list that contains all EoX records from the Cisco EoX API """ if type(api_query) is not str: raise ValueError("api_query must be a string value") # load application settings and check, that the API is enabled app_settings = AppSettings() if not app_settings.is_cisco_api_enabled(): msg = "Cisco API access not enabled" logger.warning(msg) raise CiscoApiCallFailed(msg) # start Cisco EoX API query logger.info("send query to Cisco EoX database: %s" % api_query) eoxapi = CiscoEoxApi() eoxapi.load_client_credentials() results = [] try: current_page = 1 result_pages = 999 while current_page <= result_pages: if current_page == 1: logger.info("Executing API query '%s' on first page" % api_query) else: logger.info("Executing API query '%s' on page '%d" % (api_query, current_page)) # will raise a CiscoApiCallFailed exception on error eoxapi.query_product(product_id=api_query, page=current_page) result_pages = eoxapi.amount_of_pages() if eoxapi.get_page_record_count() > 0: results.extend(eoxapi.get_eox_records()) current_page += 1 except ConnectionFailedException: logger.error("Query failed, server not reachable: %s" % api_query, exc_info=True) raise except CiscoApiCallFailed: logger.fatal("Query failed: %s" % api_query, exc_info=True) raise return results
def test_online_query_year(self): cisco_eox_api = CiscoEoxApi() with pytest.raises(CiscoApiCallFailed): cisco_eox_api.query_year(self.TEST_YEAR, 1) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 _ = cisco_eox_api.query_year(self.TEST_YEAR, 1) assert cisco_eox_api.amount_of_pages() == 14 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() >= 13203 assert cisco_eox_api.get_page_record_count() == 1000 assert cisco_eox_api.has_api_error() is False
def test_online_query_year(self): cisco_eox_api = CiscoEoxApi() with pytest.raises(CiscoApiCallFailed): cisco_eox_api.query_year(self.TEST_YEAR, 1) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 _ = cisco_eox_api.query_year(self.TEST_YEAR, 1) # the response for the year is not stable over time, so just check that something was provided assert cisco_eox_api.amount_of_pages() > 2 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() >= 1001 assert cisco_eox_api.get_page_record_count() == 1000 assert cisco_eox_api.has_api_error() is False
def test_online_query_product_no_results(self): cisco_eox_api = CiscoEoxApi() with pytest.raises(CiscoApiCallFailed): cisco_eox_api.query_product("NOTHING", 1) cisco_eox_api.load_client_credentials() cisco_eox_api.create_temporary_access_token() assert cisco_eox_api.amount_of_pages() == 0 assert cisco_eox_api.get_current_page() == 0 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 _ = cisco_eox_api.query_product("NOTHING", 1) assert cisco_eox_api.amount_of_pages() == 1 assert cisco_eox_api.get_current_page() == 1 assert cisco_eox_api.amount_of_total_records() == 0 assert cisco_eox_api.get_page_record_count() == 0 assert cisco_eox_api.has_api_error() is True assert cisco_eox_api.get_api_error_message() == "EOX information does not exist for the following product " \ "ID(s): NOTHING (SSA_ERR_026)"
def update_cisco_eox_database(api_query): """ synchronizes the local database with the Cisco EoX API using the specified query :param api_query: single query that is send to the Cisco EoX API :raises CiscoApiCallFailed: exception raised if Cisco EoX API call failed :return: list of dictionary that describe the updates to the database """ if type(api_query) is not str: raise ValueError("api_query must be a string value") # load application settings and check, that the API is enabled app_settings = AppSettings() if not app_settings.is_cisco_api_enabled(): msg = "Cisco API access not enabled" logger.warn(msg) raise CiscoApiCallFailed(msg) blacklist_raw_string = app_settings.get_product_blacklist_regex() create_missing = app_settings.is_auto_create_new_products() # clean blacklist string and remove empty statements # (split lines, if any and split the string by semicolon) blacklist = [] for e in [e.split(";") for e in blacklist_raw_string.splitlines()]: blacklist += e blacklist = [e for e in blacklist if e != ""] # start Cisco EoX API query logger.info("Query EoX database: %s" % api_query) eoxapi = CiscoEoxApi() eoxapi.load_client_credentials() results = [] try: max_pages = 999 current_page = 1 result_pages = 0 while current_page <= max_pages: logger.info("Executing API query '%s' on page '%d" % (api_query, current_page)) # will raise a CiscoApiCallFailed exception on error eoxapi.query_product(product_id=api_query, page=current_page) if current_page == 1: result_pages = eoxapi.amount_of_pages() logger.info("Initial query returns %d page(s)" % result_pages) records = eoxapi.get_eox_records() # check that the query has valid results if eoxapi.get_page_record_count() > 0: # processing records for record in records: result_record = {} pid = record["EOLProductID"] result_record["PID"] = pid result_record["created"] = False result_record["updated"] = False result_record["message"] = None logger.info("processing product '%s'..." % pid) pid_in_database = product_id_in_database(pid) # check if the product ID is blacklisted by a regular expression pid_blacklisted = False for regex in blacklist: try: if re.search(regex, pid, re.I): pid_blacklisted = True break except: # silently ignore the issue, invalid regular expressions are handled by the settings form logger.info("invalid regular expression: %s" % regex) # ignore if the product id is not in the database if pid_blacklisted and not pid_in_database: logger.info("Product '%s' blacklisted... no further processing" % pid) result_record.update({"blacklist": True}) else: res = {} try: res = update_local_db_based_on_record(record, create_missing) except ValidationError: logger.error( "invalid data received from Cisco API, cannot save data object for '%s'" % pid, exc_info=True, ) result_record["message"] = "invalid data received from Cisco EoX API, import incomplete" finally: res["blacklist"] = False result_record.update(res) results.append(result_record) if current_page == result_pages: break else: current_page += 1 # filter empty result strings if len(results) == 0: results = [ { "PID": None, "blacklist": False, "updated": False, "created": False, "message": "No product update required", } ] except ConnectionFailedException: logger.error("Query failed, server not reachable: %s" % api_query, exc_info=True) raise except CiscoApiCallFailed: logger.fatal("Query failed: %s" % api_query, exc_info=True) raise return results
def get_raw_api_data(api_query=None, year=None): """ returns all EoX records for a specific query (from all pages) :param api_query: single query that is send to the Cisco EoX API :param year: get all EoX data that are announced in a specific year :raises CiscoApiCallFailed: exception raised if Cisco EoX API call failed :return: list that contains all EoX records from the Cisco EoX API """ if api_query is None and year is None: raise ValueError("either year or the api_query must be provided") if api_query: if type(api_query) is not str: raise ValueError("api_query must be a string value") if year: if type(year) is not int: raise ValueError("year must be an integer value") # load application settings and check, that the API is enabled app_settings = AppSettings() if not app_settings.is_cisco_api_enabled(): msg = "Cisco API access not enabled" logger.warning(msg) raise CiscoApiCallFailed(msg) # start Cisco EoX API query logger.info("send query to Cisco EoX database: %s" % api_query) eoxapi = CiscoEoxApi() eoxapi.load_client_credentials() results = [] try: current_page = 1 result_pages = 999 while current_page <= result_pages: logger.info("Executing API query %s on page '%d" % ('%s' % api_query if api_query else "for year %d" % year, current_page)) # will raise a CiscoApiCallFailed exception on error if year: eoxapi.query_year(year_to_query=year, page=current_page) else: eoxapi.query_product(product_id=api_query, page=current_page) result_pages = eoxapi.amount_of_pages() if eoxapi.get_page_record_count() > 0: results.extend(eoxapi.get_eox_records()) current_page += 1 except ConnectionFailedException: logger.error("Query failed, server not reachable: %s" % api_query, exc_info=True) raise except CiscoApiCallFailed: logger.fatal("Query failed: %s" % api_query, exc_info=True) raise logger.debug("found %d records for year %s" % (len(results), year)) return results
def query_cisco_eox_api(query_string, blacklist, create_missing=False): """ execute a query against the Cisco API and updates the local database if the product is not defined as blacklisted. :param query_string: query that is executed :param blacklist: list of strings that shouldn't be imported to the database :param create_missing: :return: """ eoxapi = CiscoEoxApi() eoxapi.load_client_credentials() results = [] try: max_pages = 999 current_page = 1 result_pages = 0 while current_page <= max_pages: logger.info("Executing API query '%s' on page '%d" % (query_string, current_page)) eoxapi.query_product(product_id=query_string, page=current_page) if current_page == 1: result_pages = eoxapi.amount_of_pages() logger.info("Initial query returns %d page(s)" % result_pages) records = eoxapi.get_eox_records() # check for errors if eoxapi.has_error(records[0]): logger.info( "Query '%s' returns no valid values: %s" % (query_string, eoxapi.get_error_description(records[0]))) else: # check that the query has valid results if eoxapi.get_valid_record_count() > 0: # processing records for record in records: result_record = {} pid = record['EOLProductID'] result_record["PID"] = pid result_record["created"] = False result_record["updated"] = False result_record["message"] = None logger.info("processing product '%s'..." % pid) # check if record is product of the blacklist if pid not in blacklist: res = update_local_db_based_on_record( record, create_missing) res["blacklist"] = False result_record.update(res) else: logger.info( "Product '%s' blacklisted... no further processing" % pid) result_record.update({"blacklist": True}) results.append(result_record) else: logger.warn("Query '%s' returns no valid values" % query_string) if current_page == result_pages: break else: current_page += 1 except ConnectionFailedException: logger.error("connection for query failed: %s" % query_string, exc_info=True) raise except CiscoApiCallFailed: logger.fatal("query failed: %s" % query_string, exc_info=True) raise return results
def update_cisco_eox_database(api_query): """ synchronizes the local database with the Cisco EoX API using the specified query :param api_query: single query that is send to the Cisco EoX API :raises CiscoApiCallFailed: exception raised if Cisco EoX API call failed :return: list of dictionary that describe the updates to the database """ if type(api_query) is not str: raise ValueError("api_query must be a string value") # load application settings and check, that the API is enabled app_settings = AppSettings() if not app_settings.is_cisco_api_enabled(): msg = "Cisco API access not enabled" logger.warn(msg) raise CiscoApiCallFailed(msg) blacklist_raw_string = app_settings.get_product_blacklist_regex() create_missing = app_settings.is_auto_create_new_products() # clean blacklist string and remove empty statements # (split lines, if any and split the string by semicolon) blacklist = [] for e in [e.split(";") for e in blacklist_raw_string.splitlines()]: blacklist += e blacklist = [e for e in blacklist if e != ""] # start Cisco EoX API query logger.info("Query EoX database: %s" % api_query) eoxapi = CiscoEoxApi() eoxapi.load_client_credentials() results = [] try: max_pages = 999 current_page = 1 result_pages = 0 while current_page <= max_pages: logger.info("Executing API query '%s' on page '%d" % (api_query, current_page)) # will raise a CiscoApiCallFailed exception on error eoxapi.query_product(product_id=api_query, page=current_page) if current_page == 1: result_pages = eoxapi.amount_of_pages() logger.info("Initial query returns %d page(s)" % result_pages) records = eoxapi.get_eox_records() # check that the query has valid results if eoxapi.get_page_record_count() > 0: # processing records for record in records: result_record = {} pid = record['EOLProductID'] result_record["PID"] = pid result_record["created"] = False result_record["updated"] = False result_record["message"] = None logger.info("processing product '%s'..." % pid) pid_in_database = product_id_in_database(pid) # check if the product ID is blacklisted by a regular expression pid_blacklisted = False for regex in blacklist: try: if re.search(regex, pid, re.I): pid_blacklisted = True break except: # silently ignore the issue, invalid regular expressions are handled by the settings form logger.info("invalid regular expression: %s" % regex) # ignore if the product id is not in the database if pid_blacklisted and not pid_in_database: logger.info("Product '%s' blacklisted... no further processing" % pid) result_record.update({ "blacklist": True }) else: res = {} try: res = update_local_db_based_on_record(record, create_missing) except ValidationError: logger.error("invalid data received from Cisco API, cannot save data object for '%s'" % pid, exc_info=True) result_record["message"] = "invalid data received from Cisco EoX API, import incomplete" finally: res["blacklist"] = False result_record.update(res) results.append(result_record) if current_page == result_pages: break else: current_page += 1 # filter empty result strings if len(results) == 0: results = [ { "PID": None, "blacklist": False, "updated": False, "created": False, "message": "No product update required" } ] except ConnectionFailedException: logger.error("Query failed, server not reachable: %s" % api_query, exc_info=True) raise except CiscoApiCallFailed: logger.fatal("Query failed: %s" % api_query, exc_info=True) raise return results