-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Description
Description
After upgrading Redash from 10.x to 25.8.0, Python data source queries that call execute_query on a GA4 data source started failing with:
Error running query: <class 'TypeError'> string indices must be integers
Previously, the same Python scripts worked without changes.
The root cause appears to be that execute_query("ga4-career", ...) now returns a JSON string instead of a dict with "columns" and "rows", which breaks existing code that expects res["rows"].
Environment
Redash version: 25.8.0 (Docker image redash/redash:25.8.0)
Data source: google_analytics4 (GA4)
Python data source (Redash built-in)
Example Python Query (simplified)
ga_query = """
{
"dimensions": [
{ "name": "date" },
{ "name": "customUser:user_type" }
],
"metrics": [
{ "name": "screenPageViews" }
],
"dateRanges": [
{
"startDate": "{{period.start}}",
"endDate": "{{period.end}}"
}
],
"dimensionFilter": {
"filter": {
"fieldName": "unifiedPagePathScreen",
"stringFilter": {
"matchType": "CONTAINS",
"value": "/salaries"
}
}
},
"orderBys": [
{
"desc": true,
"dimension": { "dimensionName": "date" }
}
]
}
"""
res = execute_query("ga4-career", ga_query)
for r in res["rows"]:
# some processing...
pass
This code used to work on Redash 10.x.
Expected Behavior (old behavior)
execute_query("ga4-career", ga_query) should return a Python object like:
{
"columns": [...],
"rows": [
{ "date": "...", "customUser:user_type": "...", "screenPageViews": "123" },
...
]
}
Then for r in res["rows"]: iterates over rows without errors.
Actual Behavior (25.8.0)
The same code now fails with:
Error running query: <class 'TypeError'> string indices must be integers
Inspection shows that res is now a string, containing JSON:
res == '{"columns": [...], "rows": [...]}'
Trying to access res["rows"] on a string triggers TypeError: string indices must be integers.
Workaround
To restore functionality, I had to wrap execute_query results in a custom normalizer, e.g.:
def normalize_result(res):
import json
# Handle (data, error) tuple
if isinstance(res, tuple) and len(res) == 2:
data, error = res
if error:
raise Exception(error)
res = data
# If GA4 returns JSON string
if isinstance(res, str):
res = json.loads(res)
# If format is {"query_result": {...}}
if isinstance(res, dict) and "query_result" in res:
res = res["query_result"]["data"]
return res
raw = execute_query("ga4-career", ga_query)
res = normalize_result(raw)
for r in res["rows"]:
...
This suggests that the contract of execute_query for GA4 changed (or became inconsistent), while existing Python scripts rely on the older behavior where it returned a dict with "columns" and "rows".
Why This Is a Problem
Existing Python data source queries that worked before the upgrade now fail without any changes to user code.
The behavior of execute_query appears to differ between data sources and between versions, which makes it hard to write robust Python queries that work across upgrades.
It would be very helpful if:
- execute_query for GA4 returned a consistent structured object (dict with "columns" and "rows", or a documented shape), or
- The change of return type was documented and/or wrapped so that Python data source sees a consistent format.