alibaba/DataX · error · ValueError

headers for a list of dicts is not a dict or a keyword

Error message

headers for a list of dicts is not a dict or a keyword

What it means

ValueError from tabulate: rows were detected as a list of dicts, but 'headers' was given a truthy value that is neither a dict nor one of the keywords ('keys', 'firstrow'). This tabulate version only supports per-column header mapping via a dict when rows are dicts — a list of header strings is rejected.

Source

Thrown at otsstreamreader/tools/tabulate.py:747

                for k in row.keys():
                    #Save unique items in input order
                    if k not in uniq_keys:
                        keys.append(k)
                        uniq_keys.add(k)
            if headers == 'keys':
                headers = keys
            elif isinstance(headers, dict):
                # a dict of headers for a list of dicts
                headers = [headers.get(k, k) for k in keys]
                headers = list(map(_text_type, headers))
            elif headers == "firstrow":
                if len(rows) > 0:
                    headers = [firstdict.get(k, k) for k in keys]
                    headers = list(map(_text_type, headers))
                else:
                    headers = []
            elif headers:
                raise ValueError('headers for a list of dicts is not a dict or a keyword')
            rows = [[row.get(k) for k in keys] for row in rows]

        elif headers == "keys" and len(rows) > 0:
            # keys are column indices
            headers = list(map(_text_type, range(len(rows[0]))))

    # take headers from the first row if necessary
    if headers == "firstrow" and len(rows) > 0:
        if index is not None:
            headers = [index[0]] + list(rows[0])
            index = index[1:]
        else:
            headers = rows[0]
        headers = list(map(_text_type, headers)) # headers should be strings
        rows = rows[1:]

    headers = list(map(_text_type,headers))
    rows = list(map(list,rows))

View on GitHub (pinned to 80ec23d5c5)

Solutions

  1. Use headers='keys' to take column names from the dicts themselves.
  2. Pass headers as a dict mapping data-key -> display-name: headers={'ts': 'timestamp'}.
  3. Or convert rows to lists first: tabulate([[r[k] for k in keys] for r in rows], headers=key_list).

Example fix

# before
tabulate([{'ts': 1, 'v': 2}], headers=['timestamp', 'value'])
# after
tabulate([{'ts': 1, 'v': 2}], headers={'ts': 'timestamp', 'v': 'value'})
Defensive patterns

Strategy: type-guard

Validate before calling

if rows and isinstance(rows[0], dict) and not isinstance(headers, (dict, str)) and headers not in (None, False):
    raise ValueError('list-of-dicts rows require headers as dict or "keys"/"firstrow"')

Type guard

def headers_ok_for_dict_rows(headers):
    return headers in (None, False, 'keys', 'firstrow') or isinstance(headers, dict)

Prevention

When it happens

Trigger: Calling tabulate([{...}, {...}], headers=['A', 'B']) — list-of-dicts input with a list-type headers argument. Works fine with iterable-of-iterables rows, so the failure surprises users who switch row format without changing headers.

Common situations: Reusing a headers list from a list-of-lists call when the data becomes a list of dicts; dict rows produced by csv.DictReader and headers taken from reader.fieldnames.

Related errors


AI-assisted analysis of alibaba/DataX@80ec23d5c5 (2026-08-14). Data as JSON: /api/errors/ab88ed94ed8c0cb2. Report an issue: GitHub.