Skip to content

Release 1.1.1 #46

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

Merged
merged 4 commits into from
Apr 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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