Coverage for ramose / paging.py: 100%

37 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-07-01 13:49 +0000

1# SPDX-FileCopyrightText: 2026 Arcangelo Massari <arcangelo.massari@unibo.it> 

2# 

3# SPDX-License-Identifier: ISC 

4 

5from __future__ import annotations 

6 

7from math import ceil 

8from typing import NamedTuple 

9from urllib.parse import urlencode 

10 

11_PAGINATION_KEYS = frozenset({"page", "page_size"}) 

12 

13 

14class PaginationInfo(NamedTuple): 

15 page: int 

16 page_size: int 

17 total_items: int 

18 self_url: str 

19 next_url: str 

20 prev_url: str 

21 first_url: str 

22 last_url: str 

23 

24 

25def build_pagination_info( 

26 base_path: str, 

27 query_params: dict[str, list[str]], 

28 page: int, 

29 page_size: int, 

30 total_items: int, 

31) -> PaginationInfo: 

32 total_pages = ceil(total_items / page_size) if page_size > 0 else 0 

33 self_url = _page_url(base_path, query_params, page, page_size, total_items) 

34 next_url = _page_url(base_path, query_params, page + 1, page_size, total_items) if page < total_pages else "" 

35 prev_url = _page_url(base_path, query_params, page - 1, page_size, total_items) if page > 1 else "" 

36 first_url = _page_url(base_path, query_params, 1, page_size, total_items) 

37 last_url = _page_url(base_path, query_params, max(total_pages, 1), page_size, total_items) 

38 return PaginationInfo(page, page_size, total_items, self_url, next_url, prev_url, first_url, last_url) 

39 

40 

41def build_link_header(pagination_info: PaginationInfo) -> str: 

42 links = [] 

43 if pagination_info.next_url: 

44 links.append(f'<{pagination_info.next_url}>; rel="next"') 

45 if pagination_info.prev_url: 

46 links.append(f'<{pagination_info.prev_url}>; rel="prev"') 

47 links.append(f'<{pagination_info.first_url}>; rel="first"') 

48 links.append(f'<{pagination_info.last_url}>; rel="last"') 

49 return ", ".join(links) 

50 

51 

52def _page_url(base_path: str, query_params: dict[str, list[str]], page: int, page_size: int, total_items: int) -> str: 

53 params = {k: v for k, v in query_params.items() if k not in _PAGINATION_KEYS} 

54 params["page"] = [str(page)] 

55 params["page_size"] = [str(page_size)] 

56 params["total_items"] = [str(total_items)] 

57 return f"{base_path}?{urlencode(params, doseq=True, safe=':,')}"