Skip to content

Python data source: execute_query with GA4 returns JSON string (not dict), breaking existing scripts with string indices must be integers #7588

@netgineer

Description

@netgineer

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions