psf/requests · error · ValueError

cannot encode objects that are not 2-tuples

Error message

cannot encode objects that are not 2-tuples

What it means

utils.from_key_val_list converts a user-supplied value into an OrderedDict and explicitly rejects str, bytes, bool, and int inputs with this ValueError, because iterating those would not yield the (key, value) 2-tuples an OrderedDict requires (a string would explode into characters). It's a fail-fast guard on parameters that requests accepts as either a mapping or an iterable of pairs.

Source

Thrown at src/requests/utils.py:365

    ::

        >>> from_key_val_list([('key', 'val')])
        OrderedDict([('key', 'val')])
        >>> from_key_val_list('string')
        Traceback (most recent call last):
        ...
        ValueError: cannot encode objects that are not 2-tuples
        >>> from_key_val_list({'key': 'val'})
        OrderedDict([('key', 'val')])

    :rtype: OrderedDict
    """
    if value is None:
        return None

    if isinstance(value, (str, bytes, bool, int)):
        raise ValueError("cannot encode objects that are not 2-tuples")

    return OrderedDict(value)


@overload
def to_key_val_list(value: None) -> None: ...
@overload
def to_key_val_list(
    value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]],
) -> list[tuple[_KT, _VT]]: ...
def to_key_val_list(
    value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]] | None,
) -> list[tuple[_KT, _VT]] | None:
    """Take an object and test to see if it can be represented as a
    dictionary. If it can be, return a list of tuples, e.g.,

    ::

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Pass a mapping or an iterable of 2-tuples: {'key': 'val'} or [('key', 'val')] instead of a string/scalar
  2. If you have a query string, parse it first with urllib.parse.parse_qsl before handing it to requests
  3. Validate config-loaded values are dict-shaped before passing them into requests parameters

Example fix

# before
from_key_val_list('key=val')  # ValueError

# after
from urllib.parse import parse_qsl
from_key_val_list(parse_qsl('key=val'))  # OrderedDict([('key', 'val')])
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_params(data):
    if hasattr(data, "items"):
        return
    for item in data:
        if not (hasattr(item, "__len__") and len(item) == 2):
            raise ValueError(f"Each element must be a 2-tuple (key, value), got {item!r}")

Type guard

def is_two_tuple_sequence(data):
    return hasattr(data, "items") or all(
        isinstance(i, (tuple, list)) and len(i) == 2 for i in data
    )

Try / catch

try:
    resp = session.get(url, params=param_pairs)
except ValueError as e:
    if "2-tuples" in str(e):
        raise ConfigError(f"params must be a dict or list of (key, value) pairs: {param_pairs!r}") from e
    raise

Prevention

When it happens

Trigger: Passing a scalar or string where a dict/list-of-tuples is expected and requests routes it through from_key_val_list at src/requests/utils.py:364-365 — e.g. malformed header/param structures fed into internal conversion. Directly: from_key_val_list('key=val') or from_key_val_list(True).

Common situations: Passing a pre-encoded query string or 'k=v' string where a dict was expected; JSON config deserializing a field as a plain string instead of an object; passing a single ('k','v') tuple flattened incorrectly, or booleans/ints from templated config landing in dict-shaped parameters.

Related errors


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/d97bc89b2dd6e20e.json. Report an issue: GitHub ↗.