Skip to content

Commit da683d7

Browse files
committed
Merge remote-tracking branch 'origin/main' into 8.x
2 parents a8d8d64 + ecf2e15 commit da683d7

File tree

11 files changed

+125
-15
lines changed

11 files changed

+125
-15
lines changed

.github/workflows/backport.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
name: Backport
22
on:
3-
pull_request:
3+
pull_request_target:
44
types:
55
- closed
66
- labeled
77

88
jobs:
99
backport:
10-
runs-on: ubuntu-latest
1110
name: Backport
11+
runs-on: ubuntu-latest
12+
# Only react to merged PRs for security reasons.
13+
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
14+
if: >
15+
github.event.pull_request.merged
16+
&& (
17+
github.event.action == 'closed'
18+
|| (
19+
github.event.action == 'labeled'
20+
&& contains(github.event.label.name, 'backport')
21+
)
22+
)
1223
steps:
13-
- name: Backport
14-
uses: tibdex/backport@v2
24+
- uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4
1525
with:
1626
github_token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/ci.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Python
1313
uses: actions/setup-python@v4
1414
with:
15-
python-version: "3.11"
15+
python-version: "3.12"
1616
- name: Install dependencies
1717
run: |
1818
python3 -m pip install setuptools wheel twine
@@ -32,7 +32,7 @@ jobs:
3232
- name: Set up Python
3333
uses: actions/setup-python@v4
3434
with:
35-
python-version: "3.11"
35+
python-version: "3.12"
3636
- name: Install dependencies
3737
run: |
3838
python3 -m pip install nox
@@ -47,7 +47,7 @@ jobs:
4747
- name: Set up Python
4848
uses: actions/setup-python@v4
4949
with:
50-
python-version: "3.11"
50+
python-version: "3.12"
5151
- name: Install dependencies
5252
run: |
5353
python3 -m pip install nox
@@ -66,8 +66,9 @@ jobs:
6666
"3.9",
6767
"3.10",
6868
"3.11",
69+
"3.12",
6970
]
70-
es-version: [8.0.0, 8.9.0]
71+
es-version: [8.0.0, 8.11.0]
7172

7273
steps:
7374
- name: Checkout Repository
@@ -82,7 +83,7 @@ jobs:
8283
with:
8384
python-version: ${{ matrix.python-version }}
8485
- name: Set up Python for Nox
85-
if: matrix.python-version != '3.11'
86+
if: matrix.python-version != '3.12'
8687
uses: actions/setup-python@v4
8788
with:
8889
python-version: 3
@@ -93,4 +94,4 @@ jobs:
9394
run: |
9495
nox -rs test-${{ matrix.python-version }}
9596
env:
96-
WAIT_FOR_ES: "1"
97+
WAIT_FOR_ES: "1"

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: 2
33
build:
44
os: ubuntu-22.04
55
tools:
6-
python: "3"
6+
python: "3.12"
77

88
python:
99
install:

Changelog.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
Changelog
44
=========
55

6+
8.11.0 (2023-11-13)
7+
-------------------
8+
9+
* Added support for Python 3.12 (`#1680`_)
10+
* Added support for Search.collase() (`#1649`_, contributed by `@qcoumes`_)
11+
12+
.. _@qcoumes: https://github.com/qcoumes
13+
.. _#1680: https://github.com/elastic/elasticsearch-dsl-py/pull/1680
14+
.. _#1649: https://github.com/elastic/elasticsearch-dsl-py/pull/1649
15+
616
8.9.0 (2023-09-07)
717
------------------
818

docs/search_dsl.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ The ``Search`` object represents the entire search request:
1818

1919
* pagination
2020

21+
* highlighting
22+
23+
* suggestions
24+
25+
* collapsing
26+
2127
* additional parameters
2228

2329
* associated client
@@ -433,7 +439,26 @@ keyword arguments will be added to the suggest's json as-is which means that it
433439
should be one of ``term``, ``phrase`` or ``completion`` to indicate which type
434440
of suggester should be used.
435441

442+
Collapsing
443+
~~~~~~~~~~
444+
445+
To collapse search results use the ``collapse`` method on your ``Search`` object:
446+
447+
.. code:: python
448+
449+
s = Search().query("match", message="GET /search")
450+
# collapse results by user_id
451+
s = s.collapse("user_id")
452+
453+
The top hits will only include one result per ``user_id``. You can also expand
454+
each collapsed top hit with the ``inner_hits`` parameter,
455+
``max_concurrent_group_searches`` being the number of concurrent requests
456+
allowed to retrieve the inner hits per group:
457+
458+
.. code:: python
436459
460+
inner_hits = {"name": "recent_search", "size": 5, "sort": [{"@timestamp": "desc"}]}
461+
s = s.collapse("user_id", inner_hits=inner_hits, max_concurrent_group_searches=4)
437462
438463
More Like This Query
439464
~~~~~~~~~~~~~~~~~~~~

elasticsearch_dsl/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
from .utils import AttrDict, AttrList, DslBase
8585
from .wrappers import Range
8686

