From b8074acb4673291b1d58f62f9699daf3ef805543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Ercolanelli?= Date: Thu, 29 Jun 2017 17:27:20 +0200 Subject: [PATCH 1/3] feat(rules): endpoint implementation --- algoliasearch/index.py | 70 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/algoliasearch/index.py b/algoliasearch/index.py index 284b3d48e..fd74e911f 100644 --- a/algoliasearch/index.py +++ b/algoliasearch/index.py @@ -986,6 +986,76 @@ def search_for_facet_values(self, facet_name, facet_query, query=None): def search_facet(self, facet_name, facet_query, query=None): return self.search_for_facet_values(facet_name, facet_query, query) + def save_rule(self, rule, forward_to_replicas=False): + """ + Save a new rule in the index. + @param rule the body of the rule to upload as a python dictionary. + the dictionary must contain an objectID key. + @param forward_to_replicas should the rule also be applied to the replicas + of this index? Default is False. + """ + if 'objectID' not in rule: + raise AlgoliaException('missing objectID in rule body') + params = {'forwardToReplicas': forward_to_replicas} + return self._req(False, '/rules/%s' % str(rule['objectID']), 'PUT', params, rule) + + def batch_rules(self, rules, forward_to_replicas=False, clear_existing_rules=False): + """ + Save a batch of new rules + @param rules batch of rules to be added to the index. Each rule object must contain + its own objectID. + @param forward_to_replicas should the rules also be applied to the replicas + of this index? Default is False. + @param clear_existing_rules should all the existing rules in the index be cleared + before saving this batch? Default is False. + """ + params = {'forwardToReplicas': forward_to_replicas, 'clearExistingRules': clear_existing_rules} + return self._req(False, '/rules/batch', 'PUT', params, data) + + def read_rule(self, objectID): + """ + Retrieve a rule from the index with the specified objectID. + @param objectID The objectID of the rule to retrieve + """ + return self._req(False, '/rules/%s' % str(objectID), 'GET') + + def delete_rule(self, objectID, forward_to_replicas=False): + """ + Delete the rule with identified by the given objectID. + @param objectID the objectID of the rule to delete + @param forward_to_replicas should the rule also be + deleted from the replicas of the index? + Default is False. + """ + params =- {'forwardToReplicas': forward_to_replicas} + return self._req(False, '/rules/%s' % str(objectID), 'DELETE', params) + + def clear_rule(self, forward_to_replicas=False): + """ + Clear all the rules of an index. + @param forward_to_replicas Should the rules in the replicas also be cleared? + Default is False. + """ + params = {'forwardToReplicas': forward_to_replicas} + return self._req(False, '/rules/clear', 'POST', params) + + def search_rules(self, query=None, anchoring=None, context=None, page=0, hitsPerPage=20): + """ + Search for a rule inside the index. + @param query Full text search query + @param anchoring Research the search to rules with a specific anchoring type + @param context Restrict the search to rules with a specific context + @param page Requested page (0 based). Default 0. + @param hitsPerPage Maximum number of hits per page. Default 20. + """ + params = { + 'query': query if query else '', + 'anchoring': anchoring if anchoring else '', + 'context': context if context else '', + 'page': page, + 'hitsPerPage': hitsPerPage + } + return self._req(False, '/rules/search', 'POST', params) def _req(self, is_search, path, meth, params=None, data=None): """Perform an HTTPS request with retry logic.""" From cd3f517b0df9bdee22a38ab5a2ec78a7d43d8eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Ercolanelli?= Date: Mon, 10 Jul 2017 14:20:36 +0200 Subject: [PATCH 2/3] feat(rules): add tests and fix bugs --- algoliasearch/index.py | 8 ++--- tests/helpers.py | 13 ++++++++ tests/test_index.py | 69 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/algoliasearch/index.py b/algoliasearch/index.py index fd74e911f..f95e1f1f8 100644 --- a/algoliasearch/index.py +++ b/algoliasearch/index.py @@ -1010,7 +1010,7 @@ def batch_rules(self, rules, forward_to_replicas=False, clear_existing_rules=Fal before saving this batch? Default is False. """ params = {'forwardToReplicas': forward_to_replicas, 'clearExistingRules': clear_existing_rules} - return self._req(False, '/rules/batch', 'PUT', params, data) + return self._req(False, '/rules/batch', 'POST', params, data=rules) def read_rule(self, objectID): """ @@ -1027,10 +1027,10 @@ def delete_rule(self, objectID, forward_to_replicas=False): deleted from the replicas of the index? Default is False. """ - params =- {'forwardToReplicas': forward_to_replicas} + params = {'forwardToReplicas': forward_to_replicas} return self._req(False, '/rules/%s' % str(objectID), 'DELETE', params) - def clear_rule(self, forward_to_replicas=False): + def clear_rules(self, forward_to_replicas=False): """ Clear all the rules of an index. @param forward_to_replicas Should the rules in the replicas also be cleared? @@ -1041,7 +1041,7 @@ def clear_rule(self, forward_to_replicas=False): def search_rules(self, query=None, anchoring=None, context=None, page=0, hitsPerPage=20): """ - Search for a rule inside the index. + Search for rules inside the index. @param query Full text search query @param anchoring Research the search to rules with a specific anchoring type @param context Restrict the search to rules with a specific context diff --git a/tests/helpers.py b/tests/helpers.py index fe68cd7e6..565e615b2 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -36,6 +36,19 @@ def generate_id(self): self._generated_id.append(new_id) return str(new_id) +def get_rule_stub(objectID='my-rule'): + return { + 'objectID': objectID, + 'condition': { + 'pattern': 'some text', + 'anchoring': 'is' + }, + 'consequence': { + 'params': { + 'query': 'other text' + } + } + } def get_api_client(): if 'APPENGINE_RUNTIME' in os.environ: diff --git a/tests/test_index.py b/tests/test_index.py index 3be530be6..98f7301b7 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -16,9 +16,7 @@ from algoliasearch.client import MAX_API_KEY_LENGTH from algoliasearch.helpers import AlgoliaException -from tests.helpers import safe_index_name -from tests.helpers import get_api_client -from tests.helpers import FakeData +from tests.helpers import safe_index_name, get_api_client, FakeData, get_rule_stub class IndexTest(unittest.TestCase): @@ -191,6 +189,71 @@ def test_facet_search(self): self.assertEqual(facetHits[0]['value'], 'Peanuts') self.assertEqual(facetHits[0]['count'], 1) + def test_save_and_get_rule(self): + rule = get_rule_stub() + res = self.index.save_rule(rule) + self.index.wait_task(res['taskID']) + self.assertEqual(rule, self.index.read_rule('my-rule')) + + @unittest.expectedFailure + def test_delete_rule(self): + rule = get_rule_stub() + res = self.index.save_rule(rule) + self.index.wait_task(res['taskID']) + + res = self.index.delete_rule('my-rule') + self.index.wait_task(res['taskID']) + + self.index.read_rule('my-rule') + + def test_search_rules(self): + rule = get_rule_stub() + rule2 = get_rule_stub('my-second-rule') + + self.index.save_rule(rule); + res = self.index.save_rule(rule2); + self.index.wait_task(res['taskID']) + + rules = self.index.search_rules() + self.assertEqual(2, rules['nbHits']) + + def test_batch_and_clear_rules(self): + rule = get_rule_stub() + rule2 = get_rule_stub('my-second-rule') + + res = self.index.batch_rules([rule, rule2]) + self.index.wait_task(res['taskID']) + + self.assertEqual(self.index.read_rule('my-rule'), rule) + self.assertEqual(self.index.read_rule('my-second-rule'), rule2) + + res = self.index.clear_rules() + self.index.wait_task(res['taskID']) + + rules = self.index.search_rules() + self.assertEqual(rules['nbHits'], 0) + + + def test_batch_and_clear_existing(self): + rule = get_rule_stub() + rule2 = get_rule_stub('my-second-rule') + rule3 = get_rule_stub('my-third-rule') + rule4 = get_rule_stub('my-fourth-rule') + + res = self.index.batch_rules([rule, rule2, rule3, rule4]) + self.index.wait_task(res['taskID']) + + res = self.index.batch_rules([rule3, rule4], False, True) + self.index.wait_task(res['taskID']) + + rules = self.index.search_rules() + self.assertEqual(rules['nbHits'], 2) + del rules['hits'][0]['_highlightResult'] + del rules['hits'][1]['_highlightResult'] + + self.assertEqual(rules['hits'], [rule3, rule4]) # alphabetical ordering of objectID + + class IndexWithReadOnlyDataTest(IndexTest): """Tests that use one index with initial data (read only).""" From 998418071f7ff662529a5f10268729a42ced8e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Ercolanelli?= Date: Mon, 10 Jul 2017 14:24:15 +0200 Subject: [PATCH 3/3] refact(rules): do not hardcode page and nbHits --- algoliasearch/index.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/algoliasearch/index.py b/algoliasearch/index.py index f95e1f1f8..9a7e88b5b 100644 --- a/algoliasearch/index.py +++ b/algoliasearch/index.py @@ -1039,7 +1039,7 @@ def clear_rules(self, forward_to_replicas=False): params = {'forwardToReplicas': forward_to_replicas} return self._req(False, '/rules/clear', 'POST', params) - def search_rules(self, query=None, anchoring=None, context=None, page=0, hitsPerPage=20): + def search_rules(self, query=None, anchoring=None, context=None, page=None, hitsPerPage=None): """ Search for rules inside the index. @param query Full text search query @@ -1052,9 +1052,14 @@ def search_rules(self, query=None, anchoring=None, context=None, page=0, hitsPer 'query': query if query else '', 'anchoring': anchoring if anchoring else '', 'context': context if context else '', - 'page': page, - 'hitsPerPage': hitsPerPage } + + if page is not None: + params['page'] = page + + if hitsPerPage is not None: + params['hitsPerPage'] = hitsPerPage + return self._req(False, '/rules/search', 'POST', params) def _req(self, is_search, path, meth, params=None, data=None):