diff --git a/algoliasearch/index.py b/algoliasearch/index.py index 284b3d48e..9a7e88b5b 100644 --- a/algoliasearch/index.py +++ b/algoliasearch/index.py @@ -986,6 +986,81 @@ 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', 'POST', params, data=rules) + + 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_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? + 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=None, hitsPerPage=None): + """ + 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 + @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 '', + } + + 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): """Perform an HTTPS request with retry logic.""" 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)."""