Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config v6 #50

Merged
merged 115 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 102 commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
cf8f529
Expose Python 3.11 support
noirbizarre Mar 24, 2023
eb56f1d
add a tox.ini config for easier local testing
noirbizarre Mar 24, 2023
cc68df8
Drop unsupported Python versions and update syntax accordingly
noirbizarre Mar 30, 2023
b034063
Add Flake8 linting to tox
noirbizarre Mar 30, 2023
e045f7d
Fix semver deprecations warnings
noirbizarre Mar 30, 2023
47c5dd7
Export typing using PEP-561 py.typed
noirbizarre Mar 30, 2023
5612457
Add typing check and expose linting settings
noirbizarre Mar 30, 2023
4f40dcd
Simplify github actions workflow
noirbizarre Mar 30, 2023
12af3fa
Fix all lints
noirbizarre Mar 30, 2023
66530b8
Update contributing guide and document `tox` usage
noirbizarre Mar 30, 2023
adeea7f
Ignore python:S4790 intentional Sonar errors
noirbizarre Mar 30, 2023
7a7e773
Merge pull request #45 from noirbizarre/build/modernize
kp-cat Apr 11, 2023
46416e7
reverse commit: Drop unsupported Python versions and update syntax ac…
kp-cat Apr 14, 2023
1016f63
fix lints
kp-cat Apr 18, 2023
c9a3953
update CONTRIBUTING.md
kp-cat Apr 18, 2023
ce41c64
Merge remote-tracking branch 'origin/master' into tox
kp-cat Apr 18, 2023
42ac47f
remove MyPy check
kp-cat Apr 18, 2023
5ca3c69
universal bdist_wheel
kp-cat Apr 18, 2023
1c8d1e3
fix lints
kp-cat Apr 18, 2023
f504610
intro config v6 json format
kp-cat Apr 25, 2023
31a63cf
add comparators
kp-cat Apr 26, 2023
feea3ad
Merge branch 'master' into config-v6
kp-cat Apr 26, 2023
838f5a9
conditions check
kp-cat Apr 26, 2023
ce4cb17
dependent flag logging into the same log_entries
kp-cat Apr 26, 2023
f378bef
configclient get_config fixes
kp-cat Apr 27, 2023
d419d56
dependency loop check
kp-cat May 2, 2023
d5731a2
testmatrix comparators_v6
kp-cat May 8, 2023
5574ffd
testmatrix segments
kp-cat May 8, 2023
6a9ac44
testmatrix dependent flag
kp-cat May 9, 2023
fcaf741
testmatrix: AndOr
kp-cat May 11, 2023
eec8845
dependent flag logging
kp-cat May 11, 2023
d4dd345
comments
kp-cat May 12, 2023
f49d24e
TODO: percentage_rule_attribute
kp-cat May 12, 2023
85dbf31
sdk key validation check
kp-cat May 12, 2023
f919822
percentage_rule_attribute log
kp-cat May 12, 2023
128645e
move sha256 calculation into a function
kp-cat May 12, 2023
1a073ae
finalize no percentage_rule_attribute error handling
kp-cat May 22, 2023
9d7b17f
introduce typed value in override + test fixes
kp-cat May 23, 2023
25d435b
linter fixes
kp-cat May 23, 2023
7ee4552
cleanup
kp-cat May 23, 2023
2eda4f6
github test fix
kp-cat May 23, 2023
7cc9490
custom percentage attribute
kp-cat May 24, 2023
99df9c4
Merge remote-tracking branch 'origin/master' into config-v6
kp-cat May 26, 2023
be339e7
IS NOT IN SEGMENT fix
kp-cat Jun 13, 2023
ca4651a
operator updates
kp-cat Jun 14, 2023
ede9bd7
Merge branch 'master' into config-v6
kp-cat Jun 16, 2023
258edd2
update tests
kp-cat Jun 16, 2023
53e676c
lint fixes
kp-cat Jun 16, 2023
87db853
circular dependency test
kp-cat Jun 16, 2023
cacbc47
new evaluation logging (WIP)
kp-cat Jun 20, 2023
fc22562
github action: python 2.7 support
kp-cat Jun 28, 2023
3e3f7cd
fix user json key order on python 2.7
kp-cat Jul 3, 2023
ca6ad79
Remove the u prefix from unicode strings on python 2.7 in the eval lo…
kp-cat Jul 3, 2023
04c088a
Merge remote-tracking branch 'origin/master' into config-v6
kp-cat Jul 3, 2023
3500b46
evaluation logging
kp-cat Jul 13, 2023
d3ccbb8
lint fixes
kp-cat Jul 13, 2023
139e863
fix tests
kp-cat Jul 14, 2023
3a55798
test_options_within_targeting_rule
kp-cat Jul 17, 2023
74f0a7d
lint fix
kp-cat Jul 19, 2023
1b3747e
typo fix
kp-cat Jul 19, 2023
0aba604
handling the modified config json format
kp-cat Jul 27, 2023
f27685e
evaluation log test + generator + data
kp-cat Jul 31, 2023
1371b2f
lint fix
kp-cat Jul 31, 2023
1e5000a
Adjust evaluation and update evaluation tests
kp-cat Aug 3, 2023
6092b87
In case of local only flag overrides mode, we accept any SDK Key format
kp-cat Aug 3, 2023
8b87f5b
rename comparators
kp-cat Aug 3, 2023
d2ffb62
NOT STARTS WITH ANY OF (hashed), NOT ENDS WITH ANY OF (hashed) compar…
kp-cat Aug 3, 2023
f190fdb
eval log tests: validation error handling
kp-cat Aug 17, 2023
d332a93
lint fixes
kp-cat Aug 17, 2023
0a9eb13
consistent trim logic during evaluation
kp-cat Aug 18, 2023
fdd567e
log fix
kp-cat Aug 19, 2023
b7fa936
test incorrect json
kp-cat Aug 30, 2023
0ad97c0
evaluation log update: hashed value + max 10 length lists
kp-cat Aug 30, 2023
254201c
indentation fix
kp-cat Aug 30, 2023
7e0288c
matched_evaluation_rule -> matched_targeting_rule, matched_evaluation…
kp-cat Aug 30, 2023
491042e
evallogging: list logging fix
kp-cat Aug 30, 2023
15f8a0a
update matrix tests + eval log tests
kp-cat Oct 17, 2023
fc18fb5
Fix list_truncation.txt
kp-cat Oct 17, 2023
d355ca2
Turn off python 2.7 build
kp-cat Oct 17, 2023
728ed0a
attr_value_from_datetime + attr_value_from_list
kp-cat Oct 17, 2023
5a47b1e
inline salt, segment for handling flag override
kp-cat Oct 18, 2023
c6e2377
fix python 3.5 test
kp-cat Oct 18, 2023
4847906
check if the prerequisite key exists
kp-cat Oct 18, 2023
5de44f9
remove unnecessary served_value get
kp-cat Oct 19, 2023
488d75d
add `any of` tests to testmatrix_comparators_v6
kp-cat Oct 19, 2023
1578d96
rename config members (comparision_rule -> user_condition, segment_ru…
kp-cat Oct 19, 2023
1ae76e9
config descriptor
kp-cat Oct 19, 2023
21a2fa8
github-ci fix
kp-cat Oct 19, 2023
6e81200
fix python 2.7 tests
kp-cat Oct 20, 2023
43ceb72
github ci: python 3.5
kp-cat Oct 20, 2023
a7ed329
Adjust config model and tests to config v6 schema changes
kp-cat Oct 25, 2023
a69725c
matrix test update + new cleartext comparators
kp-cat Oct 27, 2023
01ddd9c
unicode tests
kp-cat Oct 27, 2023
f604f14
segments_old matrix test + segment eval log fix
kp-cat Oct 30, 2023
8ca15b7
python 2.7 unicode support
kp-cat Oct 31, 2023
08626e7
fix evaluation log test on python <= 3.5
kp-cat Oct 31, 2023
83f10df
type mismatched user attribute warning
kp-cat Nov 15, 2023
1edda12
python 3.12 support
kp-cat Nov 15, 2023
4e14f69
lint fix + review fixes
kp-cat Nov 15, 2023
2cf2bda
python 2.7 fix
kp-cat Nov 15, 2023
7690518
github ci: win test fix
kp-cat Nov 15, 2023
30e2f91
review fixes + exceptions in prerequisite flag evaluation
kp-cat Nov 19, 2023
7ad2ca9
lint fix
kp-cat Nov 20, 2023
a3af74e
don't force users to pass user object attributes as strings
kp-cat Nov 21, 2023
27a6d5d
test: evaluation_details_matched_evaluation_rule_and_percentage_option
kp-cat Nov 21, 2023
9dd548a
DefaultValue and SettingType mismatch warning
kp-cat Nov 21, 2023
3aab0f2
forced setting_type check
kp-cat Nov 26, 2023
cb2e14d
python 2.7 support (timezone, unicode/str handling)
kp-cat Nov 27, 2023
715e27c
Read cache upon each setting read (lazy cache fix)
kp-cat Nov 27, 2023
378aac8
fix coding convention
kp-cat Nov 27, 2023
41bf5d0
bump version 9.0.0
kp-cat Nov 27, 2023
64b5542
Merge branch 'master' into config-v6
kp-cat Nov 27, 2023
d88269c
ConfigService's refresh offline warning
kp-cat Nov 27, 2023
2aa483f
comment updates
kp-cat Nov 28, 2023
1ca1db9
remove unsupported value error log
kp-cat Nov 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ on:
jobs:
test:

runs-on: ubuntu-20.04
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ windows-latest, macos-latest, ubuntu-20.04 ]

