Skip to content

Commit ceda6f7

Browse files
committed
Added basic framework code.
1 parent 508e761 commit ceda6f7

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

LICENSE

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
This program is free software: you can redistribute it and/or modify
3+
it under the terms of the GNU General Public License as published by
4+
the Free Software Foundation, either version 3 of the License, or
5+
(at your option) any later version.
6+
7+
This program is distributed in the hope that it will be useful,
8+
but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
GNU General Public License for more details.
11+
12+
You should have received a copy of the GNU General Public License
13+
along with this program. If not, see <http://www.gnu.org/licenses/>.
14+

README

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
This is a framework for A/B and multivariate testing, feature ramp up, and other web site experiments.
3+
4+
It's based on etsy/ab found here: github.com/etsy/ab
5+

__init__.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
3+
Program.....: ab.py
4+
Author......: Herb Lainchbury
5+
License.....: (C) 2011 Dynamic Solutions Inc.
6+
Description.: python split test library
7+
8+
"""
9+
10+
import traceback
11+
12+
class Variant:
13+
def __init__(self, name, properties=None):
14+
self.name = name
15+
self.properties = properties
16+
17+
def __repr__(self):
18+
return 'Variant(%s,%s)' % (repr(self.name),repr(self.properties))
19+
20+
class Selector:
21+
def select(self):
22+
pass
23+
24+
class FixedSelector(Selector):
25+
"""
26+
A variant selector that returns a fixed variant.
27+
28+
>>> selector = FixedSelector('one')
29+
>>> selector.select('123')
30+
'one'
31+
32+
"""
33+
def __init__(self, variant_name):
34+
self.variant_name = variant_name
35+
36+
def select(self, subject):
37+
return self.variant_name
38+
39+
class Logger:
40+
def log(self, test, variant, subject):
41+
pass
42+
43+
class MultiLogger:
44+
def __init__(self, loggers, system_logger):
45+
self.loggers = loggers
46+
self.system_logger = system_logger
47+
48+
def log(self, test, variant, subject):
49+
for i, logger in enumerate(self.loggers):
50+
try:
51+
logger.log(test, variant, subject)
52+
except:
53+
self.system_logger('ab logger %d failed: %s' % (i,traceback.format_exc))
54+
55+
class SubjectIDProvider:
56+
def get_id(self): pass
57+
58+
class Test:
59+
def __init__(self, name, variants, selector, id_provider, logger, enabled=True):
60+
"""
61+
Runs a test.
62+
63+
>>> test = Test('t1',[Variant('a'),Variant('b')],FixedSelector('a'),lambda: '001',Logger())
64+
65+
"""
66+
self.name = name
67+
self.variants = variants
68+
self.selector = selector
69+
self.id_provider = id_provider
70+
self.logger = logger
71+
self.enabled = enabled
72+
self.variant_lookup = dict((v.name, v) for v in variants)
73+
74+
def select(self,subject_id=None):
75+
"""
76+
Select one of the variants.
77+
78+
>>> test = Test('t1',[Variant('a'),Variant('b')],FixedSelector('a'),lambda: '001',Logger())
79+
>>> test.select()
80+
Variant('a',None)
81+
82+
>>> test.select().name
83+
'a'
84+
85+
"""
86+
if self.enabled:
87+
subject = subject_id or self.id_provider()
88+
key = self.selector.select(subject)
89+
if key in self.variant_lookup:
90+
self.logger.log(self.name,key,subject)
91+
return self.variant_lookup[key]
92+
93+
if __name__ == '__main__':
94+
import doctest
95+
doctest.testmod()
96+

0 commit comments

Comments
 (0)