From ac89a2fe65425b9b91b61b594e20455ae999aab8 Mon Sep 17 00:00:00 2001 From: luisfelipec95 Date: Fri, 7 Nov 2025 15:26:36 -0500 Subject: [PATCH 1/5] feat: add pagination support to fetch all badge templates --- credentials/apps/badges/credly/api_client.py | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/credentials/apps/badges/credly/api_client.py b/credentials/apps/badges/credly/api_client.py index 3b587045d..0028b798a 100644 --- a/credentials/apps/badges/credly/api_client.py +++ b/credentials/apps/badges/credly/api_client.py @@ -86,7 +86,29 @@ def fetch_badge_templates(self): """ Fetches the badge templates from the Credly API. """ - return self.perform_request("get", f"badge_templates/?filter=state::{CredlyBadgeTemplate.STATES.active}") + results = [] + url = f"badge_templates/?filter=state::{CredlyBadgeTemplate.STATES.active}" + response = self.perform_request("get", url) + results.extend(response.get("data", [])) + + metadata = response.get("metadata", {}) + total_pages = metadata.get("total_pages", 1) + next_page_url = metadata.get("next_page_url") + + # Loop through all remaining pages based on the total_pages value. + # For each iteration, fetch data using the 'next_page_url' provided by the API, + # append the results to the main list, and update the URL for the next request. + # The loop stops when there are no more pages to retrieve. + for _ in range(2, total_pages + 1): + if not next_page_url: + break + + response = self.perform_request("get", next_page_url) + results.extend(response.get("data", [])) + + next_page_url = response.get("metadata", {}).get("next_page_url") + + return {"data": results} def fetch_event_information(self, event_id): """ From 7b98169b5bdbdf7078747d040efb222d1d3eb449 Mon Sep 17 00:00:00 2001 From: luisfelipec95 Date: Tue, 25 Nov 2025 17:52:09 -0500 Subject: [PATCH 2/5] chore: add minimal throttle implementation --- credentials/apps/badges/credly/api_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/credentials/apps/badges/credly/api_client.py b/credentials/apps/badges/credly/api_client.py index 0028b798a..bb5dc0b18 100644 --- a/credentials/apps/badges/credly/api_client.py +++ b/credentials/apps/badges/credly/api_client.py @@ -1,5 +1,6 @@ import base64 import logging +import time from functools import lru_cache from urllib.parse import urljoin @@ -103,6 +104,8 @@ def fetch_badge_templates(self): if not next_page_url: break + time.sleep(0.2) + response = self.perform_request("get", next_page_url) results.extend(response.get("data", [])) From a1d644f29763b4907be6c3eb76f9c2fae8fcd669 Mon Sep 17 00:00:00 2001 From: luisfelipec95 Date: Thu, 27 Nov 2025 16:16:54 -0500 Subject: [PATCH 3/5] chore: add basic retry handling for rate-limit and timeout errors in Credly badge template fetch --- credentials/apps/badges/credly/api_client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/credentials/apps/badges/credly/api_client.py b/credentials/apps/badges/credly/api_client.py index bb5dc0b18..992f01dd2 100644 --- a/credentials/apps/badges/credly/api_client.py +++ b/credentials/apps/badges/credly/api_client.py @@ -106,9 +106,18 @@ def fetch_badge_templates(self): time.sleep(0.2) - response = self.perform_request("get", next_page_url) - results.extend(response.get("data", [])) + for attempt in range(3): + try: + response = self.perform_request("get", next_page_url) + break + except (requests.Timeout, requests.ConnectionError) as exc: + sleep_time = 0.5 * (2 ** attempt) + time.sleep(sleep_time) + + if attempt == 2: + raise CredlyError(f"Failed to fetch page due to network error: {exc}") + results.extend(response.get("data", [])) next_page_url = response.get("metadata", {}).get("next_page_url") return {"data": results} From b8daf2dc414aa56e1fe68b412b41d30c3227c647 Mon Sep 17 00:00:00 2001 From: luisfelipec95 Date: Thu, 27 Nov 2025 16:34:33 -0500 Subject: [PATCH 4/5] fix: apply formatting --- credentials/apps/badges/credly/api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/apps/badges/credly/api_client.py b/credentials/apps/badges/credly/api_client.py index 992f01dd2..b6584d579 100644 --- a/credentials/apps/badges/credly/api_client.py +++ b/credentials/apps/badges/credly/api_client.py @@ -111,7 +111,7 @@ def fetch_badge_templates(self): response = self.perform_request("get", next_page_url) break except (requests.Timeout, requests.ConnectionError) as exc: - sleep_time = 0.5 * (2 ** attempt) + sleep_time = 0.5 * (2**attempt) time.sleep(sleep_time) if attempt == 2: From 8541d982182a480639aeebc2dca21066b2bee5c2 Mon Sep 17 00:00:00 2001 From: luisfelipec95 Date: Thu, 27 Nov 2025 17:14:25 -0500 Subject: [PATCH 5/5] test: update expected return format for fetch_badge_templates --- credentials/apps/badges/tests/test_api_client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/credentials/apps/badges/tests/test_api_client.py b/credentials/apps/badges/tests/test_api_client.py index 72b544b32..50c173f92 100644 --- a/credentials/apps/badges/tests/test_api_client.py +++ b/credentials/apps/badges/tests/test_api_client.py @@ -71,10 +71,13 @@ def test_fetch_organization(self): def test_fetch_badge_templates(self): with mock.patch.object(CredlyAPIClient, "perform_request") as mock_perform_request: - mock_perform_request.return_value = {"badge_templates": ["template1", "template2"]} + mock_perform_request.return_value = { + "data": ["template1", "template2"], + "metadata": {"total_pages": 1}, + } result = self.api_client.fetch_badge_templates() mock_perform_request.assert_called_once_with("get", "badge_templates/?filter=state::active") - self.assertEqual(result, {"badge_templates": ["template1", "template2"]}) + self.assertEqual(result, {"data": ["template1", "template2"]}) def test_fetch_event_information(self): event_id = "event123"