diff --git a/configcatclient/config.py b/configcatclient/config.py index 68eb08e..f550e96 100644 --- a/configcatclient/config.py +++ b/configcatclient/config.py @@ -1,3 +1,5 @@ +import sys + from enum import IntEnum CONFIG_FILE_NAME = 'config_v6' @@ -61,6 +63,24 @@ UNSUPPORTED_VALUE = 'unsupported_value' +def is_type_missmatch(value, py_type): + is_float_int_missmatch = \ + (type(value) is float and py_type is int) or \ + (type(value) is int and py_type is float) + + # On Python 2.7, ignore the type missmatch is between str and unicode. + # (ignore warning: unicode is undefined in Python 3) + is_str_unicode_missmatch = \ + (sys.version_info[0] == 2 and type(value) is unicode and py_type is str) or \ + (sys.version_info[0] == 2 and type(value) is str and py_type is unicode) # noqa: F821 + + if type(value) is not py_type: + if not is_float_int_missmatch and not is_str_unicode_missmatch: + return True + + return False + + def get_value(dictionary, setting_type): value_descriptor = dictionary.get(VALUE) if value_descriptor is None: @@ -74,8 +94,8 @@ def get_value(dictionary, setting_type): raise ValueError('Unsupported setting type') value = value_descriptor.get(expected_value_type) - if value is None: - raise ValueError('Setting value is not of the expected type %s' % expected_py_type) + if value is None or is_type_missmatch(value, expected_py_type): + raise ValueError("Setting value is not of the expected type %s" % expected_py_type) return value diff --git a/configcatclient/configcatclient.py b/configcatclient/configcatclient.py index aaccb1e..e44297e 100644 --- a/configcatclient/configcatclient.py +++ b/configcatclient/configcatclient.py @@ -1,10 +1,10 @@ import logging -import sys from threading import Lock from . import utils from .configservice import ConfigService -from .config import TARGETING_RULES, VARIATION_ID, PERCENTAGE_OPTIONS, FEATURE_FLAGS, SERVED_VALUE, SETTING_TYPE +from .config import TARGETING_RULES, VARIATION_ID, PERCENTAGE_OPTIONS, FEATURE_FLAGS, SERVED_VALUE, SETTING_TYPE, \ + is_type_missmatch from .evaluationdetails import EvaluationDetails from .evaluationlogbuilder import EvaluationLogBuilder from .interfaces import ConfigCatClientException @@ -375,22 +375,11 @@ def _get_config(self): return self._config_service.get_config() def _check_type_missmatch(self, value, default_value): - is_float_int_missmatch = \ - (type(value) is float and type(default_value) is int) or \ - (type(value) is int and type(default_value) is float) - - # On Python 2.7, do not log a warning if the type missmatch is between str and unicode. - # (ignore warning: unicode is undefined in Python 3) - is_str_unicode_missmatch = \ - (sys.version_info[0] == 2 and type(value) is unicode and type(default_value) is str) or \ - (sys.version_info[0] == 2 and type(value) is str and type(default_value) is unicode) # noqa: F821 - - if default_value is not None and type(value) is not type(default_value): - if not is_float_int_missmatch and not is_str_unicode_missmatch: - self.log.warning("The type of a setting does not match the type of the specified default value (%s). " - "Setting's type was %s but the default value's type was %s. " - "Please make sure that using a default value not matching the setting's type was intended." % - (default_value, type(value), type(default_value)), event_id=4002) + if default_value is not None and is_type_missmatch(value, type(default_value)): + self.log.warning("The type of a setting does not match the type of the specified default value (%s). " + "Setting's type was %s but the default value's type was %s. " + "Please make sure that using a default value not matching the setting's type was intended." % + (default_value, type(value), type(default_value)), event_id=4002) def _evaluate(self, key, user, default_value, default_variation_id, config, fetch_time): user = user if user is not None else self._default_user diff --git a/configcatclient/configfetcher.py b/configcatclient/configfetcher.py index fe9b9a1..e0963e1 100644 --- a/configcatclient/configfetcher.py +++ b/configcatclient/configfetcher.py @@ -201,6 +201,8 @@ def _fetch(self, etag): # noqa: C901 self.log.error(error, *error_args, event_id=1102) return FetchResponse.failure(Logger.format(error, error_args), True) except Exception as e: - error = 'Unexpected error occurred while trying to fetch config JSON.' + error = 'Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network ' \ + 'issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) ' \ + 'over HTTP.' self.log.exception(error, event_id=1103) return FetchResponse.failure(Logger.format(error, (), e), True) diff --git a/configcatclient/rolloutevaluator.py b/configcatclient/rolloutevaluator.py index ec51ed0..ccf79f4 100644 --- a/configcatclient/rolloutevaluator.py +++ b/configcatclient/rolloutevaluator.py @@ -395,13 +395,12 @@ def _evaluate_prerequisite_flag_condition(self, prerequisite_flag_condition, con prerequisite_flag_setting_type = settings[prerequisite_key].get(SETTING_TYPE) prerequisite_comparison_value_type = get_value_type(prerequisite_flag_condition) + prerequisite_comparison_value = get_value(prerequisite_flag_condition, prerequisite_flag_setting_type) + # Type mismatch check if prerequisite_comparison_value_type != SettingType.to_type(prerequisite_flag_setting_type): - raise ValueError("Type mismatch between comparison value type %s and type %s of prerequisite flag '%s'" % - (prerequisite_comparison_value_type, SettingType.to_type(prerequisite_flag_setting_type), - prerequisite_key)) - - prerequisite_comparison_value = get_value(prerequisite_flag_condition, prerequisite_flag_setting_type) + raise ValueError("Type mismatch between comparison value '%s' and prerequisite flag '%s'" % + (prerequisite_comparison_value, prerequisite_key)) prerequisite_condition = ("Flag '%s' %s '%s'" % (prerequisite_key, PREREQUISITE_COMPARATOR_TEXTS[prerequisite_comparator], diff --git a/configcatclienttests/test_config.py b/configcatclienttests/test_config.py new file mode 100644 index 0000000..15c2b1e --- /dev/null +++ b/configcatclienttests/test_config.py @@ -0,0 +1,50 @@ +import logging +import unittest + +import pytest + +from configcatclient.config import get_value, SETTING_TYPE + +logging.basicConfig(level=logging.INFO) + + +class ConfigTests(unittest.TestCase): + def test_value_setting_type_is_missing(self): + value_dictionary = { + 't': 6, # unsupported setting type + 'v': { + 'b': True + } + } + setting_type = value_dictionary.get(SETTING_TYPE) + with pytest.raises(ValueError) as e: + get_value(value_dictionary, setting_type) + assert str(e.value) == "Unsupported setting type" + + def test_value_setting_type_is_valid_but_return_value_is_missing(self): + value_dictionary = { + 't': 0, # boolean + 'v': { + 's': True # the wrong property is set ("b" should be set) + } + } + setting_type = value_dictionary.get(SETTING_TYPE) + with pytest.raises(ValueError) as e: + get_value(value_dictionary, setting_type) + assert str(e.value) == "Setting value is not of the expected type " + + def test_value_setting_type_is_valid_and_the_return_value_is_present_but_it_is_invalid(self): + value_dictionary = { + 't': 0, # boolean + 'v': { + 'b': 'True' # the value is a string instead of a boolean + } + } + setting_type = value_dictionary.get(SETTING_TYPE) + with pytest.raises(ValueError) as e: + get_value(value_dictionary, setting_type) + assert str(e.value) == "Setting value is not of the expected type " + + +if __name__ == '__main__': + unittest.main() diff --git a/configcatclienttests/test_rollout.py b/configcatclienttests/test_rollout.py index 9c3cb3e..34acaa3 100644 --- a/configcatclienttests/test_rollout.py +++ b/configcatclienttests/test_rollout.py @@ -556,8 +556,10 @@ def test_prerequisite_flag_comparison_value_type_mismatch(self, key, comparison_ error_log = log_handler.error_logs[0] prerequisite_flag_value_type = SettingType.to_type(SettingType.from_type(type(prerequisite_flag_value))) - self.assertTrue(("Type mismatch between comparison value type %s and type %s of prerequisite flag '%s'" % - (comparison_value_type, prerequisite_flag_value_type, prerequisite_flag_key)) in error_log) + if prerequisite_flag_value is None or prerequisite_flag_value_type is None: + self.assertTrue('Unsupported setting type' in error_log) + else: + self.assertTrue(("Setting value is not of the expected type %s" % prerequisite_flag_value_type) in error_log) client.close()