steps:
- uses: actions/checkout@v3
Expand All @@ -36,7 +37,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov mock flake8
pip install pytest pytest-cov parameterized mock flake8
pip install -r requirements.txt

- name: Lint with flake8
Expand Down
295 changes: 295 additions & 0 deletions configcatclient/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
from enum import IntEnum

CONFIG_FILE_NAME = 'config_v6'
SERIALIZATION_FORMAT_VERSION = 'v2'

# Config
PREFERENCES = 'p'
SEGMENTS = 's'
FEATURE_FLAGS = 'f'

# Preferences
BASE_URL = 'u'
REDIRECT = 'r'
SALT = 's'

# Segment
SEGMENT_NAME = 'n' # The first 4 characters of the Segment's name
SEGMENT_CONDITIONS = 'r' # The list of segment rule conditions (has a logical AND relation between the items).

# Segment Condition (User Condition)
COMPARISON_ATTRIBUTE = 'a' # The attribute of the user object that should be used to evaluate this rule
COMPARATOR = 'c'

# Feature flag (Evaluation Formula)
SETTING_TYPE = 't' # 0 = bool, 1 = string, 2 = int, 3 = double
PERCENTAGE_RULE_ATTRIBUTE = 'a' # Percentage rule evaluation hashes this attribute of the User object to calculate the buckets
TARGETING_RULES = 'r' # Targeting Rules (Logically connected by OR)
PERCENTAGE_OPTIONS = 'p' # Percentage Options without conditions
VALUE = 'v'
VARIATION_ID = 'i'
INLINE_SALT = 'inline_salt'