87-
VERSION = (8, 9, 0)
87+
VERSION = (8, 11, 0)
8888
__version__ = VERSION
8989
__versionstr__ = ".".join(map(str, VERSION))
9090
__all__ = [

elasticsearch_dsl/search.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def __init__(self, using="default", index=None, doc_type=None, extra=None):
120120

121121
self._doc_type = []
122122
self._doc_type_map = {}
123+
self._collapse = {}
123124
if isinstance(doc_type, (tuple, list)):
124125
self._doc_type.extend(doc_type)
125126
elif isinstance(doc_type, collections.abc.Mapping):
@@ -293,6 +294,7 @@ def _clone(self):
293294
s = self.__class__(
294295
using=self._using, index=self._index, doc_type=self._doc_type
295296
)
297+
s._collapse = self._collapse.copy()
296298
s._doc_type_map = self._doc_type_map.copy()
297299
s._extra = self._extra.copy()
298300
s._params = self._params.copy()
@@ -318,6 +320,7 @@ def __init__(self, **kwargs):
318320

319321
self.aggs = AggsProxy(self)
320322
self._sort = []
323+
self._collapse = {}
321324
self._source = None
322325
self._highlight = {}
323326
self._highlight_opts = {}
@@ -568,6 +571,27 @@ def sort(self, *keys):
568571
s._sort.append(k)
569572
return s
570573

574+
def collapse(self, field=None, inner_hits=None, max_concurrent_group_searches=None):
575+
"""
576+
Add collapsing information to the search request.
577+
If called without providing ``field``, it will remove all collapse
578+
requirements, otherwise it will replace them with the provided
579+
arguments.
580+
The API returns a copy of the Search object and can thus be chained.
581+
"""
582+
s = self._clone()
583+
s._collapse = {}
584+
585+
if field is None:
586+
return s
587+
588+
s._collapse["field"] = field
589+
if inner_hits:
590+
s._collapse["inner_hits"] = inner_hits
591+
if max_concurrent_group_searches:
592+
s._collapse["max_concurrent_group_searches"] = max_concurrent_group_searches
593+
return s
594+
571595
def highlight_options(self, **kwargs):
572596
"""
573597
Update the global highlighting options used for this request. For
@@ -663,6 +687,9 @@ def to_dict(self, count=False, **kwargs):
663687
if self._sort:
664688
d["sort"] = self._sort
665689

690+
if self._collapse:
691+
d["collapse"] = self._collapse
692+
666693
d.update(recursive_to_dict(self._extra))
667694

668695
if self._source not in (None, {}):

noxfile.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"3.9",
3636
"3.10",
3737
"3.11",
38+
"3.12",
3839
]
3940
)
4041
def test(session):
@@ -52,7 +53,7 @@ def test(session):
5253
session.run("pytest", *argv)
5354

5455

55-
@nox.session()
56+
@nox.session(python="3.12")
5657
def format(session):
5758
session.install("black~=23.0", "isort")
5859
session.run("black", "--target-version=py37", *SOURCE_FILES)
@@ -62,7 +63,7 @@ def format(session):
6263
lint(session)
6364

6465

65-
@nox.session
66+
@nox.session(python="3.12")
6667
def lint(session):
6768
session.install("flake8", "black~=23.0", "isort")
6869
session.run("black", "--check", "--target-version=py37", *SOURCE_FILES)

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ filterwarnings =
1313
# https://github.com/elastic/elasticsearch-py/issues/2181#issuecomment-1490932964
1414
ignore:The 'body' parameter is deprecated .*:DeprecationWarning
1515
ignore:Legacy index templates are deprecated in favor of composable templates.:elasticsearch.exceptions.ElasticsearchWarning
16+
ignore:datetime.datetime.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version..*:DeprecationWarning

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from setuptools import find_packages, setup
2121

22-
VERSION = (8, 9, 0)
22+
VERSION = (8, 11, 0)
2323
__version__ = VERSION
2424
__versionstr__ = ".".join(map(str, VERSION))
2525

@@ -68,6 +68,7 @@
6868
"Programming Language :: Python :: 3.9",
6969
"Programming Language :: Python :: 3.10",
7070
"Programming Language :: Python :: 3.11",
71+
"Programming Language :: Python :: 3.12",
7172
"Programming Language :: Python :: Implementation :: CPython",
7273
"Programming Language :: Python :: Implementation :: PyPy",
7374
],

tests/test_search.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,38 @@ def test_sort_by_score():
256256
s.sort("-_score")
257257

258258

259+
def test_collapse():
260+
s = search.Search()
261+
262+
inner_hits = {"name": "most_recent", "size": 5, "sort": [{"@timestamp": "desc"}]}
263+
s = s.collapse("user.id", inner_hits=inner_hits, max_concurrent_group_searches=4)
264+
265+
assert {
266+
"field": "user.id",
267+
"inner_hits": {
268+
"name": "most_recent",
269+
"size": 5,
270+
"sort": [{"@timestamp": "desc"}],
271+
},
272+
"max_concurrent_group_searches": 4,
273+
} == s._collapse
274+
assert {
275+
"collapse": {
276+
"field": "user.id",
277+
"inner_hits": {
278+
"name": "most_recent",
279+
"size": 5,
280+
"sort": [{"@timestamp": "desc"}],
281+
},
282+
"max_concurrent_group_searches": 4,
283+
}
284+
} == s.to_dict()
285+
286+
s = s.collapse()
287+
assert {} == s._collapse
288+
assert search.Search().to_dict() == s.to_dict()
289+
290+
259291
def test_slice():
260292
s = search.Search()
261293
assert {"from": 3, "size": 7} == s[3:10].to_dict()
@@ -305,6 +337,7 @@ def test_complex_example():
305337
s.query("match", title="python")
306338
.query(~Q("match", title="ruby"))
307339
.filter(Q("term", category="meetup") | Q("term", category="conference"))
340+
.collapse("user_id")
308341
.post_filter("terms", tags=["prague", "czech"])
309342
.script_fields(more_attendees="doc['attendees'].value + 42")
310343
)
@@ -342,6 +375,7 @@ def test_complex_example():
342375
"aggs": {"avg_attendees": {"avg": {"field": "attendees"}}},
343376
}
344377
},
378+
"collapse": {"field": "user_id"},
345379
"highlight": {
346380
"order": "score",
347381
"fields": {"title": {"fragment_size": 50}, "body": {"fragment_size": 50}},

0 commit comments

Comments
 (0)