diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index 400db1900..70bf343d6 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -27,6 +27,7 @@ from .helpers import enums, validator from .notification_center import NotificationCenter from .optimizely_config import OptimizelyConfigService +from .user_context import UserContext class Optimizely(object): @@ -911,3 +912,26 @@ def get_optimizely_config(self): return self.config_manager.optimizely_config return OptimizelyConfigService(project_config).get_config() + + def create_user_context(self, user_id, attributes=None): + """ + We do not check for is_valid here as a user context can be created successfully + even when the SDK is not fully configured. + + Args: + user_id: string to use as user id for user context + attributes: dictionary of attributes or None + + Returns: + UserContext instance or None if the user id or attributes are invalid. + """ + if not isinstance(user_id, string_types): + self.logger.error(enums.Errors.INVALID_INPUT.format('user_id')) + return None + + if attributes is not None and type(attributes) is not dict: + self.logger.error(enums.Errors.INVALID_INPUT.format('attributes')) + return None + + user_context = UserContext(self, user_id, attributes) + return user_context diff --git a/optimizely/user_context.py b/optimizely/user_context.py new file mode 100644 index 000000000..56984317a --- /dev/null +++ b/optimizely/user_context.py @@ -0,0 +1,83 @@ +# Copyright 2020, Optimizely and contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +class UserContext(object): + """ + Representation of an Optimizely User Context using which APIs are to be called. + """ + + def __init__(self, optimizely_client, user_id, user_attributes=None): + """ Create an instance of the Optimizely User Context. + + Args: + optimizely_client: client used when calling decisions for this user context + user_id: user id of this user context + user_attributes: user attributes to use for this user context + + Returns: + UserContext instance + """ + + self.client = optimizely_client + self.user_id = user_id + self.user_attributes = user_attributes.copy() if user_attributes else {} + + def set_attribute(self, attribute_key, attribute_value): + """ + sets a attribute by key for this user context. + Args: + attribute_key: key to use for attribute + attribute_value: attribute value + + Returns: + None + """ + self.user_attributes[attribute_key] = attribute_value + + def decide(self, key, options=None): + """ + TODO: call optimizely_clieint.decide + Args: + key: + options: + + Returns: + + """ + + def decide_for_keys(self, keys, options=None): + """ + TODO: call optimizely_client.decide_for_keys + Args: + keys: + options: + + Returns: + + """ + + def decide_all(self, options=None): + """ + TODO: call optimize_client.decide_all + Args: + options: + + Returns: + + """ + + def track_event(self, event_key, event_tags=None): + self.optimizely_client.track(event_key, self.user_id, self.user_attributes, event_tags) diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index 4e3b9cfe2..a62679367 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -4957,3 +4957,13 @@ def test_get_forced_variation__invalid_user_id(self): self.assertIsNone(self.optimizely.get_forced_variation('test_experiment', 99)) mock_client_logging.error.assert_called_once_with('Provided "user_id" is in an invalid format.') + + def test_user_context_invalid_user_id(self): + """ + Tests user context. + """ + user_ids = [5, 5.5, None, True, [], {}] + + for u in user_ids: + uc = self.optimizely.create_user_context(u) + self.assertIsNone(uc, "invalid user id should return none") diff --git a/tests/test_user_context.py b/tests/test_user_context.py new file mode 100644 index 000000000..713bb3db5 --- /dev/null +++ b/tests/test_user_context.py @@ -0,0 +1,34 @@ +# Copyright 2020, Optimizely +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import base +from optimizely import logger +from optimizely.user_context import UserContext + + +class UserContextTests(base.BaseTest): + def setUp(self): + base.BaseTest.setUp(self, 'config_dict_with_multiple_experiments') + self.logger = logger.NoOpLogger() + + def test_user_context(self): + """ + tests user context creating and attributes + """ + uc = UserContext(self.optimizely, "test_user") + self.assertEqual(uc.user_attributes, {}, "should have created default empty") + self.assertEqual(uc.user_id, "test_user", "should have same user id") + uc.set_attribute("key", "value") + self.assertEqual(uc.user_attributes["key"], "value", "should have added attribute") + uc.set_attribute("key", "value2") + self.assertEqual(uc.user_attributes["key"], "value2", "should have new attribute")