Commit b876361a authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added page based pagination to entries query.

parent d1a9e81b
Pipeline #96981 passed with stages
in 23 minutes and 51 seconds
......@@ -463,7 +463,7 @@ class Pagination(BaseModel):
the value is omitted when making a request, the response will just give the
first page of results.
The response should also contain an attribute `next_after`, which similarly
The response will also contain an attribute `next_after`, which similarly
defines the starting position of the next page. Thus, one would normally start
with a request where `after` is omitted, then use the `next_after` value from
the responses as the `after` in the next request, to get the next page of
......@@ -475,16 +475,9 @@ class Pagination(BaseModel):
It might contain an id as *tie breaker*, if `order_by` is not a field with
unique values.
The *tie breaker* will be `:` separated, e.g. `<value>:<id>`.
For simple, index-based pagination, àfter` will be the zero-based index of the
For simple, index-based pagination, after` will be the zero-based index of the
first result in the response.
'''))
page: Optional[int] = Field(
None, description=strip('''
For simple, index-based pagination, this should contain the number of the
requested page (1-based). When provided in a request, this attribute can be
used instead of `after` to jump to a particular results page. However, if you
specify both `after` *and* `page` in your request, they need to be consistent.
'''))
@validator('size')
def validate_size(cls, size): # pylint: disable=no-self-argument
......@@ -507,16 +500,16 @@ class Pagination(BaseModel):
'''
raise NotImplementedError('Validation of `after` not implemented!')
@validator('page')
def validate_page(cls, page, values): # pylint: disable=no-self-argument
if page is not None:
# This attribute is not expected unless we are using an IndexBasedPagination
# or in the PaginationResponse of an index-based paginated request.
raise AssertionError('Value for `page` not permitted')
return page
class IndexBasedPagination(Pagination):
page: Optional[int] = Field(
None, description=strip('''
For simple, index-based pagination, this should contain the number of the
requested page (1-based). When provided in a request, this attribute can be
used instead of `after` to jump to a particular results page. However, if you
specify both `after` *and* `page` in your request, they need to be consistent.
'''))
@validator('after')
def validate_after(cls, after, values): # pylint: disable=no-self-argument
# This is validated in the root validator instead
......@@ -581,6 +574,10 @@ class PaginationResponse(Pagination):
None, description=strip('''
The url to get the next page.
'''))
page: Optional[int] = Field(
None, description=strip('''
The returned page number. The availability depends on the API method.
'''))
@validator('order_by')
def validate_order_by(cls, order_by): # pylint: disable=no-self-argument
......@@ -603,7 +600,7 @@ class PaginationResponse(Pagination):
return next_after
class EntryPagination(Pagination):
class EntryBasedPagination(Pagination):
order_by: Optional[str] = Field(
calc_id, # type: ignore
description=strip('''
......@@ -629,11 +626,32 @@ class EntryPagination(Pagination):
return after
class EntryPagination(EntryBasedPagination):
page: Optional[int] = Field(
None, description=strip('''
For simple, index-based pagination, this should contain the number of the
requested page (1-based). When provided in a request, this attribute can be
used instead of `after` to jump to a particular results page.
However, you can only retreive up to the 10.000th entry with a page number.
Only one, `after` *or* `page` must be provided.
'''))
@validator('page')
def validate_page(cls, page, values): # pylint: disable=no-self-argument
if page is not None:
assert values['after'] is None, 'There can only be one, a page number or an after value.'
assert page > 0, 'Page has to be larger than 1.'
assert page * values.get('size', 10) < 10000, 'Pagination by page is limited to 10.000 entries.'
return page
entry_pagination_parameters = parameter_dependency_from_model(
'entry_pagination_parameters', EntryPagination)
class AggregationPagination(EntryPagination):
class AggregationPagination(EntryBasedPagination):
order_by: Optional[str] = Field(
None, # type: ignore
description=strip('''
......
......@@ -1098,6 +1098,9 @@ def search(
if pagination.after:
search = search.extra(search_after=pagination.after.rsplit(':', 1))
if pagination.page:
search = search[(pagination.page - 1) * pagination.size: pagination.page * pagination.size]
# required
if required:
if required.include is not None and pagination.order_by not in required.include:
......
......@@ -870,7 +870,11 @@ def test_entries_owner(
pytest.param({'order': 'misspelled'}, None, 422, id='bad-order'),
pytest.param({'order_by': 'misspelled'}, None, 422, id='bad-order-by'),
pytest.param({'order_by': 'atoms', 'after': 'H:id_01'}, None, 422, id='order-by-list'),
pytest.param({'order_by': 'n_atoms', 'after': 'some'}, None, 400, id='order-by-bad-after')
pytest.param({'order_by': 'n_atoms', 'after': 'some'}, None, 400, id='order-by-bad-after'),
pytest.param({'page': 1, 'size': 1}, {'total': 23, 'size': 1, 'next_after': 'id_02', 'page': 1}, 200, id='page-1'),
pytest.param({'page': 2, 'size': 1}, {'total': 23, 'size': 1, 'next_after': 'id_03', 'page': 2}, 200, id='page-2'),
pytest.param({'page': 1000, 'size': 10}, None, 422, id='page-too-large'),
pytest.param({'page': 9999, 'size': 1}, None, 200, id='page-just-small-enough'),
])
@pytest.mark.parametrize('http_method', ['post', 'get'])
@pytest.mark.parametrize('test_method', [
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment