psf/requests · error · ValueError

Invalid timeout {timeout}. Pass a (connect, read) timeout tu

Error message

Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, or a single float to set both timeouts to the same value.

What it means

In HTTPAdapter.send, a tuple timeout must unpack into exactly two values, (connect, read), which are wrapped into urllib3's Timeout (TimeoutSauce). If tuple unpacking raises ValueError — wrong number of elements — requests re-raises with this explicit message. Non-tuple values are treated as a single timeout applied to both connect and read, so only malformed tuples hit this branch.

Source

Thrown at src/requests/adapters.py:686

        self.cert_verify(conn, request.url, verify, cert)
        url = self.request_url(request, proxies)
        self.add_headers(
            request,
            stream=stream,
            timeout=timeout,
            verify=verify,
            cert=cert,
            proxies=proxies,
        )

        chunked = not (request.body is None or "Content-Length" in request.headers)

        if isinstance(timeout, tuple):
            try:
                connect, read = timeout
                resolved_timeout = TimeoutSauce(connect=connect, read=read)
            except ValueError:
                raise ValueError(
                    f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
                    f"or a single float to set both timeouts to the same value."
                )
        elif isinstance(timeout, TimeoutSauce):
            resolved_timeout = timeout
        else:
            resolved_timeout = TimeoutSauce(connect=timeout, read=timeout)

        try:
            resp = conn.urlopen(
                method=request.method,
                url=url,
                body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]
                headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072
                redirect=False,
                assert_same_host=False,
                preload_content=False,
                decode_content=False,

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Pass exactly two elements: timeout=(connect_timeout, read_timeout), e.g. timeout=(3.05, 27)
  2. For one value applied to both, pass a plain float — timeout=30, not timeout=(30,)
  3. For total-deadline semantics, construct urllib3.util.Timeout(connect=..., read=..., total=...) and pass that as timeout

Example fix

# before
requests.get(url, timeout=(3, 10, 30))  # 3-tuple -> ValueError

# after
requests.get(url, timeout=(3, 10))
# or a total deadline:
from urllib3.util import Timeout
requests.get(url, timeout=Timeout(connect=3, read=10, total=30))
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_timeout(timeout):
    if timeout is None or isinstance(timeout, (int, float)):
        return timeout
    if isinstance(timeout, tuple) and len(timeout) == 2 and all(
        t is None or isinstance(t, (int, float)) for t in timeout
    ):
        return timeout
    raise ValueError(f"timeout must be float or (connect, read) tuple, got {timeout!r}")

Type guard

def is_valid_timeout(value):
    if value is None or isinstance(value, (int, float)):
        return True
    return (isinstance(value, tuple) and len(value) == 2
            and all(t is None or isinstance(t, (int, float)) for t in value))

Try / catch

try:
    resp = session.get(url, timeout=timeout)
except ValueError as e:
    if "Invalid timeout" in str(e):
        raise ConfigError(f"Bad timeout value: {timeout!r}") from e
    raise

Prevention

When it happens

Trigger: requests.get(url, timeout=(3,)) or timeout=(3, 5, 10) — any tuple whose length isn't 2. Raised at src/requests/adapters.py:681-689. Note timeout=(3, 5) is valid, and a 3-tuple mimicking other libraries' (connect, read, total) form fails here.

Common situations: Porting code from libraries that accept a total/3-element timeout; a trailing comma accidentally turning a number into a 1-tuple (timeout=(30,)); config systems delivering timeout lists/tuples of the wrong arity; confusing urllib3's Timeout(total=...) capabilities with requests' 2-tuple contract.

Related errors


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