Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions optimizely/optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
83 changes: 83 additions & 0 deletions optimizely/user_context.py
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a mutex control for attributes read/write?
"setAttributes" and attribute -reading for decide can happen in other threads. See C#-sdk as a good reference (optimizely/csharp-sdk#253)


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)
10 changes: 10 additions & 0 deletions tests/test_optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
34 changes: 34 additions & 0 deletions tests/test_user_context.py
Original file line number Diff line number Diff line change
@@ -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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have more tests:

  • construct with non-empty attributes
  • setAttribute can override the init attributes
  • attributes not changed once constructed when the caller copy is updated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 314