getredash/redash · error · Exception
`columns` field should be of type `list`.
Error message
`columns` field should be of type `list`.
What it means
Raised by PythonQueryRunner.validate_result (redash/query_runner/python.py:329) when `result['columns']` exists but is not a list. Columns must be a Python list of column definition objects (dicts with at least `name`), so strings, tuples, pandas Index objects, or generators fail the isinstance check.
Source
Thrown at redash/query_runner/python.py:329
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_setattr
builtins["_getitem_"] = self.custom_get_item
builtins["_getiter_"] = self.custom_get_iterView on GitHub (pinned to ca79fe988d)
Solutions
- Convert pandas Index to a list of dicts: `result['columns'] = [{'name': c, 'friendly_name': c, 'type': str(df[c].dtype)} for c in df.columns]`
- If columns arrive as JSON, parse with `json.loads` before assigning
- Ensure it is a plain Python list, not a tuple or string
Example fix
# before
result = {'columns': df.columns, 'rows': df.to_dict('records')}
# after
result = {'columns': [{'name': c, 'friendly_name': c} for c in df.columns], 'rows': df.to_dict('records')} Defensive patterns
Strategy: type-guard
Validate before calling
cols = result.get('columns')
if not isinstance(cols, list):
result['columns'] = [{'name': c} if isinstance(c, str) else c for c in cols] Type guard
def columns_is_list(r: dict) -> bool:
return isinstance(r.get('columns'), list) Try / catch
try:
validate_result(result)
except Exception as e:
raise Exception('columns must be a list of dicts; got {}: {}'.format(type(result.get('columns')).__name__, e)) Prevention
- Use list(df.columns) conversions for pandas, never df.columns directly
- Build columns with a helper returning [{'name': n, 'friendly_name': n} for n in names]
- Validate result shape in a shared test helper
When it happens
Trigger: Assigning `result['columns'] = df.columns` (a pandas Index), a comma-separated string like `"id,name"`, a tuple, or a dict of column types.
Common situations: Pandas users passing `df.columns` directly instead of converting it; users building a string of headers; JSON-encoded column definitions that were never parsed.
Related errors
- `rows` 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/ec450c24d2fbb440.
Report an issue: GitHub.