Collapse support added to AsyncSearch helper (#769) (#827)

Signed-off-by: Radoslaw Kuczynski <radoslaw.kuczynski@infermedica.com>
This commit is contained in:
CorneeSean
2024-09-26 14:20:25 +02:00
committed by GitHub
parent 45d817289a
commit 3087686fff
3 changed files with 87 additions and 3 deletions
+1
View File
@@ -3,6 +3,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased]
### Added
- Added `AsyncSearch#collapse` ([827](https://github.com/opensearch-project/opensearch-py/pull/827))
### Changed
### Deprecated
### Removed
+36 -3
View File
@@ -8,7 +8,7 @@
# GitHub history for details.
import copy
from typing import Any, Sequence
from typing import Any, Dict, Sequence, cast
from opensearchpy._async.helpers.actions import aiter, async_scan
from opensearchpy.connection.async_connections import get_connection
@@ -39,6 +39,7 @@ class AsyncSearch(Request):
self.aggs = AggsProxy(self)
self._sort: Sequence[Any] = []
self._collapse: Dict[str, Any] = {}
self._source: Any = None
self._highlight: Any = {}
self._highlight_opts: Any = {}
@@ -111,13 +112,13 @@ class AsyncSearch(Request):
s.update_from_dict(d)
return s
def _clone(self) -> Any:
def _clone(self) -> "AsyncSearch":
"""
Return a clone of the current search request. Performs a shallow copy
of all the underlying objects. Used internally by most state modifying
APIs.
"""
s = super()._clone()
s = cast(AsyncSearch, super()._clone())
s._response_class = self._response_class
s._sort = self._sort[:]
@@ -126,6 +127,7 @@ class AsyncSearch(Request):
s._highlight_opts = self._highlight_opts.copy()
s._suggest = self._suggest.copy()
s._script_fields = self._script_fields.copy()
s._collapse = self._collapse.copy()
for x in ("query", "post_filter"):
getattr(s, x)._proxied = getattr(self, x)._proxied
@@ -281,6 +283,34 @@ class AsyncSearch(Request):
s._sort.append(k)
return s
def collapse(
self,
field: Any = None,
inner_hits: Any = None,
max_concurrent_group_searches: Any = None,
) -> "AsyncSearch":
"""
Add collapsing information to the search request.
If called without providing ``field``, it will remove all collapse
requirements, otherwise it will replace them with the provided
arguments.
The API returns a copy of the AsyncSearch object and can thus be chained.
"""
s = self._clone()
s._collapse = {}
if field is None:
return s
s._collapse["field"] = field
if inner_hits:
s._collapse["inner_hits"] = inner_hits
if max_concurrent_group_searches:
s._collapse["max_concurrent_group_searches"] = max_concurrent_group_searches
return s
def highlight_options(self, **kwargs: Any) -> Any:
"""
Update the global highlighting options used for this request. For
@@ -376,6 +406,9 @@ class AsyncSearch(Request):
if self._sort:
d["sort"] = self._sort
if self._collapse:
d["collapse"] = self._collapse
d.update(recursive_to_dict(self._extra))
if self._source not in (None, {}):
@@ -240,6 +240,40 @@ async def test_sort_by_score() -> None:
s.sort("-_score")
def test_collapse() -> None:
s = search.AsyncSearch()
inner_hits = {"name": "most_recent", "size": 5, "sort": [{"@timestamp": "desc"}]}
s = s.collapse(
field="user.id", inner_hits=inner_hits, max_concurrent_group_searches=4
)
assert {
"field": "user.id",
"inner_hits": {
"name": "most_recent",
"size": 5,
"sort": [{"@timestamp": "desc"}],
},
"max_concurrent_group_searches": 4,
} == s._collapse
assert {
"collapse": {
"field": "user.id",
"inner_hits": {
"name": "most_recent",
"size": 5,
"sort": [{"@timestamp": "desc"}],
},
"max_concurrent_group_searches": 4,
}
} == s.to_dict()
s = s.collapse()
assert {} == s._collapse
assert search.AsyncSearch().to_dict() == s.to_dict()
async def test_slice() -> None:
s = search.AsyncSearch()
assert {"from": 3, "size": 7} == s[3:10].to_dict()
@@ -546,3 +580,19 @@ async def test_rescore_query_to_dict() -> None:
},
},
}
def test_collapse_chaining() -> None:
s = search.AsyncSearch(index="index_name")
s = s.filter("term", color="red")
s = s.collapse(field="category")
s = s.filter("term", brand="something")
assert {
"query": {
"bool": {
"filter": [{"term": {"color": "red"}}, {"term": {"brand": "something"}}]
}
},
"collapse": {"field": "category"},
} == s.to_dict()