# Targeting Rule (Evaluation Rule)
CONDITIONS = 'c'
SERVED_VALUE = 's' # Value and Variation ID
TARGETING_RULE_PERCENTAGE_OPTIONS = 'p'

# Condition
USER_CONDITION = 'u'
SEGMENT_CONDITION = 's' # Segment targeting rule
PREREQUISITE_FLAG_CONDITION = 'p' # Prerequisite flag targeting rule

# Segment Condition
SEGMENT_INDEX = 's'
SEGMENT_COMPARATOR = 'c'
INLINE_SEGMENT = 'inline_segment'

# Prerequisite Flag Condition
PREREQUISITE_FLAG_KEY = 'f'
PREREQUISITE_COMPARATOR = 'c'

# Percentage Option
PERCENTAGE = 'p'

# Value
BOOL_VALUE = 'b'
STRING_VALUE = 's'
INT_VALUE = 'i'
DOUBLE_VALUE = 'd'
STRING_LIST_VALUE = 'l'


def get_value(dictionary):
value_descriptor = dictionary.get(VALUE)
if value_descriptor is None:
raise ValueError('Value is missing.')

if value_descriptor.get(BOOL_VALUE) is not None:
value = value_descriptor.get(BOOL_VALUE)
if isinstance(value, bool):
return value
if value_descriptor.get(STRING_VALUE) is not None:
value = value_descriptor.get(STRING_VALUE)
if isinstance(value, str):
return value
if value_descriptor.get(INT_VALUE) is not None:
value = value_descriptor.get(INT_VALUE)
if isinstance(value, int):
return value
if value_descriptor.get(DOUBLE_VALUE) is not None:
value = value_descriptor.get(DOUBLE_VALUE)
if isinstance(value, (float, int)):
return value
else:
raise ValueError('Unknown value type.')

raise ValueError('Invalid value type.')


def get_value_type(dictionary):
value = dictionary.get(VALUE)
if value is not None:
if value.get(BOOL_VALUE) is not None:
return bool
if value.get(STRING_VALUE) is not None:
return str
if value.get(INT_VALUE) is not None:
return int
if value.get(DOUBLE_VALUE) is not None:
return float

return None


class SettingType(IntEnum):
BOOL = 0
STRING = 1
INT = 2
DOUBLE = 3

