urllib3/urllib3 · error · ValueError

Unable to determine whether fp is closed.

Error message

Unable to determine whether fp is closed.

What it means

Raised as ValueError from is_fp_closed() when the supplied file-like object has none of: isclosed() method, closed attribute, or fp attribute. urllib3 uses these three duck-typed signals (in that order) to decide if a response stream is exhausted; if none exists it cannot make a determination and refuses to guess.

Source

Thrown at src/urllib3/util/response.py:37

        # GH Issue #928
        return obj.isclosed()  # type: ignore[no-any-return, attr-defined]
    except AttributeError:
        pass

    try:
        # Check via the official file-like-object way.
        return obj.closed  # type: ignore[no-any-return, attr-defined]
    except AttributeError:
        pass

    try:
        # Check if the object is a container for another file-like object that
        # gets released on exhaustion (e.g. HTTPResponse).
        return obj.fp is None  # type: ignore[attr-defined]
    except AttributeError:
        pass

    raise ValueError("Unable to determine whether fp is closed.")


def assert_header_parsing(headers: httplib.HTTPMessage) -> None:
    """
    Asserts whether all headers have been successfully parsed.
    Extracts encountered errors from the result of parsing headers.

    Only works on Python 3.

    :param http.client.HTTPMessage headers: Headers to verify.

    :raises urllib3.exceptions.HeaderParsingError:
        If parsing errors are found.
    """

    # This will fail silently if we pass in the wrong kind of parameter.
    # To make debugging easier add an explicit check.
    if not isinstance(headers, httplib.HTTPMessage):

View on GitHub (pinned to c8d039c1b7)

Solutions

  1. Ensure custom file-like bodies inherit from io.IOBase (which provides .closed) or expose at least one of isclosed/closed/fp.
  2. Avoid calling urllib3.util.response.is_fp_closed() on objects that aren't response-like; use resp.closed on HTTPResponse directly.
  3. For mocks, set the closed attribute explicitly (mock.closed = True/False).

Example fix

// before
class MyBody:
    def read(self, n=-1): return b''
is_fp_closed(MyBody())  # raises ValueError

// after
import io
class MyBody(io.IOBase):
    def read(self, n=-1): return b''
# .closed is now provided by io.IOBase
Defensive patterns

Strategy: type-guard

Validate before calling

def fp_closed(fp):
    for attr in ('isclosed', 'closed'):
        if hasattr(fp, attr):
            return getattr(fp, attr)
    if hasattr(fp, 'fp'):
        return fp.fp is None
    raise ValueError('object is not a recognizable file-like response')

Type guard

def looks_like_response_fp(obj) -> bool:
    return any(hasattr(obj, a) for a in ('isclosed', 'closed', 'fp'))

Try / catch

try:
    closed = is_fp_closed(obj)
except ValueError:
    closed = True  # unknown shape; assume closed to stop iterating

Prevention

When it happens

Trigger: Passing an arbitrary object to is_fp_closed() that doesn't resemble a file-like/HTTPResponse; using a custom body wrapper that omits all three signals; contrib backends whose body object lacks the expected interface.

Common situations: Test doubles/mocks that implement read() but nothing else; third-party transports that wrap responses in plain objects; legacy Python 2 shims that lost the closed attribute.

Related errors


AI-assisted analysis of urllib3/urllib3@c8d039c1b7 (2026-08-04). Data as JSON: /data/errors/8c3f6a35f48fa754.json. Report an issue: GitHub.