Skip to content
Browse files

Re-work GAE/Bingo find alternatives for user api

We want to be explicit about not opting a participant into an experiment if just querying for her alternative in the experiment. It could lead to side effects that are unintended.
  • Loading branch information...
1 parent b9246e0 commit 572e8869e4a6cbf126321285b207369e977c9dae @benkomalo benkomalo committed Nov 21, 2011
Showing with 57 additions and 23 deletions.
  1. +8 −8 cache.py
  2. +42 −11 gae_bingo.py
  3. +7 −4 identity.py
View
16 cache.py
@@ -268,12 +268,12 @@ def key_for_identity(ident):
return BingoIdentityCache.MEMCACHE_KEY % ident
@staticmethod
- def get(user_data=None):
+ def get(identity_val=None):
init_request_cache_from_memcache()
- key = BingoIdentityCache.key_for_identity(identity(user_data))
+ key = BingoIdentityCache.key_for_identity(identity(identity_val))
if not REQUEST_CACHE.get(key):
- REQUEST_CACHE[key] = BingoIdentityCache.load_from_datastore()
+ REQUEST_CACHE[key] = BingoIdentityCache.load_from_datastore(identity_val)
return REQUEST_CACHE[key]
@@ -331,13 +331,13 @@ def persist_buckets_to_datastore():
memcache.set(key, [])
@staticmethod
- def load_from_datastore():
- bingo_identity_cache = _GAEBingoIdentityRecord.load(identity())
+ def load_from_datastore(identity_val=None):
+ bingo_identity_cache = _GAEBingoIdentityRecord.load(identity(identity_val))
if bingo_identity_cache:
bingo_identity_cache.purge()
bingo_identity_cache.dirty = True
- bingo_identity_cache.store_for_identity_if_dirty(identity())
+ bingo_identity_cache.store_for_identity_if_dirty(identity(identity_val))
else:
bingo_identity_cache = BingoIdentityCache()
@@ -371,8 +371,8 @@ def convert_in(self, experiment_name):
self.converted_tests[experiment_name] += 1
self.dirty = True
-def bingo_and_identity_cache(user_data=None):
- return BingoCache.get(), BingoIdentityCache.get(user_data)
+def bingo_and_identity_cache(identity_val=None):
+ return BingoCache.get(), BingoIdentityCache.get(identity_val)
def store_if_dirty():
# Only load from request cache here -- if it hasn't been loaded from memcache previously, it's not dirty.
View
53 gae_bingo.py
@@ -14,10 +14,9 @@ def ab_test(canonical_name,
alternative_params = None,
conversion_name = None,
conversion_type = ConversionTypes.Binary,
- family_name = None,
- user_data = None):
+ family_name = None):
- bingo_cache, bingo_identity_cache = bingo_and_identity_cache(user_data)
+ bingo_cache, bingo_identity_cache = bingo_and_identity_cache()
if canonical_name not in bingo_cache.experiments:
@@ -98,9 +97,8 @@ def ab_test(canonical_name,
else:
- alternative = find_alternative_for_user(experiment.hashable_name,
- alternatives,
- user_data=user_data)
+ alternative = _find_alternative_for_user(experiment.hashable_name,
+ alternatives)
if experiment.name not in bingo_identity_cache.participating_tests:
bingo_identity_cache.participate_in(experiment.name)
@@ -148,7 +146,7 @@ def score_conversion(experiment_name):
# Only allow multiple conversions for ConversionTypes.Counting experiments
return
- alternative = find_alternative_for_user(experiment.hashable_name, bingo_cache.get_alternatives(experiment_name))
+ alternative = _find_alternative_for_user(experiment.hashable_name, bingo_cache.get_alternatives(experiment_name))
alternative.increment_conversions()
@@ -207,9 +205,42 @@ def resume_experiment(canonical_name):
experiment.live = True
bingo_cache.update_experiment(experiment)
-def find_alternative_for_user(experiment_hashable_name,
- alternatives,
- user_data=None):
+def find_alternative_for_user(canonical_name, identity_val):
+ """ Returns the alternative that the specified bingo identity is opted into.
+ If the experiment does not exist, is not live, or if the identity has not
+ been opted in, returns None. This does not affect the experiment in any way.
+
+ If an experiment has multiple instances (because it was created with
+ different alternative sets), will operate on the last experiment.
+
+ canonical_name -- the canonical name of the experiment
+ identity_val -- a string or instance of GAEBingoIdentity
+
+ """
+
+ bingo_cache, bingo_identity_cache = bingo_and_identity_cache(identity_val)
+ experiment_names = bingo_cache.get_experiment_names_by_canonical_name(canonical_name)
+
+ if not experiment_names:
+ return None
+
+ experiment_name = experiment_names[-1]
+
+ if experiment_name not in bingo_identity_cache.participating_tests:
+ return None
+
+ experiment = bingo_cache.get_experiment(experiment_name)
+
+ if not experiment or not experiment.live:
+ return None
+
+ return _find_alternative_for_user(experiment.hashable_name,
+ bingo_cache.get_alternatives(experiment_name),
+ identity_val).content
+
+def _find_alternative_for_user(experiment_hashable_name,
+ alternatives,
+ identity_val=None):
if can_control_experiments():
# If gae_bingo administrator, allow possible override of alternative
@@ -222,7 +253,7 @@ def find_alternative_for_user(experiment_hashable_name,
if len(matches) == 1:
return matches[0]
- return modulo_choose(experiment_hashable_name, alternatives, identity(user_data))
+ return modulo_choose(experiment_hashable_name, alternatives, identity(identity_val))
def modulo_choose(experiment_hashable_name, alternatives, identity):
alternatives_weight = sum(map(lambda alternative: alternative.weight, alternatives))
View
11 identity.py
@@ -23,16 +23,19 @@ def logged_in_bingo_identity():
return LOGGED_IN_IDENTITY_CACHE
-def identity(user_data=None):
+def identity(identity_val=None):
""" Determines the Bingo identity for the specified user. If no user
is specified, this will attempt to infer one based on cookies/logged in user
+
+ identity_val -- a string or instance of GAEBingoIdentityModel specifying
+ which bingo identity to retrieve.
"""
global IDENTITY_CACHE
- if user_data:
- # Don't cache for arbitrarily passed in user_data
- return bingo_identity_for_value(user_data, associate_with_cookie=False)
+ if identity_val:
+ # Don't cache for arbitrarily passed in identity_val
+ return bingo_identity_for_value(identity_val, associate_with_cookie=False)
if IDENTITY_CACHE is None:

0 comments on commit 572e886

Please sign in to comment.
Something went wrong with that request. Please try again.