diff --git a/README.md b/README.md index c737852c..8acf3cd7 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,10 @@ all_contacts = api_client.crm.contacts.get_all() Please note that pagination is used under the hood to get all results. +Pass `limit` to cap the total number of results, for example `get_all(limit=100)`. +Omitting it fetches all results. A limit of zero returns an empty list without an API +request; negative limits raise `ValueError`. + ## Search `do_search` method is available for all objects (Companies, Contacts, Deals and etc). diff --git a/hubspot/utils/objects.py b/hubspot/utils/objects.py index b68c6976..6aa35d63 100644 --- a/hubspot/utils/objects.py +++ b/hubspot/utils/objects.py @@ -8,6 +8,11 @@ def fetch_all(get_page_api_client, **kwargs): max_page_size = PROPERTY_HISTORY_MAX_SIZE if "properties_with_history" in kwargs else PAGE_MAX_SIZE limit = kwargs.pop("limit", None) + if limit is not None: + if limit < 0: + raise ValueError("limit must be non-negative") + if limit == 0: + return results total_fetched = 0 while True: diff --git a/tests/spec/utils/test_fetch_all.py b/tests/spec/utils/test_fetch_all.py new file mode 100644 index 00000000..5173fdbc --- /dev/null +++ b/tests/spec/utils/test_fetch_all.py @@ -0,0 +1,39 @@ +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest + +from hubspot.utils.objects import fetch_all + + +def test_zero_limit_does_not_fetch(): + client = Mock() + client.get_page.return_value = SimpleNamespace(results=[1], paging=None) + assert fetch_all(client, limit=0) == [] + client.get_page.assert_not_called() + + +def test_negative_limit_does_not_fetch(): + client = Mock() + client.get_page.return_value = SimpleNamespace(results=[1], paging=None) + with pytest.raises(ValueError, match="limit"): + fetch_all(client, limit=-1) + client.get_page.assert_not_called() + + +@pytest.mark.parametrize("limit", [None, 101]) +def test_multiple_pages(limit): + client = Mock() + client.get_page.side_effect = [ + SimpleNamespace( + results=list(range(100)), + paging=SimpleNamespace(next=SimpleNamespace(after="next")), + ), + SimpleNamespace(results=[100], paging=None), + ] + assert fetch_all(client, limit=limit) == list(range(101)) + assert client.get_page.call_args_list[0].kwargs == {"after": None, "limit": 100} + assert client.get_page.call_args_list[1].kwargs == { + "after": "next", + "limit": 1 if limit else 100, + }