Skip to content

Commit

Permalink
setting type mismatch test + exceptions + log update
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-cat committed Feb 7, 2024
1 parent 9463ce6 commit 2e68b2f
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 28 deletions.
24 changes: 22 additions & 2 deletions configcatclient/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sys

from enum import IntEnum

CONFIG_FILE_NAME = 'config_v6'
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
25 changes: 7 additions & 18 deletions configcatclient/configcatclient.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion configcatclient/configfetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
9 changes: 4 additions & 5 deletions configcatclient/rolloutevaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
50 changes: 50 additions & 0 deletions configcatclienttests/test_config.py
Original file line number Diff line number Diff line change
@@ -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 <type 'bool'>"

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 <type 'bool'>"


if __name__ == '__main__':
unittest.main()
6 changes: 4 additions & 2 deletions configcatclienttests/test_rollout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down

0 comments on commit 2e68b2f

Please sign in to comment.