Skip to content

Commit e76d070

Browse files
committed
adds metadata implementation for the pagination
1 parent f200427 commit e76d070

7 files changed

Lines changed: 85 additions & 93 deletions

File tree

apimatic_core/api_call.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ def execute(self):
9595
return self._response_handler.handle(_http_response, self._global_configuration.get_global_errors())
9696

9797

98-
def paginate(self, paginated_data_wrap_using):
99-
return paginated_data_wrap_using(PaginatedData(self))
98+
def paginate(self, page_iterable_creator, page_creator):
99+
return page_iterable_creator(PaginatedData(self, page_creator))
100100

101101
def clone(self, global_configuration=None, request_builder=None, response_handler=None,
102102
endpoint_configuration=None, pagination_stategies=None, paginated_item_converter=None):

apimatic_core/pagination/configuration/cursor_pagination.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,51 @@
33

44

55
class CursorPagination(PaginationStrategy):
6-
def __init__(self, output=None, input_=None):
6+
7+
@property
8+
def metadata(self):
9+
return self._metadata_creator(self._cursor_value)
10+
11+
def __init__(self, output, input_, metadata_creator):
12+
super().__init__(metadata_creator)
13+
714
if input_ is None:
815
raise ValueError("Input pointer for cursor based pagination cannot be None")
916
if output is None:
1017
raise ValueError("Output pointer for cursor based pagination cannot be None")
18+
1119
self._output = output
1220
self._input = input_
21+
self._cursor_value = None
1322

1423
def apply(self, paginated_data):
1524
last_response = paginated_data.last_response
1625
request_builder = paginated_data.request_builder
1726

1827
if last_response is None:
28+
self._cursor_value = self._get_initial_cursor_value(request_builder, self._input)
1929
return request_builder
2030

