aio-libs/aiohttp · error · TypeError

Unsupported type for last_modified: {type(value).__name__}

Error message

Unsupported type for last_modified: {type(value).__name__}

What it means

The last_modified setter on StreamResponse only accepts int, float (unix timestamps), datetime.datetime, str, or None. Any other type falls through to a TypeError. A plain datetime.date (without time) is NOT accepted and is the most common culprit.

Source

Thrown at aiohttp/web_response.py:279

        self, value: int | float | datetime.datetime | str | None
    ) -> None:
        if value is None:
            self._headers.pop(hdrs.LAST_MODIFIED, None)
        elif isinstance(value, (int, float)):
            self._headers[hdrs.LAST_MODIFIED] = time.strftime(
                "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))
            )
        elif isinstance(value, datetime.datetime):
            if value.microsecond:
                value = value.replace(microsecond=0) + datetime.timedelta(seconds=1)
            self._headers[hdrs.LAST_MODIFIED] = time.strftime(
                "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()
            )
        elif isinstance(value, str):
            self._headers[hdrs.LAST_MODIFIED] = value
        else:
            msg = f"Unsupported type for last_modified: {type(value).__name__}"  # type: ignore[unreachable]
            raise TypeError(msg)

    @property
    def etag(self) -> ETag | None:
        quoted_value = self._headers.get(hdrs.ETAG)
        if not quoted_value:
            return None
        elif quoted_value == ETAG_ANY:
            return ETag(value=ETAG_ANY)
        match = QUOTED_ETAG_RE.fullmatch(quoted_value)
        if not match:
            return None
        is_weak, value = match.group(1, 2)
        return ETag(
            is_weak=bool(is_weak),
            value=value,
        )

    @etag.setter

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Convert date to datetime: resp.last_modified = datetime.datetime.combine(d, datetime.time.min).
  2. Pass a unix timestamp (int/float) or an HTTP-date string instead.
  3. If you already have a datetime.datetime, just assign it directly (microseconds are rounded up automatically).

Example fix

# before
resp.last_modified = record.updated_at.date()  # date, not datetime -> TypeError

# after
import datetime
resp.last_modified = datetime.datetime.combine(record.updated_at.date(), datetime.time.min())
Defensive patterns

Strategy: type-guard

Validate before calling

import datetime

def coerce_last_modified(value):
    if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime):
        return datetime.datetime.combine(value, datetime.time.min)
    return value

resp.last_modified = coerce_last_modified(value)

Type guard

import datetime

def is_supported_last_modified(value) -> bool:
    return value is None or isinstance(value, (int, float, datetime.datetime, str))

Prevention

When it happens

Trigger: Assigning `resp.last_modified = some_date` where some_date is a datetime.date (not datetime.datetime), a tuple, a pd.Timestamp without datetime inheritance, or any custom object. Intended types are listed in the isinstance checks at lines 265-276.

Common situations: Using a column from pandas/sql that yields date objects; passing a struct_time; forgetting to convert a date to datetime before assignment.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/f39cd71b4b15fbeb.json. Report an issue: GitHub.