Repository: alex9smith/gdelt-doc-api

Issue:
timeline_search fails with KeyError: 'timeline' in version 1.10.1
Hi there,

I'm encountering an issue with the timeline_search method in gdeltdoc version 1.10.1 when using the "timelinetone" mode. Every time I call the method, I get a KeyError: 'timeline' before any debug output can be captured. Below are the details of my setup and the steps to reproduce the issue:

Steps to Reproduce:

1. Import GdeltDoc and Filters from gdeltdoc.

2. Create a filter as follows:

f = Filters(
    keyword="AMZN stock",  # example symbol
    timespan="1h"
)

3. Instantiate GdeltDoc and call:
gd = GdeltDoc()
timeline_df = gd.timeline_search("timelinetone", f)

The call raises a KeyError: 'timeline'.

Expected Behavior:
I expected the API response to contain a key that the library maps to "timelinetone" (or directly a column named "timelinetone" in the returned DataFrame), allowing me to compute sentiment metrics (like average tone) without error.

Actual Behavior:
The method raises a KeyError: 'timeline' because it seems to be expecting a key named "timeline" that is not present in the API response. Since this error occurs internally in the library’s timeline_search method, my debugging statements in my own code never get a chance to display the response structure.

Additional Context:

I have verified that I’m running gdeltdoc version 1.10.1.

I attempted to add debug prints inside my own script to inspect the DataFrame, but the error occurs before the DataFrame is returned.

As a temporary workaround, I monkey-patched timeline_search to catch the error and check for an alternative column name (e.g., "timelinevol"), but this is only a stopgap solution.

I’ve reviewed the [GDELT Doc API documentation](https://blog.gdeltproject.org/gdelt-doc-2-0-api-debuts/) and suspect that there might have been a change in the response format that gdeltdoc has not yet accounted for.

Request:
Could you please look into this issue? It would be great to have the library updated to either handle the new response format or at least provide a more informative error message when the expected key is missing. Any guidance on a workaround in the meantime would also be appreciated.

Thank you for your time and effort on this project!

Hints from issue comments:
@alex9smith: Thanks for the detailed report. I'll do some investigating and see what's going on. What makes you think the API response has changed? @alex9smith: I understand what's happening here now. The example filters you provided return no results, which seems plausible for any query where the timespan is `1h`. 

When I repeat the query with the same keyword but a longer timespan I get results as I expect.

The API response when there's no results is simply `{}` - not the `{"timeline": []}` that the library is expecting. I'm unsure if this is a new response format that GDELT have decided on or if this is a bug in the library that's been around for a long time. Either way it should be an easy enough fix. I'll aim to have an update out today or tomorrow.@alex9smith: Please update to 1.10.3 and let me know if that hasn't fixed it.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc6c47d..4529c1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 1.10.3
+
+Handle empty API responses in timeline search
+
 ## 1.10.2
 
 Fix `Unpack` type hint for older Python versions (#60)
diff --git a/gdeltdoc/_version.py b/gdeltdoc/_version.py
index 8c657ee..977620c 100644
--- a/gdeltdoc/_version.py
+++ b/gdeltdoc/_version.py
@@ -1 +1 @@
-version = "1.10.2"
+version = "1.10.3"
diff --git a/gdeltdoc/api_client.py b/gdeltdoc/api_client.py
index d5284d5..43c5310 100644
--- a/gdeltdoc/api_client.py
+++ b/gdeltdoc/api_client.py
@@ -107,7 +107,7 @@ class GdeltDoc:
         timeline = self._query(mode, filters.query_string)
 
         # If no results
-        if len(timeline["timeline"]) == 0:
+        if (timeline == {}) or (len(timeline["timeline"]) == 0):
             return pd.DataFrame()
 
         results = {


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 78266fb..c8748de 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1,3 +1,4 @@
+import unittest.mock
 import pandas as pd
 import unittest
 
@@ -101,6 +102,16 @@ class TimelineSearchTestCase(unittest.TestCase):
     def test_vol_raw_has_three_columns(self):
         self.assertEqual(self.all_results[1].shape[1], 3)
 
+    def test_handles_empty_API_response(self):
+        gd = GdeltDoc()
+        with unittest.mock.patch.object(gd, "_query") as query_mock:
+            query_mock.return_value = {}
+            result = gd.timeline_search(
+                "timelinetone", Filters(keyword="environment", timespan="1h")
+            )
+            self.assertTrue(type(result) == pd.DataFrame)
+            self.assertEqual(result.shape[0], 0)
+
 
 class QueryTestCase(unittest.TestCase):