Skip to content

Commit b08156d

Browse files
Merge pull request #373 from algolia/feat/ab-testing
Feat/ab testing
2 parents 1098048 + 4c2f546 commit b08156d

File tree

6 files changed

+138
-23
lines changed

6 files changed

+138
-23
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@
55

66
<Contributors, please add your changes below this line>
77

8+
* Introduce AB Testing feature - PR [#408](https://github.com/algolia/algoliasearch-client-php/pull/#408)
9+
List/Create/Stop/Delete AB Tests programmatically
10+
Introduce new Analytics object, wrapper around the
11+
[Analytics API](https://www.algolia.com/doc/rest-api/analytics/) (more methods to come).
12+
13+
* 2 methods about taskID initially available in the `Index` moved to the `Client`.
14+
You could get some taskID from the engine without necessarily have an instance of Index,
15+
instead of instantiating an index that you won't need, you can now call wait_task and get_task_status on the client.
16+
The original methods on the index still work and are **not** deprecated.
17+
18+
```python
19+
client.wait_ask(index_name, taskID)
20+
client.get_task_status(index_name, taskID)
21+
```
22+
823
### 1.16.0 - 2018-06-07
924

1025
🎉 Note to contributors:

algoliasearch/analytics.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# -*- coding: utf-8 -*-
2+
from algoliasearch.helpers import AlgoliaException
3+
from .helpers import safe
4+
5+
6+
class Analytics:
7+
8+
def __init__(self, client, _transport):
9+
self.client = client
10+
self._transport = _transport
11+
self._transport.read_hosts = ['analytics.algolia.com']
12+
self._transport.write_hosts = ['analytics.algolia.com']
13+
14+
def get_ab_tests(self, args=None):
15+
params = {'offest': 0, 'limit': 10}
16+
if args is not None:
17+
params.update(args)
18+
19+
return self._req('/2/abtests', 'GET', params)
20+
21+
def add_ab_test(self, ab_test):
22+
return self._req('/2/abtests', 'POST', None, ab_test)
23+
24+
def get_ab_test(self, ab_test_id):
25+
if ab_test_id == '':
26+
raise AlgoliaException('ab_test_id cannot be empty')
27+
28+
return self._req('/2/abtests/%s' % safe(ab_test_id), 'GET')
29+
30+
def stop_ab_test(self, ab_test_id):
31+
if ab_test_id == '':
32+
raise AlgoliaException('ab_test_id cannot be empty')
33+
34+
return self._req('/2/abtests/%s/stop' % safe(ab_test_id), 'POST')
35+
36+
def delete_ab_test(self, ab_test_id):
37+
if ab_test_id == '':
38+
raise AlgoliaException('ab_test_id cannot be empty')
39+
40+
return self._req('/2/abtests/%s' % safe(ab_test_id), 'DELETE')
41+
42+
def wait_task(self, index_name, task_id, time_before_retry=100, request_options=None):
43+
return self.client.wait_task(index_name, task_id, time_before_retry, request_options)
44+
45+
def _req(self, path, method, params=None, data=None):
46+
return self._transport.req(False, path, method, params, data)

algoliasearch/client.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import base64
2828
import random
2929
import sys
30+
import time
31+
import copy
3032
from platform import python_version
3133

3234
try:
@@ -36,6 +38,7 @@
3638

3739
from .version import VERSION
3840
from .index import Index
41+
from .analytics import Analytics
3942

4043
from .transport import Transport
4144
from .helpers import deprecated
@@ -257,6 +260,31 @@ def batch(self, requests, request_options=None):
257260
path = '/1/indexes/*/batch'
258261
return self._req(False, path, 'POST', request_options, data=requests)
259262

263+
def wait_task(self, index_name, task_id, time_before_retry=100, request_options=None):
264+
"""
265+
Wait the publication of a task on the server.
266+
All server task are asynchronous and you can check with this method
267+
that the task is published.
268+
269+
@param index_name the index name associated with the taskID
270+
@param task_id the id of the task returned by server
271+
@param time_before_retry the time in milliseconds before retry (default = 100ms)
272+
@param request_options
273+
"""
274+
while True:
275+
task = self.get_task(index_name, task_id, request_options)
276+
if task['status'] == 'published':
277+
return task
278+
time.sleep(time_before_retry / 1000.0)
279+
280+
def get_task(self, index_name, task_id, request_options=None):
281+
path = '/1/indexes/%s/task/%d' % (safe(index_name), task_id)
282+
return self._req(True, path, 'GET', request_options)
283+
284+
def is_task_published(self, index_name, task_id, request_options=None):
285+
task = self.get_task(index_name, task_id, request_options)
286+
return task['status'] == 'published'
287+
260288
@deprecated
261289
def listIndexes(self):
262290
return self.list_indexes()
@@ -351,6 +379,9 @@ def init_index(self, index_name):
351379
"""
352380
return Index(self, index_name)
353381

382+
def init_analytics(self):
383+
return Analytics(self, copy.deepcopy(self._transport))
384+
354385
@deprecated
355386
def listUserKeys(self):
356387
return self.list_user_keys()

algoliasearch/index.py

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -718,31 +718,10 @@ def waitTask(self, task_id, time_before_retry=100):
718718
return self.wait_task(task_id, time_before_retry)
719719

720720
def wait_task(self, task_id, time_before_retry=100, request_options=None):
721-
"""
722-
Wait the publication of a task on the server.
723-
All server task are asynchronous and you can check with this method
724-
that the task is published.
725-
726-
@param task_id the id of the task returned by server
727-
@param time_before_retry the time in milliseconds before retry (default = 100ms)
728-
"""
729-
path = '/task/%d' % task_id
730-
while True:
731-
res = self._req(True, path, 'GET', request_options)
732-
if res['status'] == 'published':
733-
return res
734-
time.sleep(time_before_retry / 1000.0)
721+
return self.client.wait_task(self.index_name, task_id, time_before_retry, request_options)
735722

736723
def is_task_published(self, task_id, request_options=None):
737-
'''
738-
Return True if the task on the server has been published
739-
740-
@param task_id the id of the task returned by server
741-
'''
742-
743-
path = '/task/{0}'.format(task_id)
744-
res = self._req(True, path, 'GET', request_options)
745-
return res['status'] == 'published'
724+
return self.client.wait_task(self.index_name, task_id, request_options)
746725

747726
@deprecated
748727
def getSettings(self):

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ def client():
1414
return create_client()
1515

1616

17+
@pytest.fixture
18+
def analytics():
19+
return create_client().init_analytics()
20+
21+
1722
@pytest.fixture
1823
def index(client):
1924
idx = create_index(client)

tests/test_analytics.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from __future__ import print_function
2+
from datetime import datetime
3+
4+
from algoliasearch.helpers import AlgoliaException
5+
6+
7+
8+
def test_ab_test_features(analytics, double_indexes):
9+
ab_test = {
10+
'name': "Python client integration test",
11+
'variants': [
12+
{'index': double_indexes[0].index_name, 'trafficPercentage': 90},
13+
{'index': double_indexes[1].index_name, 'trafficPercentage': 10},
14+
],
15+
'endAt': datetime.utcnow().replace(day=29).strftime("%Y-%m-%dT%H:%M:%SZ"),
16+
}
17+
response = analytics.add_ab_test(ab_test)
18+
analytics.wait_task(response['index'], response['taskID'])
19+
ab_test_id = response['abTestID']
20+
21+
response = analytics.get_ab_test(ab_test_id)
22+
assert response['name'] == ab_test['name']
23+
24+
response = analytics.get_ab_tests()
25+
assert len(response['abtests']) == response['count']
26+
assert response['total'] >= response['count']
27+
28+
response = analytics.stop_ab_test(ab_test_id)
29+
analytics.wait_task(response['index'], response['taskID'])
30+
response = analytics.get_ab_test(ab_test_id)
31+
assert response['status'] == 'stopped'
32+
33+
response = analytics.delete_ab_test(ab_test_id)
34+
analytics.wait_task(response['index'], response['taskID'])
35+
try:
36+
response = analytics.get_ab_test(ab_test_id)
37+
assert False
38+
except AlgoliaException as e:
39+
assert True

0 commit comments

Comments
 (0)