encode/httpx · warning · RuntimeError

'.elapsed' may only be accessed after the response has been

Error message

'.elapsed' may only be accessed after the response has been read or closed.

What it means

RuntimeError raised by the Response.elapsed getter when _elapsed has not been set. httpx sets _elapsed inside the client's request machinery only after the response has been fully read or closed. Accessing response.elapsed on a Response you constructed yourself (Response(200, ...)) or before the request cycle completes therefore fails.

Source

Thrown at httpx/_models.py:585

            self.stream = stream

        self._num_bytes_downloaded = 0

    def _prepare(self, default_headers: dict[str, str]) -> None:
        for key, value in default_headers.items():
            # Ignore Transfer-Encoding if the Content-Length has been set explicitly.
            if key.lower() == "transfer-encoding" and "content-length" in self.headers:
                continue
            self.headers.setdefault(key, value)

    @property
    def elapsed(self) -> datetime.timedelta:
        """
        Returns the time taken for the complete request/response
        cycle to complete.
        """
        if not hasattr(self, "_elapsed"):
            raise RuntimeError(
                "'.elapsed' may only be accessed after the response "
                "has been read or closed."
            )
        return self._elapsed

    @elapsed.setter
    def elapsed(self, elapsed: datetime.timedelta) -> None:
        self._elapsed = elapsed

    @property
    def request(self) -> Request:
        """
        Returns the request instance associated to the current response.
        """
        if self._request is None:
            raise RuntimeError(
                "The request instance has not been set on this response."
            )

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Fully read or close the response first: `resp.read()` / `await resp.aread()` / `resp.close()`, then access resp.elapsed.
  2. For Responses you construct yourself (mocks/tests), set elapsed explicitly: resp.elapsed = datetime.timedelta(seconds=0.1).
  3. Guard access: `elapsed = getattr(resp, '_elapsed', None)`.
  4. When streaming, only read .elapsed after the iteration completes (after close() has run).

Example fix

// before
resp = httpx.Response(200, content=b'hi')
print(resp.elapsed)  # RuntimeError
// after
resp = httpx.Response(200, content=b'hi')
resp.elapsed = datetime.timedelta(seconds=0.05)
print(resp.elapsed)
# for real responses:
resp = client.get(url)
resp.read()  # ensures elapsed is set
print(resp.elapsed)
Defensive patterns

Strategy: validation

Validate before calling

# Ensure the response has been read/closed before timing access
if not hasattr(resp, '_elapsed'):
    resp.read()  # or resp.close(); for async use await resp.aread()
elapsed = resp.elapsed

Type guard

import datetime

def response_elapsed_ready(resp: 'httpx.Response') -> bool:
    return isinstance(getattr(resp, '_elapsed', None), datetime.timedelta)

Try / catch

try:
    elapsed = resp.elapsed
except RuntimeError:
    elapsed = None  # response not yet read/closed; or set a default

Prevention

When it happens

Trigger: Accessing resp.elapsed on a Response constructed via httpx.Response(...) directly; accessing .elapsed inside a stream iteration before the body has been fully read/closed; accessing .elapsed on a response obtained from a mock/replay transport that does not set elapsed; reading .elapsed before aread()/close() in async code.

Common situations: Unit tests building Response objects manually; mock transports that omit elapsed; inspecting resp.elapsed inside an `async for chunk in resp.aiter_bytes()` loop (elapsed set only after the loop ends and close() runs); partial reads where the user did not consume the body.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/d943e57a5f70c180.json. Report an issue: GitHub.