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
- Pass a mapping or an iterable of 2-tuples: {'key': 'val'} or [('key', 'val')] instead of a string/scalar
- If you have a query string, parse it first with urllib.parse.parse_qsl before handing it to requests
- 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
- Pass params/data as a dict, or as a list of exactly (key, value) pairs — never a flat list of strings
- Watch for accidental 3-tuples or bare strings when building pair lists programmatically
- Unit-test the pair-building code with representative inputs before hitting the network
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
- Files must be provided.
- Data must not be a string.
- Unsupported event specified, with event name "{event}"
- Invalid URL {url!r}: No scheme supplied. Perhaps you meant h
- URL has an invalid label.
AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31).
Data as JSON: /data/errors/d97bc89b2dd6e20e.json.
Report an issue: GitHub ↗.