Skip to content

Commit fb57759

Browse files
Added support for slicing multiple times in Search class (#1771)
* Added support for slicing multiple times in Search class Fixes #799 * simplify slicing logic * A few more slicing unit tests * Removed unnecessary comment * simplified slicing logic even more * added suggested changes * add more slicing examples to documentation
1 parent e68585b commit fb57759

File tree

4 files changed

+79
-18
lines changed

4 files changed

+79
-18
lines changed

docs/search_dsl.rst

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,16 +410,25 @@ To specify the from/size parameters, use the Python slicing API:
410410

411411
.. code:: python
412412
413-
s = s[10:20]
414-
# {"from": 10, "size": 10}
413+
s = s[10:20]
414+
# {"from": 10, "size": 10}
415+
416+
s = s[:20]
417+
# {"size": 20}
418+
419+
s = s[10:]
420+
# {"from": 10}
421+
422+
s = s[10:20][2:]
423+
# {"from": 12, "size": 8}
415424
416425
If you want to access all the documents matched by your query you can use the
417426
``scan`` method which uses the scan/scroll elasticsearch API:
418427

419428
.. code:: python
420429
421-
for hit in s.scan():
422-
print(hit.title)
430+
for hit in s.scan():
431+
print(hit.title)
423432
424433
Note that in this case the results won't be sorted.
425434

elasticsearch_dsl/search_base.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -352,20 +352,34 @@ def __getitem__(self, n):
352352
# If negative slicing, abort.
353353
if n.start and n.start < 0 or n.stop and n.stop < 0:
354354
raise ValueError("Search does not support negative slicing.")
355-
# Elasticsearch won't get all results so we default to size: 10 if
356-
# stop not given.
357-
s._extra["from"] = n.start or 0
358-
s._extra["size"] = max(
359-
0, n.stop - (n.start or 0) if n.stop is not None else 10
360-
)
361-
return s
355+
slice_start = n.start
356+
slice_stop = n.stop
362357
else: # This is an index lookup, equivalent to slicing by [n:n+1].
363358
# If negative index, abort.
364359
if n < 0:
365360
raise ValueError("Search does not support negative indexing.")
366-
s._extra["from"] = n
367-
s._extra["size"] = 1
368-
return s
361+
slice_start = n
362+
slice_stop = n + 1
363+
364+
old_from = s._extra.get("from")
365+
old_to = None
366+
if "size" in s._extra:
367+
old_to = (old_from or 0) + s._extra["size"]
368+
369+
new_from = old_from
370+
if slice_start is not None:
371+
new_from = (old_from or 0) + slice_start
372+
new_to = old_to
373+
if slice_stop is not None:
374+
new_to = (old_from or 0) + slice_stop
375+
if old_to is not None and old_to < new_to:
376+
new_to = old_to
377+
378+
if new_from is not None:
379+
s._extra["from"] = new_from
380+
if new_to is not None:
381+
s._extra["size"] = max(0, new_to - (new_from or 0))
382+
return s
369383

370384
@classmethod
371385
def from_dict(cls, d):

tests/_async/test_search.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,15 +363,34 @@ def test_collapse():
363363
def test_slice():
364364
s = AsyncSearch()
365365
assert {"from": 3, "size": 7} == s[3:10].to_dict()
366-
assert {"from": 0, "size": 5} == s[:5].to_dict()
367-
assert {"from": 3, "size": 10} == s[3:].to_dict()
366+
assert {"size": 5} == s[:5].to_dict()
367+
assert {"from": 3} == s[3:].to_dict()
368368
assert {"from": 0, "size": 0} == s[0:0].to_dict()
369369
assert {"from": 20, "size": 0} == s[20:0].to_dict()
370+
assert {"from": 10, "size": 5} == s[10:][:5].to_dict()
371+
assert {"from": 10, "size": 0} == s[:5][10:].to_dict()
372+
assert {"size": 10} == s[:10][:40].to_dict()
373+
assert {"size": 10} == s[:40][:10].to_dict()
374+
assert {"size": 40} == s[:40][:80].to_dict()
375+
assert {"from": 12, "size": 0} == s[:5][10:][2:].to_dict()
376+
assert {"from": 15, "size": 0} == s[10:][:5][5:].to_dict()
377+
assert {} == s[:].to_dict()
378+
with raises(ValueError):
379+
s[-1:]
380+
with raises(ValueError):
381+
s[4:-1]
382+
with raises(ValueError):
383+
s[-3:-2]
370384

371385

372386
def test_index():
373387
s = AsyncSearch()
374388
assert {"from": 3, "size": 1} == s[3].to_dict()
389+
assert {"from": 3, "size": 1} == s[3][0].to_dict()
390+
assert {"from": 8, "size": 0} == s[3][5].to_dict()
391+
assert {"from": 4, "size": 1} == s[3:10][1].to_dict()
392+
with raises(ValueError):
393+
s[-3]
375394

376395

377396
def test_search_to_dict():

tests/_sync/test_search.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,15 +363,34 @@ def test_collapse():
363363
def test_slice():
364364
s = Search()
365365
assert {"from": 3, "size": 7} == s[3:10].to_dict()
366-
assert {"from": 0, "size": 5} == s[:5].to_dict()
367-
assert {"from": 3, "size": 10} == s[3:].to_dict()
366+
assert {"size": 5} == s[:5].to_dict()
367+
assert {"from": 3} == s[3:].to_dict()
368368
assert {"from": 0, "size": 0} == s[0:0].to_dict()
369369
assert {"from": 20, "size": 0} == s[20:0].to_dict()
370+
assert {"from": 10, "size": 5} == s[10:][:5].to_dict()
371+
assert {"from": 10, "size": 0} == s[:5][10:].to_dict()
372+
assert {"size": 10} == s[:10][:40].to_dict()
373+
assert {"size": 10} == s[:40][:10].to_dict()
374+
assert {"size": 40} == s[:40][:80].to_dict()
375+
assert {"from": 12, "size": 0} == s[:5][10:][2:].to_dict()
376+
assert {"from": 15, "size": 0} == s[10:][:5][5:].to_dict()
377+
assert {} == s[:].to_dict()
378+
with raises(ValueError):
379+
s[-1:]
380+
with raises(ValueError):
381+
s[4:-1]
382+
with raises(ValueError):
383+
s[-3:-2]
370384

371385

372386
def test_index():
373387
s = Search()
374388
assert {"from": 3, "size": 1} == s[3].to_dict()
389+
assert {"from": 3, "size": 1} == s[3][0].to_dict()
390+
assert {"from": 8, "size": 0} == s[3][5].to_dict()
391+
assert {"from": 4, "size": 1} == s[3:10][1].to_dict()
392+
with raises(ValueError):
393+
s[-3]
375394

376395

377396
def test_search_to_dict():

0 commit comments

Comments
 (0)