@staticmethod
def from_type(object_type):
if object_type is bool:
return SettingType.BOOL
if object_type is str:
return SettingType.STRING
if object_type is int:
return SettingType.INT
if object_type is float:
return SettingType.DOUBLE

return None


def get_setting_type(setting):
setting_type = setting.get(SETTING_TYPE)
if setting_type is not None:
if setting_type == SettingType.BOOL:
return bool
if setting_type == SettingType.STRING:
return str
if setting_type == SettingType.INT:
return int
if setting_type == SettingType.DOUBLE:
return float

return None


class PrerequisiteComparator(IntEnum):
EQUALS = 0
NOT_EQUALS = 1


class SegmentComparator(IntEnum):
IS_IN = 0
IS_NOT_IN = 1


class Comparator(IntEnum):
IS_ONE_OF = 0
IS_NOT_ONE_OF = 1
CONTAINS_ANY_OF = 2
NOT_CONTAINS_ANY_OF = 3
IS_ONE_OF_SEMVER = 4
IS_NOT_ONE_OF_SEMVER = 5
LESS_THAN_SEMVER = 6
LESS_THAN_OR_EQUAL_SEMVER = 7
GREATER_THAN_SEMVER = 8
GREATER_THAN_OR_EQUAL_SEMVER = 9
EQUALS_NUMBER = 10
NOT_EQUALS_NUMBER = 11
LESS_THAN_NUMBER = 12
LESS_THAN_OR_EQUAL_NUMBER = 13
GREATER_THAN_NUMBER = 14
GREATER_THAN_OR_EQUAL_NUMBER = 15
IS_ONE_OF_HASHED = 16
IS_NOT_ONE_OF_HASHED = 17
BEFORE_DATETIME = 18
AFTER_DATETIME = 19
EQUALS_HASHED = 20
NOT_EQUALS_HASHED = 21
STARTS_WITH_ANY_OF_HASHED = 22
NOT_STARTS_WITH_ANY_OF_HASHED = 23
ENDS_WITH_ANY_OF_HASHED = 24
NOT_ENDS_WITH_ANY_OF_HASHED = 25
ARRAY_CONTAINS_ANY_OF_HASHED = 26
ARRAY_NOT_CONTAINS_ANY_OF_HASHED = 27
EQUALS = 28
NOT_EQUALS = 29
STARTS_WITH_ANY_OF = 30
NOT_STARTS_WITH_ANY_OF = 31
ENDS_WITH_ANY_OF = 32
NOT_ENDS_WITH_ANY_OF = 33
ARRAY_CONTAINS_ANY_OF = 34
ARRAY_NOT_CONTAINS_ANY_OF = 35


