Skip to content

Commit

Permalink
util: Implement Paginator class as interface to access a list by pa…
Browse files Browse the repository at this point in the history
…ges (#11247)

* util: Implement `Paginator` class as interface to access a list by pages

* Be less restrictive about page sizes and refactor tests

* Make the pages based of 0 instead of 1 and some more test refactoring

* More tests

* Adjust workflows after rebase

* Introduce `Paginator.create`

* `<=` instead of `- 1`
  • Loading branch information
xdustinface authored and wallentx committed May 2, 2022
1 parent abbe9f4 commit baa67e8
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
46 changes: 46 additions & 0 deletions chia/util/paginator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations

import dataclasses
from math import ceil
from typing import Sequence


class InvalidPageSizeLimit(Exception):
def __init__(self, page_size_limit: int) -> None:
super().__init__(f"Page size limit must be one or more, not: {page_size_limit}")


class InvalidPageSizeError(Exception):
def __init__(self, page_size: int, page_size_limit: int) -> None:
super().__init__(f"Invalid page size {page_size}. Must be between: 1 and {page_size_limit}")


class PageOutOfBoundsError(Exception):
def __init__(self, page_size: int, max_page_size: int) -> None:
super().__init__(f"Page {page_size} out of bounds. Available pages: 0-{max_page_size}")


@dataclasses.dataclass
class Paginator:
_source: Sequence[object]
_page_size: int

@classmethod
def create(cls, source: Sequence[object], page_size: int, page_size_limit: int = 100) -> Paginator:
if page_size_limit < 1:
raise InvalidPageSizeLimit(page_size_limit)
if page_size > page_size_limit:
raise InvalidPageSizeError(page_size, page_size_limit)
return cls(source, page_size)

def page_size(self) -> int:
return self._page_size

def page_count(self) -> int:
return max(1, ceil(len(self._source) / self._page_size))

def get_page(self, page: int) -> Sequence[object]:
if page < 0 or page >= self.page_count():
raise PageOutOfBoundsError(page, self.page_count() - 1)
offset = page * self._page_size
return self._source[offset : offset + self._page_size]
70 changes: 70 additions & 0 deletions tests/util/test_paginator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from math import ceil
from typing import List, Type

import pytest

from chia.util.paginator import InvalidPageSizeError, InvalidPageSizeLimit, PageOutOfBoundsError, Paginator


@pytest.mark.parametrize(
"source, page_size, page_size_limit",
[([], 1, 1), ([1], 1, 2), ([1, 2], 2, 2), ([], 10, 100), ([1, 2, 10], 1000, 1000)],
)
def test_constructor_valid_inputs(source: List[int], page_size: int, page_size_limit: int) -> None:
paginator: Paginator = Paginator.create(source, page_size, page_size_limit)
assert paginator.page_size() == page_size
assert paginator.page_count() == 1
assert paginator.get_page(0) == source


@pytest.mark.parametrize(
"page_size, page_size_limit, exception",
[
(5, -1, InvalidPageSizeLimit),
(5, 0, InvalidPageSizeLimit),
(2, 1, InvalidPageSizeError),
(100, 1, InvalidPageSizeError),
(1001, 1000, InvalidPageSizeError),
],
)
def test_constructor_invalid_inputs(page_size: int, page_size_limit: int, exception: Type[Exception]) -> None:
with pytest.raises(exception):
Paginator.create([], page_size, page_size_limit)


def test_page_count() -> None:
for page_size in range(1, 10):
for i in range(0, 10):
assert Paginator.create(range(0, i), page_size).page_count() == max(1, ceil(i / page_size))


@pytest.mark.parametrize(
"length, page_size, page, expected_data",
[
(17, 5, 0, [0, 1, 2, 3, 4]),
(17, 5, 1, [5, 6, 7, 8, 9]),
(17, 5, 2, [10, 11, 12, 13, 14]),
(17, 5, 3, [15, 16]),
(3, 4, 0, [0, 1, 2]),
(3, 3, 0, [0, 1, 2]),
(3, 2, 0, [0, 1]),
(3, 2, 1, [2]),
(3, 1, 0, [0]),
(3, 1, 1, [1]),
(3, 1, 2, [2]),
(2, 2, 0, [0, 1]),
(2, 1, 0, [0]),
(2, 1, 1, [1]),
(1, 2, 0, [0]),
(0, 2, 0, []),
(0, 10, 0, []),
],
)
def test_get_page_valid(length: int, page: int, page_size: int, expected_data: List[int]) -> None:
assert Paginator.create(list(range(0, length)), page_size).get_page(page) == expected_data


@pytest.mark.parametrize("page", [-1000, -10, -1, 5, 10, 1000])
def test_get_page_invalid(page: int) -> None:
with pytest.raises(PageOutOfBoundsError):
Paginator.create(range(0, 17), 5).get_page(page)

0 comments on commit baa67e8

Please sign in to comment.