Skip to content
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

Feat/ab testing #373

Merged
merged 2 commits into from
Jun 18, 2018
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@

<Contributors, please add your changes below this line>

* Introduce AB Testing feature - PR [#408](https://github.com/algolia/algoliasearch-client-php/pull/#408)
List/Create/Stop/Delete AB Tests programmatically
Introduce new Analytics object, wrapper around the
[Analytics API](https://www.algolia.com/doc/rest-api/analytics/) (more methods to come).

* 2 methods about taskID initially available in the `Index` moved to the `Client`.
You could get some taskID from the engine without necessarily have an instance of Index,
instead of instantiating an index that you won't need, you can now call wait_task and get_task_status on the client.
The original methods on the index still work and are **not** deprecated.

```python
client.wait_ask(index_name, taskID)
client.get_task_status(index_name, taskID)
```

### 1.16.0 - 2018-06-07

🎉 Note to contributors:
Expand Down
46 changes: 46 additions & 0 deletions algoliasearch/analytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from algoliasearch.helpers import AlgoliaException
from .helpers import safe


class Analytics:

def __init__(self, client, _transport):
self.client = client
self._transport = _transport
self._transport.read_hosts = ['analytics.algolia.com']
self._transport.write_hosts = ['analytics.algolia.com']

def get_ab_tests(self, args=None):
params = {'offest': 0, 'limit': 10}
if args is not None:
params.update(args)

return self._req('/2/abtests', 'GET', params)

def add_ab_test(self, ab_test):
return self._req('/2/abtests', 'POST', None, ab_test)

def get_ab_test(self, ab_test_id):
if ab_test_id == '':
raise AlgoliaException('ab_test_id cannot be empty')

return self._req('/2/abtests/%s' % safe(ab_test_id), 'GET')

def stop_ab_test(self, ab_test_id):
if ab_test_id == '':
raise AlgoliaException('ab_test_id cannot be empty')

return self._req('/2/abtests/%s/stop' % safe(ab_test_id), 'POST')

def delete_ab_test(self, ab_test_id):
if ab_test_id == '':
raise AlgoliaException('ab_test_id cannot be empty')

return self._req('/2/abtests/%s' % safe(ab_test_id), 'DELETE')

def wait_task(self, index_name, task_id, time_before_retry=100, request_options=None):
return self.client.wait_task(index_name, task_id, time_before_retry, request_options)

def _req(self, path, method, params=None, data=None):
return self._transport.req(False, path, method, params, data)
31 changes: 31 additions & 0 deletions algoliasearch/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import base64
import random
import sys
import time
import copy
from platform import python_version

try:
Expand All @@ -36,6 +38,7 @@

from .version import VERSION
from .index import Index
from .analytics import Analytics

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

def wait_task(self, index_name, task_id, time_before_retry=100, request_options=None):
"""
Wait the publication of a task on the server.
All server task are asynchronous and you can check with this method
that the task is published.

@param index_name the index name associated with the taskID
@param task_id the id of the task returned by server
@param time_before_retry the time in milliseconds before retry (default = 100ms)
@param request_options
"""
while True:
task = self.get_task(index_name, task_id, request_options)
if task['status'] == 'published':
return task
time.sleep(time_before_retry / 1000.0)

def get_task(self, index_name, task_id, request_options=None):
path = '/1/indexes/%s/task/%d' % (safe(index_name), task_id)
return self._req(True, path, 'GET', request_options)

def is_task_published(self, index_name, task_id, request_options=None):
task = self.get_task(index_name, task_id, request_options)
return task['status'] == 'published'

@deprecated
def listIndexes(self):
return self.list_indexes()
Expand Down Expand Up @@ -351,6 +379,9 @@ def init_index(self, index_name):
"""
return Index(self, index_name)

def init_analytics(self):
return Analytics(self, copy.deepcopy(self._transport))

@deprecated
def listUserKeys(self):
return self.list_user_keys()
Expand Down
25 changes: 2 additions & 23 deletions algoliasearch/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,31 +718,10 @@ def waitTask(self, task_id, time_before_retry=100):
return self.wait_task(task_id, time_before_retry)

def wait_task(self, task_id, time_before_retry=100, request_options=None):
"""
Wait the publication of a task on the server.
All server task are asynchronous and you can check with this method
that the task is published.

@param task_id the id of the task returned by server
@param time_before_retry the time in milliseconds before retry (default = 100ms)
"""
path = '/task/%d' % task_id
while True:
res = self._req(True, path, 'GET', request_options)
if res['status'] == 'published':
return res
time.sleep(time_before_retry / 1000.0)
return self.client.wait_task(self.index_name, task_id, time_before_retry, request_options)

def is_task_published(self, task_id, request_options=None):
'''
Return True if the task on the server has been published

@param task_id the id of the task returned by server
'''

path = '/task/{0}'.format(task_id)
res = self._req(True, path, 'GET', request_options)
return res['status'] == 'published'
return self.client.wait_task(self.index_name, task_id, request_options)

@deprecated
def getSettings(self):
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def client():
return create_client()


@pytest.fixture
def analytics():
return create_client().init_analytics()


@pytest.fixture
def index(client):
idx = create_index(client)
Expand Down
39 changes: 39 additions & 0 deletions tests/test_analytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import print_function
from datetime import datetime

from algoliasearch.helpers import AlgoliaException



def test_ab_test_features(analytics, double_indexes):
ab_test = {
'name': "Python client integration test",
'variants': [
{'index': double_indexes[0].index_name, 'trafficPercentage': 90},
{'index': double_indexes[1].index_name, 'trafficPercentage': 10},
],
'endAt': datetime.utcnow().replace(day=29).strftime("%Y-%m-%dT%H:%M:%SZ"),
}
response = analytics.add_ab_test(ab_test)
analytics.wait_task(response['index'], response['taskID'])
ab_test_id = response['abTestID']

response = analytics.get_ab_test(ab_test_id)
assert response['name'] == ab_test['name']

response = analytics.get_ab_tests()
assert len(response['abtests']) == response['count']
assert response['total'] >= response['count']

response = analytics.stop_ab_test(ab_test_id)
analytics.wait_task(response['index'], response['taskID'])
response = analytics.get_ab_test(ab_test_id)
assert response['status'] == 'stopped'

response = analytics.delete_ab_test(ab_test_id)
analytics.wait_task(response['index'], response['taskID'])
try:
response = analytics.get_ab_test(ab_test_id)
assert False
except AlgoliaException as e:
assert True