COMPARATOR_TEXTS = [
'IS ONE OF', # IS_ONE_OF
'IS NOT ONE OF', # IS_NOT_ONE_OF
'CONTAINS ANY OF', # CONTAINS_ANY_OF
'NOT CONTAINS ANY OF', # NOT_CONTAINS_ANY_OF
'IS ONE OF', # IS_ONE_OF_SEMVER
'IS NOT ONE OF', # IS_NOT_ONE_OF_SEMVER
'<', # LESS_THAN_SEMVER
'<=', # LESS_THAN_OR_EQUAL_SEMVER
'>', # GREATER_THAN_SEMVER
'>=', # GREATER_THAN_OR_EQUAL_SEMVER
'=', # EQUALS_NUMBER
'!=', # NOT_EQUALS_NUMBER
'<', # LESS_THAN_NUMBER
'<=', # LESS_THAN_OR_EQUAL_NUMBER
'>', # GREATER_THAN_NUMBER
'>=', # GREATER_THAN_OR_EQUAL_NUMBER
'IS ONE OF', # IS_ONE_OF_HASHED
'IS NOT ONE OF', # IS_NOT_ONE_OF_HASHED
'BEFORE', # BEFORE_DATETIME
'AFTER', # AFTER_DATETIME
'EQUALS', # EQUALS_HASHED
'NOT EQUALS', # NOT_EQUALS_HASHED
'STARTS WITH ANY OF', # STARTS_WITH_ANY_OF_HASHED
'NOT STARTS WITH ANY OF', # NOT_STARTS_WITH_ANY_OF_HASHED
'ENDS WITH ANY OF', # ENDS_WITH_ANY_OF_HASHED
'NOT ENDS WITH ANY OF', # NOT_ENDS_WITH_ANY_OF_HASHED
'ARRAY CONTAINS ANY OF', # ARRAY_CONTAINS_ANY_OF_HASHED
'ARRAY NOT CONTAINS ANY OF', # ARRAY_NOT_CONTAINS_ANY_OF_HASHED
'EQUALS', # EQUALS
'NOT EQUALS', # NOT_EQUALS
'STARTS WITH ANY OF', # STARTS_WITH_ANY_OF
'NOT STARTS WITH ANY OF', # NOT_STARTS_WITH_ANY_OF
'ENDS WITH ANY OF', # ENDS_WITH_ANY_OF
'NOT ENDS WITH ANY OF', # NOT_ENDS_WITH_ANY_OF
'ARRAY CONTAINS ANY OF', # ARRAY_CONTAINS_ANY_OF
'ARRAY NOT CONTAINS ANY OF' # ARRAY_NOT_CONTAINS_ANY_OF
]
COMPARISON_VALUES = [
STRING_LIST_VALUE, # IS_ONE_OF
STRING_LIST_VALUE, # IS_NOT_ONE_OF
STRING_LIST_VALUE, # CONTAINS_ANY_OF
STRING_LIST_VALUE, # NOT_CONTAINS_ANY_OF
STRING_LIST_VALUE, # IS_ONE_OF_SEMVER
STRING_LIST_VALUE, # IS_NOT_ONE_OF_SEMVER
STRING_VALUE, # LESS_THAN_SEMVER
STRING_VALUE, # LESS_THAN_OR_EQUAL_SEMVER
STRING_VALUE, # GREATER_THAN_SEMVER
STRING_VALUE, # GREATER_THAN_OR_EQUAL_SEMVER
DOUBLE_VALUE, # EQUALS_NUMBER
DOUBLE_VALUE, # NOT_EQUALS_NUMBER
DOUBLE_VALUE, # LESS_THAN_NUMBER
DOUBLE_VALUE, # LESS_THAN_OR_EQUAL_NUMBER
DOUBLE_VALUE, # GREATER_THAN_NUMBER
DOUBLE_VALUE, # GREATER_THAN_OR_EQUAL_NUMBER
STRING_LIST_VALUE, # IS_ONE_OF_HASHED
STRING_LIST_VALUE, # IS_NOT_ONE_OF_HASHED
DOUBLE_VALUE, # BEFORE_DATETIME
DOUBLE_VALUE, # AFTER_DATETIME
STRING_VALUE, # EQUALS_HASHED
STRING_VALUE, # NOT_EQUALS_HASHED
STRING_LIST_VALUE, # STARTS_WITH_ANY_OF_HASHED
STRING_LIST_VALUE, # NOT_STARTS_WITH_ANY_OF_HASHED
STRING_LIST_VALUE, # ENDS_WITH_ANY_OF_HASHED
STRING_LIST_VALUE, # NOT_ENDS_WITH_ANY_OF_HASHED
STRING_LIST_VALUE, # ARRAY_CONTAINS_ANY_OF_HASHED
STRING_LIST_VALUE, # ARRAY_NOT_CONTAINS_ANY_OF_HASHED
STRING_VALUE, # EQUALS
STRING_VALUE, # NOT_EQUALS
STRING_LIST_VALUE, # STARTS_WITH_ANY_OF
STRING_LIST_VALUE, # NOT_STARTS_WITH_ANY_OF
STRING_LIST_VALUE, # ENDS_WITH_ANY_OF
STRING_LIST_VALUE, # NOT_ENDS_WITH_ANY_OF
STRING_LIST_VALUE, # ARRAY_CONTAINS_ANY_OF
STRING_LIST_VALUE # ARRAY_NOT_CONTAINS_ANY_OF
]
SEGMENT_COMPARATOR_TEXTS = ['IS IN SEGMENT', 'IS NOT IN SEGMENT']
PREREQUISITE_COMPARATOR_TEXTS = ['EQUALS', 'DOES NOT EQUAL']


def extend_config_with_inline_salt_and_segment(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
object references to the locations where they are referenced and use these references instead of the indexes.
"""
salt = config.get(PREFERENCES, {}).get(SALT, '')
segments = config.get(SEGMENTS, [])
settings = config.get(FEATURE_FLAGS, {})
for setting in settings.values():
if not isinstance(setting, dict):
continue

# add salt
setting[INLINE_SALT] = salt

# add segment to the segment conditions
targeting_rules = setting.get(TARGETING_RULES, [])
for targeting_rule in targeting_rules:
conditions = targeting_rule.get(CONDITIONS, [])
for condition in conditions:

segment_condition = condition.get(SEGMENT_CONDITION)
if segment_condition:
segment_index = segment_condition.get(SEGMENT_INDEX)
segment = segments[segment_index]
segment_condition[INLINE_SEGMENT] = segment
Loading
Loading