21-
cursor_value = ApiHelper.resolve_response_pointer(
31+
self._cursor_value = ApiHelper.resolve_response_pointer(
2232
self._output,
2333
last_response.text,
2434
last_response.headers
2535
)
2636

27-
if cursor_value is None:
37+
if self._cursor_value is None:
2838
return None
2939

30-
return self._get_updated_request_builder(request_builder, cursor_value)
40+
return self.get_updated_request_builder(request_builder, self._input, self._cursor_value)
3141

32-
def _get_updated_request_builder(self, request_builder, cursor_value):
33-
path_prefix, field_path = ApiHelper.split_into_parts(self._input)
34-
template_params = request_builder.template_params
35-
query_params = request_builder.query_params
36-
header_params = request_builder.header_params
42+
@staticmethod
43+
def _get_initial_cursor_value(request_builder, input_pointer):
44+
path_prefix, field_path = ApiHelper.split_into_parts(input_pointer)
3745

3846
if path_prefix == "$request.path":
39-
template_params = ApiHelper.update_entry_by_json_pointer(
40-
template_params.copy(), field_path, cursor_value, inplace=True)
47+
return ApiHelper.get_value_by_json_pointer(request_builder.template_params, field_path)
4148
elif path_prefix == "$request.query":
42-
query_params = ApiHelper.update_entry_by_json_pointer(
43-
query_params.copy(), field_path, cursor_value, inplace=True)
49+
return ApiHelper.get_value_by_json_pointer(request_builder.query_params, field_path)
4450
elif path_prefix == "$request.headers":
45-
header_params = ApiHelper.update_entry_by_json_pointer(
46-
header_params.copy(), field_path, cursor_value, inplace=True)
47-
48-
return request_builder.clone_with(
49-
template_params=template_params,
50-
query_params=query_params,
51-
header_params=header_params
52-
)
51+
return ApiHelper.get_value_by_json_pointer(request_builder.header_params, field_path)
52+
53+
return None

apimatic_core/pagination/configuration/link_pagination.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66
class LinkPagination(PaginationStrategy):
77
"""Pagination manager implementation for link-based pagination."""
88

9-
def __init__(self, next_link):
10-
"""
11-
Initializes a new instance of the LinkPagination class.
12-
13-
Args:
14-
next (str): JSON pointer of a field in the response, representing the next request query URL.
15-
"""
16-
if next_link is None:
9+
@property
10+
def metadata(self):
11+
return self._metadata_creator(self._next_link)
12+
13+
def __init__(self, next_link_pointer, metadata_creator):
14+
super().__init__(metadata_creator)
15+
16+
if next_link_pointer is None:
1717
raise ValueError("Next link pointer for cursor based pagination cannot be None")
18-
self._next_link = next_link
18+
19+
self._next_link_pointer = next_link_pointer
20+
self._next_link = None
1921

2022
def apply(self, paginated_data):
2123
last_response = paginated_data.last_response
@@ -24,20 +26,19 @@ def apply(self, paginated_data):
2426
if last_response is None:
2527
return request_builder
2628

27-
link_value = ApiHelper.resolve_response_pointer(
28-
self._next_link,
29+
self._next_link = ApiHelper.resolve_response_pointer(
30+
self._next_link_pointer,
2931
last_response.text,
3032
last_response.headers
3133
)
3234

33-
if link_value is None:
35+
if self._next_link is None:
3436
return None
3537

36-
query_params = ApiHelper.get_query_parameters(link_value)
38+
query_params = ApiHelper.get_query_parameters(self._next_link)
3739
updated_query_params = request_builder.query_params.copy()
3840
updated_query_params.update(query_params)
3941

4042
return request_builder.clone_with(
4143
query_params=updated_query_params
4244
)
43-

apimatic_core/pagination/configuration/offset_pagination.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
class OffsetPagination(PaginationStrategy):
77
"""Pagination manager implementation for offset-based pagination."""
88

9-
def __init__(self, input_):
9+
@property
10+
def metadata(self):
11+
return self._metadata_creator(self._offset)
12+
13+
def __init__(self, input_, metadata_creator):
1014
"""
1115
Initializes a new instance of the OffsetPagination class.
1216
1317
Args:
1418
input_ (str): JSON pointer to the request field representing the offset.
1519
"""
20+
super().__init__(metadata_creator)
21+
1622
if input_ is None:
1723
raise ValueError("Input pointer for offset based pagination cannot be None")
1824

@@ -42,4 +48,4 @@ def _get_initial_offset(self, request_builder):
4248
elif path_prefix == "$request.headers":
4349
return int(ApiHelper.get_value_by_json_pointer(request_builder.header_params, field_path))
4450

45-
return 0
51+
return 0

apimatic_core/pagination/configuration/page_pagination.py

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
from apimatic_core.utilities.api_helper import ApiHelper
33

44
class PagePagination(PaginationStrategy):
5-
def __init__(self, input_):
6-
"""
7-
Initializes a new instance of the OffsetPagination class.
85

9-
Args:
10-
input_ (str): JSON pointer to the request field representing the offset.
11-
"""
6+
@property
7+
def metadata(self):
8+
return self._metadata_creator(self._page_number)
9+
10+
def __init__(self, input_, metadata_creator):
11+
super().__init__(metadata_creator)
12+
1213
if input_ is None:
1314
raise ValueError("Input pointer for page based pagination cannot be None")
1415

@@ -25,42 +26,7 @@ def apply(self, paginated_data):
2526
return request_builder
2627
self._page_number += 1 if last_page_size > 0 else 0
2728

28-
return self._get_updated_request_builder(request_builder, last_page_size)
29-
30-
def _get_updated_request_builder(self, request_builder, last_page_size):
31-
"""
32-
Updates the given request builder with the new offset or cursor value for pagination.
33-
34-
Depending on the JSON pointer prefix, updates the appropriate field in the path, query, or headers
35-
with the new offset or cursor value, and returns a cloned request builder with these updated parameters.
36-
37-
Args:
38-
request_builder: The request builder instance to update.
39-
40-
Returns:
41-
A new request builder instance with updated pagination parameters.
42-
"""
43-
path_prefix, field_path = ApiHelper.split_into_parts(self._input)
44-
template_params = request_builder.template_params
45-
query_params = request_builder.query_params
46-
header_params = request_builder.header_params
47-
self._page_number += 1 if last_page_size > 0 else 0
48-
49-
if path_prefix == "$request.path":
50-
template_params = ApiHelper.update_entry_by_json_pointer(
51-
template_params.copy(), field_path, self._page_number, inplace=True)
52-
elif path_prefix == "$request.query":
53-
query_params = ApiHelper.update_entry_by_json_pointer(
54-
query_params.copy(), field_path, self._page_number, inplace=True)
55-
elif path_prefix == "$request.headers":
56-
header_params = ApiHelper.update_entry_by_json_pointer(
57-
header_params.copy(), field_path, self._page_number, inplace=True)
58-
59-
return request_builder.clone_with(
60-
template_params=template_params,
61-
query_params=query_params,
62-
header_params=header_params
63-
)
29+
return self.get_updated_request_builder(request_builder, self._input, self._page_number)
6430

6531
def _get_initial_page_offset(self, request_builder):
6632
path_prefix, field_path = ApiHelper.split_into_parts(self._input)

apimatic_core/pagination/paginated_data.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ def request_builder(self):
1717

1818
@property
1919
def page_size(self):
20-
return len(self._page)
20+
return len(self._items)
2121

22-
def __init__(self, api_call):
22+
def __init__(self, api_call, page_creator):
2323
self._api_call = copy.deepcopy(api_call)
24+
self._page_creator = page_creator
2425
self._initial_request_builder = api_call.request_builder
2526
self._pagination_strategies = self._api_call.get_pagination_stategies
2627
self._http_call_context =\
@@ -29,21 +30,22 @@ def __init__(self, api_call):
2930
http_callback=self._http_call_context)
3031
self._global_configuration = self._api_call.global_configuration.clone_with(
3132
http_client_configuration=_http_client_configuration)
32-
self._page = []
33+
self._page = None
34+
self._items = []
3335
self._current_index = 0
3436

3537
def __iter__(self):
3638
return self._get_new_self_instance()
3739

3840
def __next__(self):
3941
if self._current_index >= self.page_size:
40-
self._page = self._fetch_next_page()
42+
_, self._items = self._fetch_next_page()
4143
self._current_index = 0
4244

43-
if not self._page:
45+
if not self._items:
4446
raise StopIteration
4547

46-
item = self._page[self._current_index]
48+
item = self._items[self._current_index]
4749
self._current_index += 1
4850
return item
4951

@@ -55,12 +57,15 @@ def pages(self):
5557
paginated_data = self._get_new_self_instance()
5658

5759
while True:
58-
paginated_data._page = paginated_data._fetch_next_page(should_convert_items=False)
59-
if not paginated_data._page:
60+
metadata, paginated_data._page = paginated_data._fetch_next_page(is_page_iterator=True)
61+
if not paginated_data._page or not metadata:
6062
break
61-
yield paginated_data._page
63+
paginated_data._items = self._api_call.get_paginated_item_converter(paginated_data._page)
64+
if not paginated_data._items:
65+
break
66+
yield self._page_creator(metadata, paginated_data._page)
6267

63-
def _fetch_next_page(self, should_convert_items=True):
68+
def _fetch_next_page(self, is_page_iterator=False):
6469
for pagination_strategy in self._pagination_strategies:
6570
request_builder = pagination_strategy.apply(self)
6671
if request_builder is None:
@@ -69,10 +74,12 @@ def _fetch_next_page(self, should_convert_items=True):
6974
response = self._api_call.clone(
7075
global_configuration=self._global_configuration, request_builder=request_builder
7176
).execute()
72-
return self._api_call.get_paginated_item_converter(response) if should_convert_items else response
77+
response = response if is_page_iterator else self._api_call.get_paginated_item_converter(response)
78+
metadata = pagination_strategy.metadata if is_page_iterator else None
79+
return metadata, response
7380
except Exception as ex:
7481
raise ex
7582
return []
7683

7784
def _get_new_self_instance(self):
78-
return PaginatedData(self._api_call.clone(request_builder=self._initial_request_builder))
85+
return PaginatedData(self._api_call.clone(request_builder=self._initial_request_builder), self._page_creator)

apimatic_core/pagination/pagination_strategy.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ class PaginationStrategy(ABC):
1111
to provide specific pagination management logic for paginated API responses.
1212
"""
1313

14+
def __init__(self, metadata_creator):
15+
if metadata_creator is None:
16+
raise ValueError("Metadata creator for offset based pagination cannot be None")
17+
18+
self._metadata_creator = metadata_creator
19+
1420
@abstractmethod
1521
def apply(self, paginated_data):
1622
"""
@@ -24,6 +30,11 @@ def apply(self, paginated_data):
2430
"""
2531
...
2632

33+
@property
34+
@abstractmethod
35+
def metadata(self):
36+
...
37+
2738
@staticmethod
2839
def get_updated_request_builder(request_builder, input_pointer, offset):
2940
"""

0 commit comments

Comments
 (0)