From ac447659887f335529345ed478cff69196d5fca1 Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Mon, 22 Dec 2025 23:24:29 +0530 Subject: [PATCH 1/8] feat: Pagination helper with tests --- examples/general/pagination_helper.py | 55 +++++ nisystemlink/clients/core/helpers/__init__.py | 1 + .../clients/core/helpers/_pagination.py | 59 +++++ tests/core/test_pagination.py | 213 ++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 examples/general/pagination_helper.py create mode 100644 nisystemlink/clients/core/helpers/_pagination.py create mode 100644 tests/core/test_pagination.py diff --git a/examples/general/pagination_helper.py b/examples/general/pagination_helper.py new file mode 100644 index 00000000..d135ca9e --- /dev/null +++ b/examples/general/pagination_helper.py @@ -0,0 +1,55 @@ +"""Example demonstrating the paginate helper for iterating through paginated API results. + +This example shows how to use the paginate() helper function to automatically +handle continuation tokens when fetching all results from a paginated API. +""" + +from nisystemlink.clients.core.helpers import paginate +from nisystemlink.clients.testmonitor import TestMonitorClient +from nisystemlink.clients.testmonitor.models import Result + +# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink +server_configuration = None + +# # Example of setting up the server configuration to point to your instance of SystemLink Enterprise +# from nisystemlink.clients.core import HttpConfiguration +# server_configuration = HttpConfiguration( +# server_uri="https://yourserver.yourcompany.com", +# api_key="YourAPIKeyGeneratedFromSystemLink", +# ) + +client = TestMonitorClient(configuration=server_configuration) + +# Example 1: Basic usage - iterate through all results automatically +print("Example 1: Iterating through all results") +print("-" * 50) +result: Result +for result in paginate(client.get_results, items_field="results", take=100): + print(f"Result ID: {result.id}, Status: {result.status.status_type}") # type: ignore[attr-defined] + +# Example 2: Collect all results into a list +print("\nExample 2: Collecting all results into a list") +print("-" * 50) +all_results: list[Result] = list( + paginate(client.get_results, items_field="results", take=100) +) +print(f"Total results retrieved: {len(all_results)}") + +# Example 3: Process in chunks while still using automatic pagination +print("\nExample 3: Processing results in batches") +print("-" * 50) +batch: list[Result] = [] +batch_size = 50 +for i, result in enumerate( + paginate(client.get_results, items_field="results", take=100), start=1 +): + batch.append(result) + if len(batch) >= batch_size: + # Process batch + print(f"Processing batch of {len(batch)} results...") + # Do something with batch + batch = [] + +# Process remaining items +if batch: + print(f"Processing final batch of {len(batch)} results...") diff --git a/nisystemlink/clients/core/helpers/__init__.py b/nisystemlink/clients/core/helpers/__init__.py index 402880f6..f3533f2a 100644 --- a/nisystemlink/clients/core/helpers/__init__.py +++ b/nisystemlink/clients/core/helpers/__init__.py @@ -1,4 +1,5 @@ from ._iterator_file_like import IteratorFileLike from ._minion_id import read_minion_id +from ._pagination import paginate # flake8: noqa diff --git a/nisystemlink/clients/core/helpers/_pagination.py b/nisystemlink/clients/core/helpers/_pagination.py new file mode 100644 index 00000000..f48c41f4 --- /dev/null +++ b/nisystemlink/clients/core/helpers/_pagination.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from typing import Any, Callable, Generator, TypeVar + +ItemType = TypeVar("ItemType") + + +def paginate( + fetch_function: Callable[..., Any], + items_field: str = "items", + continuation_token_field: str = "continuation_token", + **fetch_kwargs: Any, +) -> Generator[ItemType, None, None]: + """Generate items from paginated API responses using continuation tokens. + + This helper function provides a convenient way to iterate over all items + from a paginated API endpoint that uses continuation tokens. It automatically + handles fetching subsequent pages until all results are retrieved. + + Args: + fetch_function: The API function to call to fetch each page of results. + Must accept a ``continuation_token`` parameter (or a parameter name + matching ``continuation_token_field``). + items_field: The name of the field in the response object that contains + the list of items to yield. Defaults to "items". + continuation_token_field: The name of the field in the response object + that contains the continuation token. Defaults to "continuation_token". + **fetch_kwargs: Additional keyword arguments to pass to the fetch function + on every call (e.g., filters, take limits, etc.). + + Yields: + Individual items from each page of results. + + Note: + The fetch function will be called with the continuation_token parameter + set to None on the first call, then with each subsequent token until + the response contains a None continuation token. + """ + continuation_token = None + + while True: + # Set the continuation token parameter for this page + fetch_kwargs[continuation_token_field] = continuation_token + + # Fetch the current page + response = fetch_function(**fetch_kwargs) + + # Get the items from the response using the specified field name + items = getattr(response, items_field, []) + + # Yield each item individually + for item in items: + yield item + + # Get the continuation token for the next page + continuation_token = getattr(response, continuation_token_field, None) + + # Stop if there are no more pages + if continuation_token is None: + break diff --git a/tests/core/test_pagination.py b/tests/core/test_pagination.py new file mode 100644 index 00000000..991fcc52 --- /dev/null +++ b/tests/core/test_pagination.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +"""Tests for the pagination helper function.""" + +from typing import Any, List +from unittest.mock import MagicMock + +from nisystemlink.clients.core.helpers import paginate + + +class MockResponse: + """Mock API response object for testing pagination.""" + + def __init__(self, items: List[Any], continuation_token: str | None = None): + self.items = items + self.continuation_token = continuation_token + + +class MockResponseCustomFields: + """Mock API response with custom field names.""" + + def __init__(self, results: List[Any], next_token: str | None = None): + self.results = results + self.next_token = next_token + + +class TestPaginate: + """Tests for the paginate helper function.""" + + def test__paginate_single_page__yields_all_items(self): + """Test pagination with a single page of results.""" + # Arrange + items = [1, 2, 3, 4, 5] + mock_fetch = MagicMock(return_value=MockResponse(items, None)) + + # Act + result = list(paginate(mock_fetch)) + + # Assert + assert result == items + assert mock_fetch.call_count == 1 + mock_fetch.assert_called_with(continuation_token=None) + + def test__paginate_multiple_pages__yields_all_items_in_order(self): + """Test pagination with multiple pages.""" + # Arrange + page1_items = [1, 2, 3] + page2_items = [4, 5, 6] + page3_items = [7, 8, 9] + + mock_fetch = MagicMock( + side_effect=[ + MockResponse(page1_items, "token1"), + MockResponse(page2_items, "token2"), + MockResponse(page3_items, None), + ] + ) + + # Act + result = list(paginate(mock_fetch)) + + # Assert + assert result == [1, 2, 3, 4, 5, 6, 7, 8, 9] + assert mock_fetch.call_count == 3 + assert mock_fetch.call_args_list[0][1]["continuation_token"] is None + assert mock_fetch.call_args_list[1][1]["continuation_token"] == "token1" + assert mock_fetch.call_args_list[2][1]["continuation_token"] == "token2" + + def test__paginate_with_custom_field_names__yields_all_items(self): + """Test pagination with custom field names.""" + # Arrange + page1_results = ["a", "b"] + page2_results = ["c", "d"] + + mock_fetch = MagicMock( + side_effect=[ + MockResponseCustomFields(page1_results, "next1"), + MockResponseCustomFields(page2_results, None), + ] + ) + + # Act + result = list( + paginate( + mock_fetch, items_field="results", continuation_token_field="next_token" + ) + ) + + # Assert + assert result == ["a", "b", "c", "d"] + assert mock_fetch.call_count == 2 + + def test__paginate_with_additional_kwargs__passes_kwargs_to_fetch(self): + """Test that additional keyword arguments are passed to the fetch function.""" + # Arrange + items = [1, 2, 3] + mock_fetch = MagicMock(return_value=MockResponse(items, None)) + + # Act + result = list( + paginate(mock_fetch, take=10, filter="status==PASSED", return_count=True) + ) + + # Assert + assert result == items + mock_fetch.assert_called_once_with( + continuation_token=None, take=10, filter="status==PASSED", return_count=True + ) + + def test__paginate_empty_results__returns_empty(self): + """Test pagination with no results.""" + # Arrange + mock_fetch = MagicMock(return_value=MockResponse([], None)) + + # Act + result = list(paginate(mock_fetch)) + + # Assert + assert result == [] + assert mock_fetch.call_count == 1 + + def test__paginate_empty_page_in_middle__yields_only_non_empty_pages(self): + """Test pagination when a middle page is empty.""" + # Arrange + page1_items = [1, 2, 3] + page2_items = [] + page3_items = [4, 5] + + mock_fetch = MagicMock( + side_effect=[ + MockResponse(page1_items, "token1"), + MockResponse(page2_items, "token2"), + MockResponse(page3_items, None), + ] + ) + + # Act + result = list(paginate(mock_fetch)) + + # Assert + assert result == [1, 2, 3, 4, 5] + assert mock_fetch.call_count == 3 + + def test__paginate_generator__can_be_used_in_for_loop(self): + """Test that paginate works as expected in a for loop.""" + # Arrange + page1_items = [1, 2] + page2_items = [3, 4] + mock_fetch = MagicMock( + side_effect=[ + MockResponse(page1_items, "token1"), + MockResponse(page2_items, None), + ] + ) + + # Act + collected = [] + for item in paginate(mock_fetch): + collected.append(item * 2) + + # Assert + assert collected == [2, 4, 6, 8] + + def test__paginate_lazy_evaluation__only_fetches_as_needed(self): + """Test that pagination is lazy and doesn't fetch all pages immediately.""" + # Arrange + page1_items = [1, 2, 3] + page2_items = [4, 5, 6] + mock_fetch = MagicMock( + side_effect=[ + MockResponse(page1_items, "token1"), + MockResponse(page2_items, None), + ] + ) + + # Act + gen = paginate(mock_fetch) + assert mock_fetch.call_count == 0 # Nothing called yet + + first_item = next(gen) + assert first_item == 1 + assert mock_fetch.call_count == 1 # First page fetched + + # Consume rest of first page + next(gen) # 2 + next(gen) # 3 + + # First page exhausted, but second page not yet fetched + assert mock_fetch.call_count == 1 + + # Fetch first item of second page + fourth_item = next(gen) + assert fourth_item == 4 + assert mock_fetch.call_count == 2 # Second page fetched + + def test__paginate_with_kwargs_persisted_across_pages__kwargs_used_on_all_calls( + self, + ): + """Test that kwargs are passed to all fetch function calls.""" + # Arrange + mock_fetch = MagicMock( + side_effect=[ + MockResponse([1, 2], "token1"), + MockResponse([3, 4], None), + ] + ) + + # Act + list(paginate(mock_fetch, take=100, filter="active==true")) + + # Assert + for call in mock_fetch.call_args_list: + assert call[1]["take"] == 100 + assert call[1]["filter"] == "active==true" From aa03e31b761bd22dbf85f1b9c040d37165946040 Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Tue, 6 Jan 2026 17:51:18 +0530 Subject: [PATCH 2/8] refactor: address PR comments --- .../clients/core/helpers/_pagination.py | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/nisystemlink/clients/core/helpers/_pagination.py b/nisystemlink/clients/core/helpers/_pagination.py index f48c41f4..07cb3167 100644 --- a/nisystemlink/clients/core/helpers/_pagination.py +++ b/nisystemlink/clients/core/helpers/_pagination.py @@ -1,29 +1,26 @@ # -*- coding: utf-8 -*- -from typing import Any, Callable, Generator, TypeVar +from typing import Any, Callable, Generator -ItemType = TypeVar("ItemType") +from nisystemlink.clients.core._uplink._with_paging import WithPaging def paginate( - fetch_function: Callable[..., Any], - items_field: str = "items", - continuation_token_field: str = "continuation_token", + fetch_function: Callable[..., WithPaging], + items_field: str, **fetch_kwargs: Any, -) -> Generator[ItemType, None, None]: +) -> Generator[Any, None, None]: """Generate items from paginated API responses using continuation tokens. This helper function provides a convenient way to iterate over all items from a paginated API endpoint that uses continuation tokens. It automatically - handles fetching subsequent pages until all results are retrieved. + handles fetching subsequent pages until all items are retrieved. Args: - fetch_function: The API function to call to fetch each page of results. - Must accept a ``continuation_token`` parameter (or a parameter name - matching ``continuation_token_field``). + fetch_function: The API function to call to fetch each page of items. + Must accept a ``continuation_token`` parameter and return a response + that derives from ``WithPaging``. items_field: The name of the field in the response object that contains - the list of items to yield. Defaults to "items". - continuation_token_field: The name of the field in the response object - that contains the continuation token. Defaults to "continuation_token". + the list of items to yield. **fetch_kwargs: Additional keyword arguments to pass to the fetch function on every call (e.g., filters, take limits, etc.). @@ -31,15 +28,15 @@ def paginate( Individual items from each page of results. Note: - The fetch function will be called with the continuation_token parameter - set to None on the first call, then with each subsequent token until - the response contains a None continuation token. + The fetch function will be called with the `continuation_token` parameter + set to `None` on the first call, then with each subsequent token until + the response contains a `None` continuation token. """ continuation_token = None while True: # Set the continuation token parameter for this page - fetch_kwargs[continuation_token_field] = continuation_token + fetch_kwargs["continuation_token"] = continuation_token # Fetch the current page response = fetch_function(**fetch_kwargs) @@ -52,8 +49,14 @@ def paginate( yield item # Get the continuation token for the next page - continuation_token = getattr(response, continuation_token_field, None) + next_continuation_token = response.continuation_token # Stop if there are no more pages - if continuation_token is None: + if next_continuation_token is None: break + + # Guard against infinite loop if continuation token doesn't change + if next_continuation_token == continuation_token: + raise RuntimeError("Continuation token did not change between iterations.") + + continuation_token = next_continuation_token From 18f86be7c3089d2284cf1021ab731a33fc7492b4 Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Tue, 6 Jan 2026 17:59:36 +0530 Subject: [PATCH 3/8] tests: refactor tests to work with updated function --- tests/core/test_pagination.py | 65 ++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/tests/core/test_pagination.py b/tests/core/test_pagination.py index 991fcc52..3fadf72d 100644 --- a/tests/core/test_pagination.py +++ b/tests/core/test_pagination.py @@ -4,6 +4,8 @@ from typing import Any, List from unittest.mock import MagicMock +import pytest + from nisystemlink.clients.core.helpers import paginate @@ -18,9 +20,9 @@ def __init__(self, items: List[Any], continuation_token: str | None = None): class MockResponseCustomFields: """Mock API response with custom field names.""" - def __init__(self, results: List[Any], next_token: str | None = None): + def __init__(self, results: List[Any], continuation_token: str | None = None): self.results = results - self.next_token = next_token + self.continuation_token = continuation_token class TestPaginate: @@ -33,7 +35,7 @@ def test__paginate_single_page__yields_all_items(self): mock_fetch = MagicMock(return_value=MockResponse(items, None)) # Act - result = list(paginate(mock_fetch)) + result = list(paginate(mock_fetch, "items")) # Assert assert result == items @@ -56,7 +58,7 @@ def test__paginate_multiple_pages__yields_all_items_in_order(self): ) # Act - result = list(paginate(mock_fetch)) + result = list(paginate(mock_fetch, "items")) # Assert assert result == [1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -65,8 +67,8 @@ def test__paginate_multiple_pages__yields_all_items_in_order(self): assert mock_fetch.call_args_list[1][1]["continuation_token"] == "token1" assert mock_fetch.call_args_list[2][1]["continuation_token"] == "token2" - def test__paginate_with_custom_field_names__yields_all_items(self): - """Test pagination with custom field names.""" + def test__paginate_with_custom_items_field__yields_all_items(self): + """Test pagination with custom items field name.""" # Arrange page1_results = ["a", "b"] page2_results = ["c", "d"] @@ -79,11 +81,7 @@ def test__paginate_with_custom_field_names__yields_all_items(self): ) # Act - result = list( - paginate( - mock_fetch, items_field="results", continuation_token_field="next_token" - ) - ) + result = list(paginate(mock_fetch, items_field="results")) # Assert assert result == ["a", "b", "c", "d"] @@ -97,7 +95,7 @@ def test__paginate_with_additional_kwargs__passes_kwargs_to_fetch(self): # Act result = list( - paginate(mock_fetch, take=10, filter="status==PASSED", return_count=True) + paginate(mock_fetch, "items", take=10, filter="status==PASSED", return_count=True) ) # Assert @@ -112,7 +110,7 @@ def test__paginate_empty_results__returns_empty(self): mock_fetch = MagicMock(return_value=MockResponse([], None)) # Act - result = list(paginate(mock_fetch)) + result = list(paginate(mock_fetch, "items")) # Assert assert result == [] @@ -134,7 +132,7 @@ def test__paginate_empty_page_in_middle__yields_only_non_empty_pages(self): ) # Act - result = list(paginate(mock_fetch)) + result = list(paginate(mock_fetch, "items")) # Assert assert result == [1, 2, 3, 4, 5] @@ -154,7 +152,7 @@ def test__paginate_generator__can_be_used_in_for_loop(self): # Act collected = [] - for item in paginate(mock_fetch): + for item in paginate(mock_fetch, "items"): collected.append(item * 2) # Assert @@ -173,7 +171,7 @@ def test__paginate_lazy_evaluation__only_fetches_as_needed(self): ) # Act - gen = paginate(mock_fetch) + gen = paginate(mock_fetch, "items") assert mock_fetch.call_count == 0 # Nothing called yet first_item = next(gen) @@ -205,9 +203,42 @@ def test__paginate_with_kwargs_persisted_across_pages__kwargs_used_on_all_calls( ) # Act - list(paginate(mock_fetch, take=100, filter="active==true")) + list(paginate(mock_fetch, "items", take=100, filter="active==true")) # Assert for call in mock_fetch.call_args_list: assert call[1]["take"] == 100 assert call[1]["filter"] == "active==true" + + def test__paginate_missing_items_field__yields_nothing(self): + """Test pagination when the items field doesn't exist on the response.""" + # Arrange + mock_response = MockResponse([], None) + # Remove the items field + delattr(mock_response, "items") + mock_fetch = MagicMock(return_value=mock_response) + + # Act + result = list(paginate(mock_fetch, "items")) + + # Assert + assert result == [] + assert mock_fetch.call_count == 1 + + def test__paginate_continuation_token_unchanged__raises_runtime_error(self): + """Test that an error is raised if the continuation token doesn't change.""" + # Arrange + # First call returns token1, second call returns token1 again (infinite loop) + mock_fetch = MagicMock( + side_effect=[ + MockResponse([1, 2], "token1"), + MockResponse([3, 4], "token1"), # Same token - should raise error + ] + ) + + # Act & Assert + with pytest.raises(RuntimeError, match="Continuation token did not change"): + list(paginate(mock_fetch, "items")) + + # Should have made 2 calls before detecting the issue + assert mock_fetch.call_count == 2 From c57aae0973db0a69caa0c800589fd48435b43eb0 Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Tue, 6 Jan 2026 19:19:36 +0530 Subject: [PATCH 4/8] refactor: fix black formatting --- tests/core/test_pagination.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/core/test_pagination.py b/tests/core/test_pagination.py index 3fadf72d..68f9bee3 100644 --- a/tests/core/test_pagination.py +++ b/tests/core/test_pagination.py @@ -95,7 +95,9 @@ def test__paginate_with_additional_kwargs__passes_kwargs_to_fetch(self): # Act result = list( - paginate(mock_fetch, "items", take=10, filter="status==PASSED", return_count=True) + paginate( + mock_fetch, "items", take=10, filter="status==PASSED", return_count=True + ) ) # Assert From baa76135c2ecfae5d46a2b08251ad5fba2f88475 Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Tue, 6 Jan 2026 22:01:02 +0530 Subject: [PATCH 5/8] fix linting --- tests/core/test_pagination.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/test_pagination.py b/tests/core/test_pagination.py index 68f9bee3..b1ec23e7 100644 --- a/tests/core/test_pagination.py +++ b/tests/core/test_pagination.py @@ -5,7 +5,6 @@ from unittest.mock import MagicMock import pytest - from nisystemlink.clients.core.helpers import paginate From 14ef40121de718456ea88d68b82fcea356066a5e Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Wed, 7 Jan 2026 17:41:11 +0530 Subject: [PATCH 6/8] refactor: fix typing --- examples/general/pagination_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/general/pagination_helper.py b/examples/general/pagination_helper.py index d135ca9e..1dfe9866 100644 --- a/examples/general/pagination_helper.py +++ b/examples/general/pagination_helper.py @@ -25,7 +25,7 @@ print("-" * 50) result: Result for result in paginate(client.get_results, items_field="results", take=100): - print(f"Result ID: {result.id}, Status: {result.status.status_type}") # type: ignore[attr-defined] + print(f"Result ID: {result.id}, Status: {result.status.status_type}") # type: ignore[union-attr] # Example 2: Collect all results into a list print("\nExample 2: Collecting all results into a list") From 7e433412f9370517eb51b24cea0bbbe4e876945b Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Wed, 7 Jan 2026 21:40:07 +0530 Subject: [PATCH 7/8] refactor: update names to be relevant --- tests/core/test_pagination.py | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/core/test_pagination.py b/tests/core/test_pagination.py index b1ec23e7..7a65b846 100644 --- a/tests/core/test_pagination.py +++ b/tests/core/test_pagination.py @@ -8,16 +8,16 @@ from nisystemlink.clients.core.helpers import paginate -class MockResponse: - """Mock API response object for testing pagination.""" +class MockResponseWithItems: + """Mock API response object with 'items' field for testing pagination.""" def __init__(self, items: List[Any], continuation_token: str | None = None): self.items = items self.continuation_token = continuation_token -class MockResponseCustomFields: - """Mock API response with custom field names.""" +class MockResponseWithResults: + """Mock API response object with 'results' field for testing pagination.""" def __init__(self, results: List[Any], continuation_token: str | None = None): self.results = results @@ -31,7 +31,7 @@ def test__paginate_single_page__yields_all_items(self): """Test pagination with a single page of results.""" # Arrange items = [1, 2, 3, 4, 5] - mock_fetch = MagicMock(return_value=MockResponse(items, None)) + mock_fetch = MagicMock(return_value=MockResponseWithItems(items, None)) # Act result = list(paginate(mock_fetch, "items")) @@ -50,9 +50,9 @@ def test__paginate_multiple_pages__yields_all_items_in_order(self): mock_fetch = MagicMock( side_effect=[ - MockResponse(page1_items, "token1"), - MockResponse(page2_items, "token2"), - MockResponse(page3_items, None), + MockResponseWithItems(page1_items, "token1"), + MockResponseWithItems(page2_items, "token2"), + MockResponseWithItems(page3_items, None), ] ) @@ -66,16 +66,16 @@ def test__paginate_multiple_pages__yields_all_items_in_order(self): assert mock_fetch.call_args_list[1][1]["continuation_token"] == "token1" assert mock_fetch.call_args_list[2][1]["continuation_token"] == "token2" - def test__paginate_with_custom_items_field__yields_all_items(self): - """Test pagination with custom items field name.""" + def test__paginate_with_results_field__yields_all_items(self): + """Test pagination with 'results' as the items field name.""" # Arrange page1_results = ["a", "b"] page2_results = ["c", "d"] mock_fetch = MagicMock( side_effect=[ - MockResponseCustomFields(page1_results, "next1"), - MockResponseCustomFields(page2_results, None), + MockResponseWithResults(page1_results, "next1"), + MockResponseWithResults(page2_results, None), ] ) @@ -90,7 +90,7 @@ def test__paginate_with_additional_kwargs__passes_kwargs_to_fetch(self): """Test that additional keyword arguments are passed to the fetch function.""" # Arrange items = [1, 2, 3] - mock_fetch = MagicMock(return_value=MockResponse(items, None)) + mock_fetch = MagicMock(return_value=MockResponseWithItems(items, None)) # Act result = list( @@ -108,7 +108,7 @@ def test__paginate_with_additional_kwargs__passes_kwargs_to_fetch(self): def test__paginate_empty_results__returns_empty(self): """Test pagination with no results.""" # Arrange - mock_fetch = MagicMock(return_value=MockResponse([], None)) + mock_fetch = MagicMock(return_value=MockResponseWithItems([], None)) # Act result = list(paginate(mock_fetch, "items")) @@ -126,9 +126,9 @@ def test__paginate_empty_page_in_middle__yields_only_non_empty_pages(self): mock_fetch = MagicMock( side_effect=[ - MockResponse(page1_items, "token1"), - MockResponse(page2_items, "token2"), - MockResponse(page3_items, None), + MockResponseWithItems(page1_items, "token1"), + MockResponseWithItems(page2_items, "token2"), + MockResponseWithItems(page3_items, None), ] ) @@ -146,8 +146,8 @@ def test__paginate_generator__can_be_used_in_for_loop(self): page2_items = [3, 4] mock_fetch = MagicMock( side_effect=[ - MockResponse(page1_items, "token1"), - MockResponse(page2_items, None), + MockResponseWithItems(page1_items, "token1"), + MockResponseWithItems(page2_items, None), ] ) @@ -166,8 +166,8 @@ def test__paginate_lazy_evaluation__only_fetches_as_needed(self): page2_items = [4, 5, 6] mock_fetch = MagicMock( side_effect=[ - MockResponse(page1_items, "token1"), - MockResponse(page2_items, None), + MockResponseWithItems(page1_items, "token1"), + MockResponseWithItems(page2_items, None), ] ) @@ -198,8 +198,8 @@ def test__paginate_with_kwargs_persisted_across_pages__kwargs_used_on_all_calls( # Arrange mock_fetch = MagicMock( side_effect=[ - MockResponse([1, 2], "token1"), - MockResponse([3, 4], None), + MockResponseWithItems([1, 2], "token1"), + MockResponseWithItems([3, 4], None), ] ) @@ -214,7 +214,7 @@ def test__paginate_with_kwargs_persisted_across_pages__kwargs_used_on_all_calls( def test__paginate_missing_items_field__yields_nothing(self): """Test pagination when the items field doesn't exist on the response.""" # Arrange - mock_response = MockResponse([], None) + mock_response = MockResponseWithItems([], None) # Remove the items field delattr(mock_response, "items") mock_fetch = MagicMock(return_value=mock_response) @@ -232,8 +232,8 @@ def test__paginate_continuation_token_unchanged__raises_runtime_error(self): # First call returns token1, second call returns token1 again (infinite loop) mock_fetch = MagicMock( side_effect=[ - MockResponse([1, 2], "token1"), - MockResponse([3, 4], "token1"), # Same token - should raise error + MockResponseWithItems([1, 2], "token1"), + MockResponseWithItems([3, 4], "token1"), # Same token - should raise error ] ) From 935094b3f742a7da4607e6c2595e3bbac64e4aba Mon Sep 17 00:00:00 2001 From: Santhosh Ramaraj Date: Wed, 7 Jan 2026 23:34:35 +0530 Subject: [PATCH 8/8] refactor: run black formatting --- tests/core/test_pagination.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/core/test_pagination.py b/tests/core/test_pagination.py index 7a65b846..4518c2c9 100644 --- a/tests/core/test_pagination.py +++ b/tests/core/test_pagination.py @@ -233,7 +233,9 @@ def test__paginate_continuation_token_unchanged__raises_runtime_error(self): mock_fetch = MagicMock( side_effect=[ MockResponseWithItems([1, 2], "token1"), - MockResponseWithItems([3, 4], "token1"), # Same token - should raise error + MockResponseWithItems( + [3, 4], "token1" + ), # Same token - should raise error ] )