psf/requests · error · ValueError
Invalid timeout . Pass a (connect, read) timeout tuple, or…
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
Raised when the timeout argument is a tuple that does not unpack into exactly two values (connect, read). The code calls connect, read = timeout inside a try; a tuple of length != 2 raises ValueError, which is caught and re-raised with this descriptive message. Non-tuple timeouts (float, int, TimeoutSauce) bypass this branch entirely.
Solutions
- Pass a single float for equal connect/read timeouts: timeout=5.0.
- Pass exactly a 2-tuple: timeout=(connect, read).
- Validate dynamic timeouts before sending: assert len(t) == 2 when t is a tuple/list.
- Normalize config-driven values through a helper that always returns a float or a 2-tuple.
- Add a unit test that exercises the timeout builder with edge inputs.
Example fix
# before requests.get(url, timeout=(5,)) # -> ValueError: Invalid timeout # after requests.get(url, timeout=(5, 30)) # (connect, read)
Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_timeout(t):
"""Always return a float or a 2-tuple (connect, read)."""
if isinstance(t, (int, float)):
return float(t)
if isinstance(t, tuple):
if len(t) != 2:
raise ValueError(
f"timeout tuple must be (connect, read), got {t!r}"
)
return (float(t[0]), float(t[1]))
# assume it's already a urllib3 Timeout
return t
requests.get(url, timeout=normalize_timeout(config["timeout"])) Type guard
def is_valid_timeout(t) -> bool:
if isinstance(t, (int, float)):
return t >= 0
if isinstance(t, tuple):
return (
len(t) == 2
and all(isinstance(x, (int, float)) and x >= 0 for x in t)
)
from urllib3.util import Timeout
return isinstance(t, Timeout) Try / catch
try:
resp = session.get(url, timeout=timeout)
except ValueError as e:
if "Invalid timeout" in str(e):
# fall back to a safe default and retry once
resp = session.get(url, timeout=30.0)
else:
raise Prevention
- Build timeouts from typed config, not ad-hoc tuples. Document the (connect, read) convention anywhere timeouts are configured. Unit-test your timeout builder with 1-, 2-, and 3-element inputs. Prefer a single float unless connect and read genuinely differ.
When it happens
Trigger: Triggered by passing timeout=(5,) (one element), timeout=(1, 2, 3) (three elements), or any tuple/list whose length is not 2, to request/get/post/send or a Session. The error occurs before any network call, during timeout resolution in send.
Common situations: Happens when callers build timeouts dynamically and accidentally produce a 1- or 3-tuple, when copying a timeout from config that was meant to be a single float but got wrapped, or when using a list instead of a 2-tuple. Easy to introduce via f-string/config-driven timeout assembly.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Please check proxy URL. It is malformed and could be…
- Unsupported event specified, with event name
- Could not find a suitable TLS CA certificate bundle…
- Could not find the TLS certificate file, invalid path
- Could not find the TLS key file, invalid path
AI-assisted analysis of psf/requests@8068356288 (2026-08-11).
Data as JSON: /api/errors/64ab0e48615e4134.
Report an issue: GitHub.
Appendix: 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 8068356288)