Skip to content

Commit 5f71922

Browse files
[FSSDK-11139] update: enable project config to track CMAB properties (#451)
* Add CmabDict type and update Experiment class to include cmab field * Refactor ProjectConfig to add attribute ID to key mapping and implement retrieval methods; update test for cmab field population
1 parent f8da261 commit 5f71922

File tree

4 files changed

+57
-2
lines changed

4 files changed

+57
-2
lines changed

optimizely/entities.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
if TYPE_CHECKING:
2424
# prevent circular dependenacy by skipping import at runtime
25-
from .helpers.types import ExperimentDict, TrafficAllocation, VariableDict, VariationDict
25+
from .helpers.types import ExperimentDict, TrafficAllocation, VariableDict, VariationDict, CmabDict
2626

2727

2828
class BaseEntity:
@@ -84,6 +84,7 @@ def __init__(
8484
audienceConditions: Optional[Sequence[str | list[str]]] = None,
8585
groupId: Optional[str] = None,
8686
groupPolicy: Optional[str] = None,
87+
cmab: Optional[CmabDict] = None,
8788
**kwargs: Any
8889
):
8990
self.id = id
@@ -97,6 +98,7 @@ def __init__(
9798
self.layerId = layerId
9899
self.groupId = groupId
99100
self.groupPolicy = groupPolicy
101+
self.cmab = cmab
100102

101103
def get_audience_conditions_or_ids(self) -> Sequence[str | list[str]]:
102104
""" Returns audienceConditions if present, otherwise audienceIds. """

optimizely/helpers/types.py

+6
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,9 @@ class IntegrationDict(BaseEntity):
109109
key: str
110110
host: str
111111
publicKey: str
112+
113+
114+
class CmabDict(BaseEntity):
115+
"""Cmab dict from parsed datafile json."""
116+
attributeIds: list[str]
117+
trafficAllocation: int

optimizely/project_config.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ def __init__(self, datafile: str | bytes, logger: Logger, error_handler: Any):
9494
self.attribute_key_map: dict[str, entities.Attribute] = self._generate_key_map(
9595
self.attributes, 'key', entities.Attribute
9696
)
97-
97+
self.attribute_id_to_key_map: dict[str, str] = {}
98+
for attribute in self.attributes:
99+
self.attribute_id_to_key_map[attribute['id']] = attribute['key']
98100
self.audience_id_map: dict[str, entities.Audience] = self._generate_key_map(
99101
self.audiences, 'id', entities.Audience
100102
)
@@ -510,6 +512,34 @@ def get_attribute_id(self, attribute_key: str) -> Optional[str]:
510512
self.error_handler.handle_error(exceptions.InvalidAttributeException(enums.Errors.INVALID_ATTRIBUTE))
511513
return None
512514

515+
def get_attribute_by_key(self, key: str) -> Optional[entities.Attribute]:
516+
""" Get attribute for the provided attribute key.
517+
518+
Args:
519+
key: Attribute key for which attribute is to be fetched.
520+
521+
Returns:
522+
Attribute corresponding to the provided attribute key.
523+
"""
524+
if key in self.attribute_key_map:
525+
return self.attribute_key_map[key]
526+
self.logger.error(f'Attribute with key:"{key}" is not in datafile.')
527+
return None
528+
529+
def get_attribute_key_by_id(self, id: str) -> Optional[str]:
530+
""" Get attribute key for the provided attribute id.
531+
532+
Args:
533+
id: Attribute id for which attribute is to be fetched.
534+
535+
Returns:
536+
Attribute key corresponding to the provided attribute id.
537+
"""
538+
if id in self.attribute_id_to_key_map:
539+
return self.attribute_id_to_key_map[id]
540+
self.logger.error(f'Attribute with id:"{id}" is not in datafile.')
541+
return None
542+
513543
def get_feature_from_key(self, feature_key: str) -> Optional[entities.FeatureFlag]:
514544
""" Get feature for the provided feature key.
515545

tests/test_config.py

+17
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,23 @@ def test_init(self):
154154
self.assertEqual(expected_variation_key_map, self.project_config.variation_key_map)
155155
self.assertEqual(expected_variation_id_map, self.project_config.variation_id_map)
156156

157+
def test_cmab_field_population(self):
158+
""" Test that the cmab field is populated correctly in experiments."""
159+
160+
# Deep copy existing datafile and add cmab config to the first experiment
161+
config_dict = copy.deepcopy(self.config_dict_with_multiple_experiments)
162+
config_dict['experiments'][0]['cmab'] = {'attributeIds': ['808797688', '808797689'], 'trafficAllocation': 4000}
163+
config_dict['experiments'][0]['trafficAllocation'] = []
164+
165+
opt_obj = optimizely.Optimizely(json.dumps(config_dict))
166+
project_config = opt_obj.config_manager.get_config()
167+
168+
experiment = project_config.get_experiment_from_key('test_experiment')
169+
self.assertEqual(experiment.cmab, {'attributeIds': ['808797688', '808797689'], 'trafficAllocation': 4000})
170+
171+
experiment_2 = project_config.get_experiment_from_key('test_experiment_2')
172+
self.assertIsNone(experiment_2.cmab)
173+
157174
def test_init__with_v4_datafile(self):
158175
""" Test that on creating object, properties are initiated correctly for version 4 datafile. """
159176

0 commit comments

Comments
 (0)