Skip to content

Commit c36b287

Browse files
committed
Lazy evaluation of RecordList[...] slicing
1 parent 99abc17 commit c36b287

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Changelog
1414
are read or methods are called. It will use `search_read` API method
1515
when it's adequate.
1616

17+
* New: extracting a part of a lazy :class:`RecordList` will not call
18+
API method, for simple use cases like
19+
``env['account.move'].search([])[10:90]``. It will set
20+
``offset`` and ``limit`` on the prepared search instead.
21+
1722
* Remove undocumented :meth:`Env._web`.
1823

1924
* Refactor code for ``read`` field formatter.

odooly.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,26 @@ def refresh(self):
21582158
for key in 'id', 'ids', '_idnames':
21592159
self.__dict__.pop(key, None)
21602160

2161+
def __getitem__(self, key):
2162+
if 'id' in self.__dict__ or (getattr(key, 'start', -1) or 0) < 0:
2163+
return super().__getitem__(key)
2164+
is_stop_positive = key.stop is not None and key.stop >= 0
2165+
search_args = {**self._search_args}
2166+
new_offset, new_stop = key.start or 0, search_args.get('limit', None)
2167+
if is_stop_positive:
2168+
new_stop = key.stop if new_stop is None else min(new_stop, key.stop)
2169+
if new_stop is not None:
2170+
search_args['limit'] = limit = new_stop - new_offset
2171+
if limit <= 0:
2172+
return RecordList(self._model, [])
2173+
if new_offset:
2174+
search_args['offset'] = (search_args.get('offset') or 0) + new_offset
2175+
result = RecordList(self._model, None, search=search_args)
2176+
if (is_stop_positive or key.stop is None) and key.step in (1, None):
2177+
return result
2178+
key = slice(None, limit if is_stop_positive else key.stop, key.step)
2179+
return super(RecordList, result).__getitem__(key)
2180+
21612181
def with_env(self, env):
21622182
if 'id' in self.__dict__:
21632183
return super().with_env(env)

tests/test_model.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,11 @@ def test_search(self):
187187
self.assertIsInstance(FooBar.search([searchterm]), odooly.RecordList)
188188
FooBar.search([searchterm], limit=2)
189189
FooBar.search([searchterm], offset=80, limit=99)
190+
FooBar.search([searchterm], offset=80, limit=99)[100:400:3]
190191
FooBar.search([searchterm], order='name ASC')
192+
FooBar.search([searchterm], order='name ASC')[:3]
193+
FooBar.search([searchterm], order='name ASC')[40:]
194+
FooBar.search([searchterm], order='name ASC')[40:99]
191195
FooBar.search(['name = mushroom', 'state != draft'])
192196
FooBar.search([('name', 'like', 'Morice')])
193197
FooBar.search([])
@@ -199,15 +203,14 @@ def test_search(self):
199203

200204
FooBar.search([searchterm], limit=2).ids
201205
FooBar.search([searchterm], offset=80, limit=99).id
202-
FooBar.search([searchterm], order='name ASC')[:3]
203206
FooBar.search(['name = mushroom', 'state != draft']) or 42
204207
FooBar.search([('name', 'like', 'Morice')]).ids
205208
FooBar._execute('search', [('name like Morice')])[0]
206209
FooBar.search([]).ids
210+
207211
self.assertCalls(
208212
OBJ('foo.bar', 'search', domain, 0, 2, None),
209213
OBJ('foo.bar', 'search', domain, 80, 99, None),
210-
OBJ('foo.bar', 'search', domain, 0, None, 'name ASC'),
211214
OBJ('foo.bar', 'search', domain2),
212215
OBJ('foo.bar', 'search', domain),
213216
OBJ('foo.bar', 'search', domain),
@@ -276,6 +279,25 @@ def test_search_count(self):
276279
self.assertCalls()
277280
self.assertOutput('')
278281

282+
def test_search_read(self):
283+
FooBar = self.env['foo.bar']
284+
searchterm = 'name like Morice'
285+
domain = [('name', 'like', 'Morice')]
286+
287+
FooBar.search([searchterm], order='name ASC')[:3].name
288+
289+
expected_calls = [
290+
OBJ('foo.bar', 'fields_get'),
291+
OBJ('foo.bar', 'search_read', domain, ['name'], order='name ASC', limit=3),
292+
]
293+
if float(self.server_version) < 8.0:
294+
expected_calls[1:2] = [
295+
OBJ('foo.bar', 'search', domain, 0, 3, 'name ASC'),
296+
OBJ('foo.bar', 'read', [1001, 1002], ['name']),
297+
]
298+
self.assertCalls(*expected_calls)
299+
self.assertOutput('')
300+
279301
def test_read(self):
280302
FooBar = self.env['foo.bar']
281303

0 commit comments

Comments
 (0)