diff --git a/elasticutils/__init__.py b/elasticutils/__init__.py index f63474a..b7131db 100644 --- a/elasticutils/__init__.py +++ b/elasticutils/__init__.py @@ -778,6 +778,27 @@ def query_raw(self, query): """ return self._clone(next_step=('query_raw', query)) + def search_raw(self, search): + """ + Return a new S instance with a search_raw. + + :arg search: Python dict specifying the complete search to send + to Elasticsearch + + Example:: + + S().search_raw({'match': {'title': 'example'}}) + + + .. Note:: + + If there's a search_raw in your S, then that's your + search. All ``.search()``, ``.demote()``, ``.boost()`` and + anything else that affects the search clause is ignored. + + """ + return self._clone(next_step=('search_raw', search)) + def filter(self, *filters, **kw): """ Return a new S instance with filter args combined with @@ -1091,6 +1112,7 @@ def build_search(self): explain = False as_list = as_dict = False search_type = None + search_raw = None for action, value in self.steps: if action == 'order_by': @@ -1118,6 +1140,8 @@ def build_search(self): queries.append(value) elif action == 'query_raw': query_raw = value + elif action == 'search_raw': + search_raw = value elif action == 'demote': # value here is a tuple of (negative_boost, query) demote = value @@ -1148,79 +1172,84 @@ def build_search(self): else: raise NotImplementedError(action) - qs = {} - - # If there's a filters_raw, we use that. - if filters_raw: - qs['filter'] = filters_raw + if search_raw: + qs = search_raw + fields = set() else: - if len(filters) > 1: - qs['filter'] = {'and': filters} - elif filters: - qs['filter'] = filters[0] + qs = {} - # If there's a query_raw, we use that. Otherwise we use - # whatever we got from query and demote. - if query_raw: - qs['query'] = query_raw + # If there's a filters_raw, we use that. + if filters_raw: + qs['filter'] = filters_raw + else: + if len(filters) > 1: + qs['filter'] = {'and': filters} + elif filters: + qs['filter'] = filters[0] - else: - pq = self._process_queries(queries) + # If there's a query_raw, we use that. Otherwise we use + # whatever we got from query and demote. + if query_raw: + qs['query'] = query_raw - if demote is not None: - qs['query'] = { - 'boosting': { - 'negative': self._process_queries([demote[1]]), - 'negative_boost': demote[0] + else: + pq = self._process_queries(queries) + + if demote is not None: + qs['query'] = { + 'boosting': { + 'negative': self._process_queries([demote[1]]), + 'negative_boost': demote[0] + } } - } - if pq: - qs['query']['boosting']['positive'] = pq - - elif pq: - qs['query'] = pq - - if as_list: - fields = qs['fields'] = list(list_fields) if list_fields else ['*'] - elif as_dict: - fields = qs['fields'] = list(dict_fields) if dict_fields else ['*'] - else: - fields = set() + if pq: + qs['query']['boosting']['positive'] = pq - if facets: - qs['facets'] = facets - # Hunt for `facet_filter` shells and update those. We use - # None as a shell, so if it's explicitly set to None, then - # we update it. - for facet in facets.values(): - if facet.get('facet_filter', 1) is None and 'filter' in qs: - facet['facet_filter'] = qs['filter'] + elif pq: + qs['query'] = pq - if facets_raw: - qs.setdefault('facets', {}).update(facets_raw) + if as_list: + fields = qs['fields'] = list(list_fields) if list_fields else ['*'] + elif as_dict: + fields = qs['fields'] = list(dict_fields) if dict_fields else ['*'] + else: + fields = set() + + if facets: + qs['facets'] = facets + # Hunt for `facet_filter` shells and update those. We use + # None as a shell, so if it's explicitly set to None, then + # we update it. + for facet in facets.values(): + if facet.get('facet_filter', 1) is None and 'filter' in qs: + facet['facet_filter'] = qs['filter'] + + if facets_raw: + qs.setdefault('facets', {}).update(facets_raw) + + if sort: + qs['sort'] = sort + + if highlight_fields: + qs['highlight'] = self._build_highlight( + highlight_fields, highlight_options) + + if explain: + qs['explain'] = True + + for suggestion, (term, kwargs) in six.iteritems(suggestions): + qs.setdefault('suggest', {})[suggestion] = { + 'text': term, + 'term': { + 'field': kwargs.get('field', '_all'), + }, + } - if sort: - qs['sort'] = sort if self.start: qs['from'] = self.start if self.stop is not None: qs['size'] = self.stop - self.start - if highlight_fields: - qs['highlight'] = self._build_highlight( - highlight_fields, highlight_options) - - if explain: - qs['explain'] = True - - for suggestion, (term, kwargs) in six.iteritems(suggestions): - qs.setdefault('suggest', {})[suggestion] = { - 'text': term, - 'term': { - 'field': kwargs.get('field', '_all'), - }, - } - self.fields, self.as_list, self.as_dict = fields, as_list, as_dict self.search_type = search_type return qs diff --git a/elasticutils/tests/test_query.py b/elasticutils/tests/test_query.py index a28e64c..d68dabe 100644 --- a/elasticutils/tests/test_query.py +++ b/elasticutils/tests/test_query.py @@ -388,6 +388,11 @@ def test_query_raw(self): eq_(s.build_search(), {'query': {'match': {'title': 'example'}}}) + def test_search_raw(self): + s = self.get_s().search_raw({'query': {'match': {'title': 'example'}}}) + eq_(s.build_search(), + {'query': {'match': {'title': 'example'}}}) + def test_query_raw_overrides_everything(self): s = self.get_s().query_raw({'match': {'title': 'example'}}) s = s.query(foo__match='foo')