diff --git a/configcatclient/config.py b/configcatclient/config.py index f1cdadd..ee56409 100644 --- a/configcatclient/config.py +++ b/configcatclient/config.py @@ -284,7 +284,7 @@ class Comparator(IntEnum): PREREQUISITE_COMPARATOR_TEXTS = ['EQUALS', 'DOES NOT EQUAL'] -def extend_config_with_inline_salt_and_segment(config): +def fixup_config_salt_and_segments(config): """ Adds the inline salt and segment to the config. When using flag overrides, the original salt and segment indexes may become invalid. Therefore, we copy the diff --git a/configcatclient/configcatclient.py b/configcatclient/configcatclient.py index bb2d077..4815771 100644 --- a/configcatclient/configcatclient.py +++ b/configcatclient/configcatclient.py @@ -207,13 +207,14 @@ def get_key_and_value(self, variation_id): # noqa: C901 targeting_rules = value.get(TARGETING_RULES, []) for targeting_rule in targeting_rules: served_value = targeting_rule.get(SERVED_VALUE) - if served_value is not None and variation_id == served_value.get(VARIATION_ID): - return KeyValue(key, get_value(served_value, setting_type)) - - targeting_rule_percentage_options = targeting_rule.get(PERCENTAGE_OPTIONS, []) - for percentage_option in targeting_rule_percentage_options: - if variation_id == percentage_option.get(VARIATION_ID): - return KeyValue(key, get_value(percentage_option, setting_type)) + if served_value is not None: + if variation_id == served_value.get(VARIATION_ID): + return KeyValue(key, get_value(served_value, setting_type)) + else: + targeting_rule_percentage_options = targeting_rule.get(PERCENTAGE_OPTIONS, []) + for percentage_option in targeting_rule_percentage_options: + if variation_id == percentage_option.get(VARIATION_ID): + return KeyValue(key, get_value(percentage_option, setting_type)) percentage_options = value.get(PERCENTAGE_OPTIONS, []) for percentage_option in percentage_options: diff --git a/configcatclient/configentry.py b/configcatclient/configentry.py index bfb7c24..1d2c889 100644 --- a/configcatclient/configentry.py +++ b/configcatclient/configentry.py @@ -3,7 +3,7 @@ from math import floor from . import utils -from .config import extend_config_with_inline_salt_and_segment +from .config import fixup_config_salt_and_segments from .utils import unicode_to_utf8 @@ -43,7 +43,7 @@ def create_from_string(cls, string): config = json.loads(config_json) if sys.version_info[0] == 2: config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8 - extend_config_with_inline_salt_and_segment(config) + fixup_config_salt_and_segments(config) except ValueError as e: raise ValueError('Invalid config JSON: {}. {}'.format(config_json, str(e))) diff --git a/configcatclient/configfetcher.py b/configcatclient/configfetcher.py index e0963e1..34bc47e 100644 --- a/configcatclient/configfetcher.py +++ b/configcatclient/configfetcher.py @@ -5,7 +5,7 @@ from requests import HTTPError from requests import Timeout -from .config import extend_config_with_inline_salt_and_segment +from .config import fixup_config_salt_and_segments from .configentry import ConfigEntry from .config import CONFIG_FILE_NAME, PREFERENCES, BASE_URL, REDIRECT from .datagovernance import DataGovernance @@ -170,7 +170,7 @@ def _fetch(self, etag): # noqa: C901 if response_etag is None: response_etag = '' config = response.json() - extend_config_with_inline_salt_and_segment(config) + fixup_config_salt_and_segments(config) if sys.version_info[0] == 2: config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8 config_json_string = response.text.encode('utf-8') diff --git a/configcatclient/localfiledatasource.py b/configcatclient/localfiledatasource.py index d58e816..edc65df 100644 --- a/configcatclient/localfiledatasource.py +++ b/configcatclient/localfiledatasource.py @@ -1,7 +1,7 @@ import codecs import sys -from .config import extend_config_with_inline_salt_and_segment, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \ +from .config import fixup_config_salt_and_segments, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \ INT_VALUE, DOUBLE_VALUE, SettingType, SETTING_TYPE, UNSUPPORTED_VALUE from .overridedatasource import OverrideDataSource, FlagOverrides import json @@ -77,7 +77,7 @@ def _reload_file_content(self): # noqa: C901 if setting_type is not None: self._config[FEATURE_FLAGS][key][SETTING_TYPE] = int(setting_type) else: - extend_config_with_inline_salt_and_segment(data) + fixup_config_salt_and_segments(data) self._config = data except OSError: self.log.exception('Failed to read the local config file \'%s\'.', self._file_path, event_id=1302) diff --git a/configcatclient/rolloutevaluator.py b/configcatclient/rolloutevaluator.py index ccf79f4..9433556 100644 --- a/configcatclient/rolloutevaluator.py +++ b/configcatclient/rolloutevaluator.py @@ -17,7 +17,7 @@ from datetime import datetime from .user import User -from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch +from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch, is_string_list def sha256(value_utf8, salt, context_salt): @@ -151,7 +151,7 @@ def _user_attribute_value_to_string(self, value): if isinstance(value, datetime): value = self._get_user_attribute_value_as_seconds_since_epoch(value) - elif isinstance(value, list): + elif is_string_list(value): value = self._get_user_attribute_value_as_string_list(value) return json.dumps(value, ensure_ascii=False, separators=(',', ':')) # Convert the list to a JSON string @@ -198,25 +198,15 @@ def _get_user_attribute_value_as_seconds_since_epoch(self, attribute_value): return self._convert_numeric_to_float(attribute_value) def _get_user_attribute_value_as_string_list(self, attribute_value): - if not isinstance(attribute_value, list): + # Handle unicode strings on Python 2.7 + if isinstance(attribute_value, str) or sys.version_info[0] == 2 and isinstance(attribute_value, unicode): # noqa: F821 attribute_value_list = json.loads(attribute_value) else: attribute_value_list = attribute_value - # Check if the result is a list - if not isinstance(attribute_value_list, list): + if not is_string_list(attribute_value_list): raise ValueError() - # Check if all items in the list are strings - for item in attribute_value_list: - # Handle unicode strings on Python 2.7 - if sys.version_info[0] == 2: - if not isinstance(attribute_value, (str, unicode)): # noqa: F821 - return attribute_value - else: - if not isinstance(item, str): - raise ValueError() - return attribute_value_list def _handle_invalid_user_attribute(self, comparison_attribute, comparator, comparison_value, key, validation_error): @@ -285,6 +275,12 @@ def _evaluate_percentage_options(self, percentage_options, context, percentage_r hash_candidate = ('%s%s' % (key, self._user_attribute_value_to_string(user_key))).encode('utf-8') hash_val = int(hashlib.sha1(hash_candidate).hexdigest()[:7], 16) % 100 + if log_builder: + log_builder.new_line('Evaluating %% options based on the User.%s attribute:' % user_attribute_name) + log_builder.new_line('- Computing hash in the [0..99] range from User.%s => %s ' + '(this value is sticky and consistent across all SDKs)' % + (user_attribute_name, hash_val)) + bucket = 0 index = 1 for percentage_option in percentage_options or []: @@ -294,11 +290,6 @@ def _evaluate_percentage_options(self, percentage_options, context, percentage_r percentage_value = get_value(percentage_option, context.setting_type) variation_id = percentage_option.get(VARIATION_ID, default_variation_id) if log_builder: - log_builder.new_line('Evaluating %% options based on the User.%s attribute:' % - user_attribute_name) - log_builder.new_line('- Computing hash in the [0..99] range from User.%s => %s ' - '(this value is sticky and consistent across all SDKs)' % - (user_attribute_name, hash_val)) log_builder.new_line("- Hash value %s selects %% option %s (%s%%), '%s'." % (hash_val, index, percentage, percentage_value)) return True, percentage_value, variation_id, percentage_option @@ -421,8 +412,7 @@ def _evaluate_prerequisite_flag_condition(self, prerequisite_flag_condition, con prerequisite_value, _, _, _, _ = self.evaluate(prerequisite_key, context.user, None, None, config, log_builder, context.visited_keys) - if visited_keys: - visited_keys.pop() + visited_keys.pop() if log_builder: log_builder.new_line("Prerequisite flag evaluation result: '%s'." % str(prerequisite_value)) @@ -438,6 +428,8 @@ def _evaluate_prerequisite_flag_condition(self, prerequisite_flag_condition, con elif prerequisite_comparator == PrerequisiteComparator.NOT_EQUALS: if prerequisite_value != prerequisite_comparison_value: prerequisite_condition_result = True + else: + raise ValueError('Comparison operator is missing or invalid.') if log_builder: log_builder.append('%s.' % ('true' if prerequisite_condition_result else 'false')) @@ -527,7 +519,7 @@ def _evaluate_segment_condition(self, segment_condition, context, salt, log_buil return segment_condition_result, error - return False, None + raise ValueError('Comparison operator is missing or invalid.') def _evaluate_user_condition(self, user_condition, context, context_salt, salt, log_builder): # noqa: C901, E501 """ @@ -562,7 +554,7 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt, return False, error user_value = user.get_attribute(comparison_attribute) - if user_value is None or (not user_value and not isinstance(user_value, list)): + if user_value is None or (isinstance(user_value, str) and len(user_value) == 0): self.log.warning('Cannot evaluate condition (%s) for setting \'%s\' ' '(the User.%s attribute is missing). You should set the User.%s attribute in order to make ' 'targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/', @@ -764,5 +756,7 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt, if comparison in user_value_list: return False, None return True, error + else: + raise ValueError('Comparison operator is missing or invalid.') return False, error diff --git a/configcatclient/utils.py b/configcatclient/utils.py index 5b12ba8..8df9089 100644 --- a/configcatclient/utils.py +++ b/configcatclient/utils.py @@ -81,6 +81,24 @@ def get_utc_now_seconds_since_epoch(): return get_seconds_since_epoch(get_utc_now()) +def is_string_list(value): + # Check if the value is a list + if not isinstance(value, list): + return False + + # Check if all items in the list are strings + for item in value: + # Handle unicode strings on Python 2.7 + if sys.version_info[0] == 2: + if not isinstance(item, (str, unicode)): # noqa: F821 + return False + else: + if not isinstance(item, str): + return False + + return True + + def unicode_to_utf8(data): """ Convert unicode data in a collection to UTF-8 data. Used for supporting unicode config json strings on Python 2.7. diff --git a/configcatclient/version.py b/configcatclient/version.py index 872bf2e..4014ba7 100644 --- a/configcatclient/version.py +++ b/configcatclient/version.py @@ -1 +1 @@ -CONFIGCATCLIENT_VERSION = "9.0.2" +CONFIGCATCLIENT_VERSION = "9.0.3" diff --git a/configcatclienttests/data/evaluation/circular_dependency.json b/configcatclienttests/data/evaluation/circular_dependency.json deleted file mode 100644 index cb50d22..0000000 --- a/configcatclienttests/data/evaluation/circular_dependency.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "jsonOverride": "circular_dependency_override.json", - "tests": [ - { - "key": "key1", - "defaultValue": "default", - "user": { - "Identifier": "1234" - }, - "returnValue": "first", - "expectedLog": "circular_dependency.txt" - } - ] -} diff --git a/configcatclienttests/test_hooks.py b/configcatclienttests/test_hooks.py index d2afb84..fc465bd 100644 --- a/configcatclienttests/test_hooks.py +++ b/configcatclienttests/test_hooks.py @@ -5,7 +5,7 @@ from configcatclient.configcatclient import ConfigCatClient from configcatclient.config import FEATURE_FLAGS, VALUE, SERVED_VALUE, STRING_VALUE, \ - extend_config_with_inline_salt_and_segment + fixup_config_salt_and_segments from configcatclient.user import User from configcatclient.configcatoptions import ConfigCatOptions, Hooks from configcatclient.pollingmode import PollingMode @@ -47,7 +47,7 @@ def test_init(self): self.assertTrue(hook_callbacks.is_ready) self.assertEqual(1, hook_callbacks.is_ready_call_count) extended_config = TEST_OBJECT - extend_config_with_inline_salt_and_segment(extended_config) + fixup_config_salt_and_segments(extended_config) self.assertEqual(extended_config.get(FEATURE_FLAGS), hook_callbacks.changed_config) self.assertEqual(1, hook_callbacks.changed_config_call_count) self.assertTrue(hook_callbacks.evaluation_details) diff --git a/configcatclienttests/test_rollout.py b/configcatclienttests/test_rollout.py index 34acaa3..5da3f5a 100644 --- a/configcatclienttests/test_rollout.py +++ b/configcatclienttests/test_rollout.py @@ -89,6 +89,16 @@ def test_matrix_sensitive(self): self._test_matrix('data/testmatrix_sensitive.csv', 'configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/-0YmVOUNgEGKkgRF-rU65g', self.value_test_type) + def test_matrix_segments_old(self): + # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbd6ca-a85f-4ed0-888a-2da18def92b5/244cf8b0-f604-11e8-b543-f23c917f9d8d + self._test_matrix('data/testmatrix_segments_old.csv', + 'configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/y_ZB7o-Xb0Swxth-ZlMSeA', self.value_test_type) + + def test_matrix_variation_id(self): + # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-30c6-4969-8e4c-03f6a8764199/244cf8b0-f604-11e8-b543-f23c917f9d8d + self._test_matrix('data/testmatrix_variationId.csv', + 'configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/spQnkRTIPEWVivZkWM84lQ', self.variation_test_type) + def test_matrix_comparators_v6(self): # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9a6b-4947-84e2-91529248278a/08dbc325-9ebd-4587-8171-88f76a3004cb self._test_matrix('data/testmatrix_comparators_v6.csv', @@ -99,11 +109,6 @@ def test_matrix_segments(self): self._test_matrix('data/testmatrix_segments.csv', 'configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/h99HYXWWNE2bH8eWyLAVMA', self.value_test_type) - def test_matrix_segments_old(self): - # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbd6ca-a85f-4ed0-888a-2da18def92b5/244cf8b0-f604-11e8-b543-f23c917f9d8d - self._test_matrix('data/testmatrix_segments_old.csv', - 'configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/y_ZB7o-Xb0Swxth-ZlMSeA', self.value_test_type) - def test_matrix_prerequisite_flag(self): # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbc325-9b74-45cb-86d0-4d61c25af1aa/08dbc325-9ebd-4587-8171-88f76a3004cb self._test_matrix('data/testmatrix_prerequisite_flag.csv', @@ -114,11 +119,6 @@ def test_matrix_and_or(self): self._test_matrix('data/testmatrix_and_or.csv', 'configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/ByMO9yZNn02kXcm72lnY1A', self.value_test_type) - def test_matrix_variation_id(self): - # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08dbc4dc-30c6-4969-8e4c-03f6a8764199/244cf8b0-f604-11e8-b543-f23c917f9d8d - self._test_matrix('data/testmatrix_variationId.csv', - 'configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/spQnkRTIPEWVivZkWM84lQ', self.variation_test_type) - def test_matrix_unicode(self): # https://app.configcat.com/v2/e7a75611-4256-49a5-9320-ce158755e3ba/08dbc325-7f69-4fd4-8af4-cf9f24ec8ac9/08dbd63c-9774-49d6-8187-5f2aab7bd606/08dbc325-9ebd-4587-8171-88f76a3004cb self._test_matrix('data/testmatrix_unicode.csv', @@ -284,15 +284,16 @@ def test_wrong_config_json_type_mismatch(self): ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 5, ">=5"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", float('inf'), ">5"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", float('nan'), "<>4.2"), - ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-inf", "<2.1"), + ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-Infinity", "<2.1"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-1", "<2.1"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2", "<2.1"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2.1", "<=2,1"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2,1", "<=2,1"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "3", "<>4.2"), ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "5", ">=5"), - ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "inf", ">5"), - ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "nan", "<>4.2"), + ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "Infinity", ">5"), + ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaN", "<>4.2"), + ("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaNa", "80%"), # Date time-based comparisons ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", datetime(2023, 3, 31, 23, 59, 59, 999000), False), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", datetime(2023, 3, 31, 23, 59, 59, 999000, timezone.utc), False), @@ -314,12 +315,12 @@ def test_wrong_config_json_type_mismatch(self): ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1680307201, True), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899199, True), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899201, False), - ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "-inf", False), + ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "-Infinity", False), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1680307199.999", False), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1680307200.001", True), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1682899199.999", True), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1682899200.001", False), - ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "+inf", False), + ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "+Infinity", False), ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "NaN", False), # String array-based comparisons ("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", ["x", "read"], "Dog"), @@ -356,7 +357,7 @@ def test_user_object_attribute_value_conversion_non_text_comparisons(self, sdk_k ("stringArrayToStringConversionSpecialChars", ["+<>%\"'\\/\t\r\n"], "3"), ("stringArrayToStringConversionUnicode", ["äöüÄÖÜçéèñışğ⢙✓😀"], "2"), ]) - def test_attribute_conversion_to_canonical_string(self, key, customAttributeValue, expectedReturnValue): + def test_attribute_conversion_to_canonical_string(self, key, custom_attribute_value, expected_return_value): # Skip "dateToStringConversion" tests on Python 2.7 because of float precision issues if sys.version_info[0] == 2 and key == 'dateToStringConversion': self.skipTest("Python 2 float precision issue") @@ -370,9 +371,9 @@ def test_attribute_conversion_to_canonical_string(self, key, customAttributeValu logger.addHandler(log_handler) evaluator = RolloutEvaluator(log) - user = User('12345', custom={'Custom1': customAttributeValue}) + user = User('12345', custom={'Custom1': custom_attribute_value}) value, _, _, _, _ = evaluator.evaluate(key, user, 'default_value', 'default_variation_id', config, None) - self.assertEqual(expectedReturnValue, value) + self.assertEqual(expected_return_value, value) @parameterized.expand([ ("isoneof", "no trim"), @@ -505,36 +506,36 @@ def test_prerequisite_flag_circular_dependency(self, key, dependency_cycle): self.assertTrue(dependency_cycle in error_log) @parameterized.expand([ - ("stringDependsOnBool", bool, "mainBoolFlag", True, "Dog"), - ("stringDependsOnBool", bool, "mainBoolFlag", False, "Cat"), - ("stringDependsOnBool", bool, "mainBoolFlag", "1", None), - ("stringDependsOnBool", bool, "mainBoolFlag", 1, None), - ("stringDependsOnBool", bool, "mainBoolFlag", 1.0, None), - ("stringDependsOnBool", bool, "mainBoolFlag", [True], None), - ("stringDependsOnBool", bool, "mainBoolFlag", None, None), - ("stringDependsOnString", str, "mainStringFlag", "private", "Dog"), - ("stringDependsOnString", str, "mainStringFlag", "Private", "Cat"), - ("stringDependsOnString", str, "mainStringFlag", True, None), - ("stringDependsOnString", str, "mainStringFlag", 1, None), - ("stringDependsOnString", str, "mainStringFlag", 1.0, None), - ("stringDependsOnString", str, "mainStringFlag", ["private"], None), - ("stringDependsOnString", str, "mainStringFlag", None, None), - ("stringDependsOnInt", int, "mainIntFlag", 2, "Dog"), - ("stringDependsOnInt", int, "mainIntFlag", 1, "Cat"), - ("stringDependsOnInt", int, "mainIntFlag", "2", None), - ("stringDependsOnInt", int, "mainIntFlag", True, None), - ("stringDependsOnInt", int, "mainIntFlag", 2.0, None), - ("stringDependsOnInt", int, "mainIntFlag", [2], None), - ("stringDependsOnInt", int, "mainIntFlag", None, None), - ("stringDependsOnDouble", float, "mainDoubleFlag", 0.1, "Dog"), - ("stringDependsOnDouble", float, "mainDoubleFlag", 0.11, "Cat"), - ("stringDependsOnDouble", float, "mainDoubleFlag", "0.1", None), - ("stringDependsOnDouble", float, "mainDoubleFlag", True, None), - ("stringDependsOnDouble", float, "mainDoubleFlag", 1, None), - ("stringDependsOnDouble", float, "mainDoubleFlag", [0.1], None), - ("stringDependsOnDouble", float, "mainDoubleFlag", None, None) + ("stringDependsOnBool", "mainBoolFlag", True, "Dog"), + ("stringDependsOnBool", "mainBoolFlag", False, "Cat"), + ("stringDependsOnBool", "mainBoolFlag", "1", None), + ("stringDependsOnBool", "mainBoolFlag", 1, None), + ("stringDependsOnBool", "mainBoolFlag", 1.0, None), + ("stringDependsOnBool", "mainBoolFlag", [True], None), + ("stringDependsOnBool", "mainBoolFlag", None, None), + ("stringDependsOnString", "mainStringFlag", "private", "Dog"), + ("stringDependsOnString", "mainStringFlag", "Private", "Cat"), + ("stringDependsOnString", "mainStringFlag", True, None), + ("stringDependsOnString", "mainStringFlag", 1, None), + ("stringDependsOnString", "mainStringFlag", 1.0, None), + ("stringDependsOnString", "mainStringFlag", ["private"], None), + ("stringDependsOnString", "mainStringFlag", None, None), + ("stringDependsOnInt", "mainIntFlag", 2, "Dog"), + ("stringDependsOnInt", "mainIntFlag", 1, "Cat"), + ("stringDependsOnInt", "mainIntFlag", "2", None), + ("stringDependsOnInt", "mainIntFlag", True, None), + ("stringDependsOnInt", "mainIntFlag", 2.0, None), + ("stringDependsOnInt", "mainIntFlag", [2], None), + ("stringDependsOnInt", "mainIntFlag", None, None), + ("stringDependsOnDouble", "mainDoubleFlag", 0.1, "Dog"), + ("stringDependsOnDouble", "mainDoubleFlag", 0.11, "Cat"), + ("stringDependsOnDouble", "mainDoubleFlag", "0.1", None), + ("stringDependsOnDouble", "mainDoubleFlag", True, None), + ("stringDependsOnDouble", "mainDoubleFlag", 1, None), + ("stringDependsOnDouble", "mainDoubleFlag", [0.1], None), + ("stringDependsOnDouble", "mainDoubleFlag", None, None) ]) - def test_prerequisite_flag_comparison_value_type_mismatch(self, key, comparison_value_type, prerequisite_flag_key, prerequisite_flag_value, expected_value): + def test_prerequisite_flag_comparison_value_type_mismatch(self, key, prerequisite_flag_key, prerequisite_flag_value, expected_value): override_dictionary = {prerequisite_flag_key: prerequisite_flag_value} options = ConfigCatOptions(polling_mode=PollingMode.manual_poll(), flag_overrides=LocalDictionaryFlagOverrides( diff --git a/setup.py b/setup.py index 4d4131a..c4f8039 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ def parse_requirements(filename): return [line for line in lines if line] -configcatclient_version = '9.0.2' +configcatclient_version = '9.0.3' requirements = parse_requirements('requirements.txt')