getredash/redash · error · Exception
`rows` field should be of type `list`.
Error message
`rows` field should be of type `list`.
What it means
Raised by PythonQueryRunner.validate_result (redash/query_runner/python.py:327) when `result['rows']` is present but is not a Python list. The rows contract is strictly a list (usually of dicts keyed by column name), so tuples, generators, DataFrames, dicts, or JSON strings all fail this isinstance check.
Source
Thrown at redash/query_runner/python.py:327
def test_connection(self):
pass
def validate_result(self, result):
"""Validate the result after executing the query.
Parameters:
:result dict: The result dict.
"""
if not result:
raise Exception("local variable `result` should not be empty.")
if not isinstance(result, dict):
raise Exception("local variable `result` should be of type `dict`.")
if "rows" not in result:
raise Exception("Missing `rows` field in `result` dict.")
if "columns" not in result:
raise Exception("Missing `columns` field in `result` dict.")
if not isinstance(result["rows"], list):
raise Exception("`rows` field should be of type `list`.")
if not isinstance(result["columns"], list):
raise Exception("`columns` field should be of type `list`.")
def run_query(self, query, user):
self._current_user = user
try:
error = None
code = compile_restricted(query, "<string>", "exec")
builtins = safe_builtins.copy()
builtins["_write_"] = self.custom_write
builtins["__import__"] = self.custom_import
builtins["_getattr_"] = safer_getattr
builtins["getattr"] = safer_getattr
builtins["_setattr_"] = guarded_setattr
builtins["setattr"] = guarded_setattrView on GitHub (pinned to ca79fe988d)
Solutions
- Convert to a list of dicts: `result['rows'] = df.to_dict(orient='records')` or `[dict(r) for r in rows]`
- If rows are JSON strings, parse first: `json.loads(payload)`
- Ensure each element is a dict whose keys match the `columns` names
Example fix
# before
result = {'columns': cols, 'rows': df}
# after
result = {'columns': cols, 'rows': df.to_dict(orient='records')} Defensive patterns
Strategy: type-guard
Validate before calling
rows = result.get('rows')
if not isinstance(rows, list):
result['rows'] = list(rows) if hasattr(rows, '__iter__') and not isinstance(rows, dict) else [] Type guard
def rows_is_list(r: dict) -> bool:
return isinstance(r.get('rows'), list) Try / catch
try:
validate_result(result)
except Exception as e:
raise Exception('rows must be a list of dicts; got {}: {}'.format(type(result.get('rows')).__name__, e)) Prevention
- Never assign a DataFrame, tuple, or generator to result['rows']
- Convert once at the end: result['rows'] = list(map(dict, rows))
- JSON-serialize a sample of rows in dev to catch non-serializable values early
When it happens
Trigger: Assigning `result['rows'] = df` (a pandas DataFrame), `result['rows'] = tuple(...)`, a generator expression, a numpy array, or a JSON-encoded string instead of an actual list.
Common situations: Pandas users returning the DataFrame directly; users returning `df.to_json()` or `df.values`; converting via `df.itertuples()` which yields a generator.
Related errors
- `columns` field should be of type `list`.
- Missing `rows` field in `result` dict.
- Missing `columns` field in `result` dict.
- 'pagination.path' should be a string
- 'pagination.fields' should be a list of 2 field names
AI-assisted analysis of getredash/redash@ca79fe988d (2026-08-28).
Data as JSON: /api/errors/84203fb657739269.
Report an issue: GitHub.