Signed-off-by: Radoslaw Kuczynski <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- Added `AsyncSearch#collapse` ([827](https://github.com/opensearch-project/opensearch-py/pull/827))
|
||||||
### Changed
|
### Changed
|
||||||
### Deprecated
|
### Deprecated
|
||||||
### Removed
|
### Removed
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# GitHub history for details.
|
# GitHub history for details.
|
||||||
|
|
||||||
import copy
|
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._async.helpers.actions import aiter, async_scan
|
||||||
from opensearchpy.connection.async_connections import get_connection
|
from opensearchpy.connection.async_connections import get_connection
|
||||||
@@ -39,6 +39,7 @@ class AsyncSearch(Request):
|
|||||||
|
|
||||||
self.aggs = AggsProxy(self)
|
self.aggs = AggsProxy(self)
|
||||||
self._sort: Sequence[Any] = []
|
self._sort: Sequence[Any] = []
|
||||||
|
self._collapse: Dict[str, Any] = {}
|
||||||
self._source: Any = None
|
self._source: Any = None
|
||||||
self._highlight: Any = {}
|
self._highlight: Any = {}
|
||||||
self._highlight_opts: Any = {}
|
self._highlight_opts: Any = {}
|
||||||
@@ -111,13 +112,13 @@ class AsyncSearch(Request):
|
|||||||
s.update_from_dict(d)
|
s.update_from_dict(d)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def _clone(self) -> Any:
|
def _clone(self) -> "AsyncSearch":
|
||||||
"""
|
"""
|
||||||
Return a clone of the current search request. Performs a shallow copy
|
Return a clone of the current search request. Performs a shallow copy
|
||||||
of all the underlying objects. Used internally by most state modifying
|
of all the underlying objects. Used internally by most state modifying
|
||||||
APIs.
|
APIs.
|
||||||
"""
|
"""
|
||||||
s = super()._clone()
|
s = cast(AsyncSearch, super()._clone())
|
||||||
|
|
||||||
s._response_class = self._response_class
|
s._response_class = self._response_class
|
||||||
s._sort = self._sort[:]
|
s._sort = self._sort[:]
|
||||||
@@ -126,6 +127,7 @@ class AsyncSearch(Request):
|
|||||||
s._highlight_opts = self._highlight_opts.copy()
|
s._highlight_opts = self._highlight_opts.copy()
|
||||||
s._suggest = self._suggest.copy()
|
s._suggest = self._suggest.copy()
|
||||||
s._script_fields = self._script_fields.copy()
|
s._script_fields = self._script_fields.copy()
|
||||||
|
s._collapse = self._collapse.copy()
|
||||||
for x in ("query", "post_filter"):
|
for x in ("query", "post_filter"):
|
||||||
getattr(s, x)._proxied = getattr(self, x)._proxied
|
getattr(s, x)._proxied = getattr(self, x)._proxied
|
||||||
|
|
||||||
@@ -281,6 +283,34 @@ class AsyncSearch(Request):
|
|||||||
s._sort.append(k)
|
s._sort.append(k)
|
||||||
return s
|
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:
|
def highlight_options(self, **kwargs: Any) -> Any:
|
||||||
"""
|
"""
|
||||||
Update the global highlighting options used for this request. For
|
Update the global highlighting options used for this request. For
|
||||||
@@ -376,6 +406,9 @@ class AsyncSearch(Request):
|
|||||||
if self._sort:
|
if self._sort:
|
||||||
d["sort"] = self._sort
|
d["sort"] = self._sort
|
||||||
|
|
||||||
|
if self._collapse:
|
||||||
|
d["collapse"] = self._collapse
|
||||||
|
|
||||||
d.update(recursive_to_dict(self._extra))
|
d.update(recursive_to_dict(self._extra))
|
||||||
|
|
||||||
if self._source not in (None, {}):
|
if self._source not in (None, {}):
|
||||||
|
|||||||
@@ -240,6 +240,40 @@ async def test_sort_by_score() -> None:
|
|||||||
s.sort("-_score")
|
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:
|
async def test_slice() -> None:
|
||||||
s = search.AsyncSearch()
|
s = search.AsyncSearch()
|
||||||
assert {"from": 3, "size": 7} == s[3:10].to_dict()
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user