Skip to content

Commit 72f74df

Browse files
seer-by-sentry[bot]mhieta
authored andcommitted
feat: handle LinkedEvents API timeouts for popular keywords
Add graceful degradation for LinkedEvents API timeout handling in popularKultusKeywords resolver. The resolver now continues processing remaining keyword sets even if one request fails due to timeout or connection errors. Refs: PT-1990
1 parent e560c59 commit 72f74df

2 files changed

Lines changed: 134 additions & 7 deletions

File tree

graphene_linked_events/schema.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import math
44
from types import SimpleNamespace
55

6+
import requests
67
from django.conf import settings
78
from django.core.exceptions import PermissionDenied
89
from django.db import transaction
@@ -661,14 +662,29 @@ def resolve_popular_kultus_keywords(parent, info, **kwargs):
661662
]
662663
keywords = {}
663664

665+
# Graceful degradation: Continue processing remaining keyword sets
666+
# even if one API request fails due to timeout or other errors.
667+
# This prevents the entire query from failing when the LinkedEvents API
668+
# is slow or experiencing issues.
664669
for set_id in set_ids:
665-
response = api_client.retrieve(
666-
"keyword_set", set_id, params={"include": "keywords"}
667-
)
668-
response = json2obj(format_response(response))
669-
for keyword in response.keywords:
670-
if show_all_keywords or keyword.n_events > 0:
671-
keywords[keyword.id] = keyword
670+
try:
671+
response = api_client.retrieve(
672+
"keyword_set", set_id, params={"include": "keywords"}
673+
)
674+
response = json2obj(format_response(response))
675+
for keyword in response.keywords:
676+
if show_all_keywords or keyword.n_events > 0:
677+
keywords[keyword.id] = keyword
678+
except (
679+
requests.exceptions.ConnectTimeout,
680+
requests.exceptions.Timeout,
681+
requests.exceptions.RequestException,
682+
) as e:
683+
# Log the error but continue processing other keyword sets
684+
logger.warning(
685+
f"Failed to fetch keyword set '{set_id}' from LinkedEvents API: {e}"
686+
)
687+
continue
672688

