Skip to content

Commit

Permalink
Merge pull request #46 from optimizely/devel
Browse files Browse the repository at this point in the history
Release 1.1.1
  • Loading branch information
aliabbasrizvi committed Apr 3, 2017
2 parents e4e27f4 + c0ccc97 commit 6ba07f3
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 1,351 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.1.1
- Updated datafile parsing to be able to handle additional fields.
- Deprecated Classic project support.

# 1.1.0
- Included datafile revision information in log events.
- Added event tags to track API to allow users to pass in event metadata.
Expand Down
16 changes: 7 additions & 9 deletions optimizely/entities.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2016, Optimizely
# Copyright 2016-2017, 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
Expand All @@ -19,15 +19,14 @@ def __eq__(self, other):

class Attribute(BaseEntity):

def __init__(self, id, key, segmentId=None):
def __init__(self, id, key, **kwargs):
self.id = id
self.key = key
self.segmentId = segmentId


class Audience(BaseEntity):

def __init__(self, id, name, conditions, conditionStructure=None, conditionList=None):
def __init__(self, id, name, conditions, conditionStructure=None, conditionList=None, **kwargs):
self.id = id
self.name = name
self.conditions = conditions
Expand All @@ -37,7 +36,7 @@ def __init__(self, id, name, conditions, conditionStructure=None, conditionList=

class Event(BaseEntity):

def __init__(self, id, key, experimentIds):
def __init__(self, id, key, experimentIds, **kwargs):
self.id = id
self.key = key
self.experimentIds = experimentIds
Expand All @@ -46,7 +45,7 @@ def __init__(self, id, key, experimentIds):
class Experiment(BaseEntity):

def __init__(self, id, key, status, audienceIds, variations, forcedVariations,
trafficAllocation, layerId=None, groupId=None, groupPolicy=None, percentageIncluded=None):
trafficAllocation, layerId, groupId=None, groupPolicy=None, **kwargs):
self.id = id
self.key = key
self.status = status
Expand All @@ -57,12 +56,11 @@ def __init__(self, id, key, status, audienceIds, variations, forcedVariations,
self.layerId = layerId
self.groupId = groupId
self.groupPolicy = groupPolicy
self.percentageIncluded = percentageIncluded


class Group(BaseEntity):

def __init__(self, id, policy, experiments, trafficAllocation):
def __init__(self, id, policy, experiments, trafficAllocation, **kwargs):
self.id = id
self.policy = policy
self.experiments = experiments
Expand All @@ -71,6 +69,6 @@ def __init__(self, id, policy, experiments, trafficAllocation):

class Variation(BaseEntity):

def __init__(self, id, key):
def __init__(self, id, key, **kwargs):
self.id = id
self.key = key
184 changes: 1 addition & 183 deletions optimizely/event_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
from abc import abstractmethod
from abc import abstractproperty

from . import exceptions
from . import project_config
from . import version
from .helpers import enums
from .helpers import event_tag_utils


Expand Down Expand Up @@ -99,163 +96,7 @@ def _add_common_params(self, user_id, attributes):
self._add_time()


class EventBuilderV1(BaseEventBuilder):
""" Class which encapsulates methods to build events for tracking
impressions and conversions using the old endpoint. """

# Attribute mapping format
ATTRIBUTE_PARAM_FORMAT = '{segment_prefix}{segment_id}'
# Experiment mapping format
EXPERIMENT_PARAM_FORMAT = '{experiment_prefix}{experiment_id}'
# Event API format
OFFLINE_API_PATH = 'https://{project_id}.log.optimizely.com/event'


class EventParams(object):
ACCOUNT_ID = 'd'
PROJECT_ID = 'a'
EXPERIMENT_PREFIX = 'x'
GOAL_ID = 'g'
GOAL_NAME = 'n'
END_USER_ID = 'u'
EVENT_VALUE = 'v'
SEGMENT_PREFIX = 's'
SOURCE = 'src'
TIME = 'time'

def _add_attributes(self, attributes):
""" Add attribute(s) information to the event.
Args:
attributes: Dict representing user attributes and values which need to be recorded.
"""

if not attributes:
return

for attribute_key in attributes.keys():
attribute_value = attributes.get(attribute_key)
# Omit falsy attribute values
if attribute_value:
attribute = self.config.get_attribute(attribute_key)
if attribute:
self.params[self.ATTRIBUTE_PARAM_FORMAT.format(
segment_prefix=self.EventParams.SEGMENT_PREFIX, segment_id=attribute.segmentId)] = attribute_value

def _add_source(self):
""" Add source information to the event. """

self.params[self.EventParams.SOURCE] = 'python-sdk-{version}'.format(version=version.__version__)

def _add_time(self):
""" Add time information to the event. """

self.params[self.EventParams.TIME] = int(time.time())

def _add_impression_goal(self, experiment):
""" Add impression goal information to the event.
Args:
experiment: Object representing experiment being activated.
"""

# For tracking impressions, goal ID is set equal to experiment ID of experiment being activated
self.params[self.EventParams.GOAL_ID] = experiment.id
self.params[self.EventParams.GOAL_NAME] = 'visitor-event'

def _add_experiment(self, experiment, variation_id):
""" Add experiment to variation mapping to the impression event.
Args:
experiment: Object representing experiment being activated.
variation_id: ID for variation which would be presented to user.
"""

self.params[self.EXPERIMENT_PARAM_FORMAT.format(experiment_prefix=self.EventParams.EXPERIMENT_PREFIX,
experiment_id=experiment.id)] = variation_id

def _add_experiment_variation_params(self, user_id, valid_experiments):
""" Maps experiment and corresponding variation as parameters to be used in the event tracking call.
Args:
user_id: ID for user.
valid_experiments: List of tuples representing valid experiments for the event.
"""

for experiment in valid_experiments:
variation = self.bucketer.bucket(experiment, user_id)
if variation:
self.params[self.EXPERIMENT_PARAM_FORMAT.format(experiment_prefix=self.EventParams.EXPERIMENT_PREFIX,
experiment_id=experiment.id)] = variation.id

def _add_conversion_goal(self, event_key, event_value):
""" Add conversion goal information to the event.
Args:
event_key: Event key representing the event which needs to be recorded.
event_value: Value associated with the event. Can be used to represent revenue in cents.
"""

event = self.config.get_event(event_key)

if not event:
return

event_ids = event.id

if event_value:
event_ids = '{goal_id},{revenue_goal_id}'.format(goal_id=event.id,
revenue_goal_id=self.config.get_revenue_goal().id)
self.params[self.EventParams.EVENT_VALUE] = event_value

self.params[self.EventParams.GOAL_ID] = event_ids
self.params[self.EventParams.GOAL_NAME] = event_key

def create_impression_event(self, experiment, variation_id, user_id, attributes):
""" Create impression Event to be sent to the logging endpoint.
Args:
experiment: Object representing experiment for which impression needs to be recorded.
variation_id: ID for variation which would be presented to user.
user_id: ID for user.
attributes: Dict representing user attributes and values which need to be recorded.
Returns:
Event object encapsulating the impression event.
"""

self.params = {}
self._add_common_params(user_id, attributes)
self._add_impression_goal(experiment)
self._add_experiment(experiment, variation_id)
return Event(self.OFFLINE_API_PATH.format(project_id=self.params[self.EventParams.PROJECT_ID]),
self.params)

def create_conversion_event(self, event_key, user_id, attributes, event_tags, valid_experiments):
""" Create conversion Event to be sent to the logging endpoint.
Args:
event_key: Event key representing the event which needs to be recorded.
user_id: ID for user.
attributes: Dict representing user attributes and values.
event_tags: Dict representing metadata associated with the event.
valid_experiments: List of tuples representing valid experiments for the event.
Returns:
Event object encapsulating the conversion event.
"""

event_value = event_tag_utils.get_revenue_value(event_tags)

self.params = {}
self._add_common_params(user_id, attributes)
self._add_conversion_goal(event_key, event_value)
self._add_experiment_variation_params(user_id, valid_experiments)
return Event(self.OFFLINE_API_PATH.format(project_id=self.params[self.EventParams.PROJECT_ID]),
self.params)


class EventBuilderV2(BaseEventBuilder):
class EventBuilder(BaseEventBuilder):
""" Class which encapsulates methods to build events for tracking
impressions and conversions using the new endpoints. """

Expand Down Expand Up @@ -437,26 +278,3 @@ def create_conversion_event(self, event_key, user_id, attributes, event_tags, va
self.params,
http_verb=self.HTTP_VERB,
headers=self.HTTP_HEADERS)


def get_event_builder(config, bucketer):
""" Helper method to get appropriate EventBuilder class based on the version of the datafile.
Args:
config: Object representing the project's configuration.
bucketer: Object representing the bucketer.
Returns:
Event builder based on the version of the datafile.
Raises:
Exception if provided datafile has unsupported version.
"""

config_version = config.get_version()
if config_version == project_config.V1_CONFIG_VERSION:
return EventBuilderV1(config, bucketer)
if config_version == project_config.V2_CONFIG_VERSION:
return EventBuilderV2(config, bucketer)

raise exceptions.InvalidInputException(enums.Errors.UNSUPPORTED_DATAFILE_VERSION)
Loading

0 comments on commit 6ba07f3

Please sign in to comment.