673689
unique_keywords = sorted(
674690
keywords.values(), key=lambda x: x.n_events, reverse=True

graphene_linked_events/tests/test_api.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
EVENT_DATA,
2828
EVENTS_DATA,
2929
KEYWORD_SET_DATA,
30+
POPULAR_KEYWORD_SET_DATA,
3031
UPDATE_EVENT_DATA,
3132
)
3233
from graphene_linked_events.tests.utils import MockResponse
@@ -2130,6 +2131,116 @@ def test_get_popular_kultus_keywords(
21302131
snapshot.assert_match(executed)
21312132

21322133

2134+
def test_get_popular_kultus_keywords_with_timeout(api_client, monkeypatch, settings):
2135+
"""Test that popularKultusKeywords handles ConnectTimeout gracefully."""
2136+
from requests.exceptions import ConnectTimeout
2137+
2138+
def mock_retrieve_with_timeout(
2139+
self, resource, id, params=None, is_event_staff=False
2140+
):
2141+
# Simulate timeout for the first keyword set
2142+
if id == settings.KEYWORD_SET_ID_MAPPING["CATEGORY"]:
2143+
raise ConnectTimeout("Connection to api.hel.fi timed out")
2144+
# Return valid data for other keyword sets
2145+
return MockResponse(status_code=200, json_data=POPULAR_KEYWORD_SET_DATA)
2146+
2147+
monkeypatch.setattr(
2148+
LinkedEventsApiClient,
2149+
"retrieve",
2150+
mock_retrieve_with_timeout,
2151+
)
2152+
2153+
executed = api_client.execute(
2154+
POPULAR_KULTUS_KEYWORDS_QUERY,
2155+
variables={"amount": None, "showAllKeywords": True},
2156+
)
2157+
2158+
# Should not error out, should return partial results
2159+
assert "errors" not in executed
2160+
assert executed["data"]["popularKultusKeywords"] is not None
2161+
# Should still have keywords from the other two sets
2162+
assert executed["data"]["popularKultusKeywords"]["meta"]["count"] >= 0
2163+
2164+
2165+
def test_get_popular_kultus_keywords_continues_after_timeout(
2166+
api_client, monkeypatch, settings
2167+
):
2168+
"""Test that resolver continues processing after one keyword set times out."""
2169+
from requests.exceptions import ConnectTimeout
2170+
2171+
call_count = {"count": 0}
2172+
set_ids_called = []
2173+
2174+
def mock_retrieve_with_partial_timeout(
2175+
self, resource, id, params=None, is_event_staff=False
2176+
):
2177+
call_count["count"] += 1
2178+
set_ids_called.append(id)
2179+
# Timeout on ADDITIONAL_CRITERIA (middle set)
2180+
if id == settings.KEYWORD_SET_ID_MAPPING["ADDITIONAL_CRITERIA"]:
2181+
raise ConnectTimeout("Connection timeout")
2182+
return MockResponse(status_code=200, json_data=POPULAR_KEYWORD_SET_DATA)
2183+
2184+
monkeypatch.setattr(
2185+
LinkedEventsApiClient,
2186+
"retrieve",
2187+
mock_retrieve_with_partial_timeout,
2188+
)
2189+
2190+
executed = api_client.execute(
2191+
POPULAR_KULTUS_KEYWORDS_QUERY,
2192+
variables={"amount": None, "showAllKeywords": True},
2193+
)
2194+
2195+
# All 3 keyword sets should have been attempted
2196+
assert call_count["count"] == 3
2197+
assert len(set_ids_called) == 3
2198+
# Should not error
2199+
assert "errors" not in executed
2200+
# Should return results from the 2 successful requests
2201+
assert executed["data"]["popularKultusKeywords"]["meta"]["count"] > 0
2202+
2203+
2204+
def test_get_popular_kultus_keywords_partial_results(api_client, monkeypatch, settings):
2205+
"""Test partial results when some API requests fail."""
2206+
from requests.exceptions import Timeout
2207+
2208+
def mock_retrieve_with_mixed_results(
2209+
self, resource, id, params=None, is_event_staff=False
2210+
):
2211+
# Fail TARGET_GROUP request
2212+
if id == settings.KEYWORD_SET_ID_MAPPING["TARGET_GROUP"]:
2213+
raise Timeout("Request timeout")
2214+
# Return data with different keywords for other sets
2215+
keyword_data = {
2216+
**POPULAR_KEYWORD_SET_DATA,
2217+
"keywords": [
2218+
{
2219+
**POPULAR_KEYWORD_SET_DATA["keywords"][0],
2220+
"id": f"keyword:{id}",
2221+
}
2222+
],
2223+
}
2224+
return MockResponse(status_code=200, json_data=keyword_data)
2225+
2226+
monkeypatch.setattr(
2227+
LinkedEventsApiClient,
2228+
"retrieve",
2229+
mock_retrieve_with_mixed_results,
2230+
)
2231+
2232+
executed = api_client.execute(
2233+
POPULAR_KULTUS_KEYWORDS_QUERY,
2234+
variables={"amount": None, "showAllKeywords": True},
2235+
)
2236+
2237+
# Should return partial results from successful requests
2238+
assert "errors" not in executed
2239+
assert executed["data"]["popularKultusKeywords"] is not None
2240+
# Should have keywords from 2 out of 3 sets
2241+
assert executed["data"]["popularKultusKeywords"]["meta"]["count"] == 2
2242+
2243+
21332244
@pytest.mark.parametrize(
21342245
"status_code,error_cls",
21352246
[(400, ApiBadRequestError), (404, ObjectDoesNotExistError)],

0 commit comments

Comments
 (0)