aio-libs/aiohttp · error · TypeError

Unsupported type for last_modified

Error message

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

What it means

Raised by the last_modified setter when the assigned value is not None, int, float, datetime.datetime, or str. The setter formats the header into an RFC 7231 IMF-fixdate string and only knows how to convert those five input shapes. Any other type (e.g. a date object, a pandas Timestamp, a tuple) is rejected as a programming error rather than silently mis-formatted.

Solutions

  1. Convert date to datetime before assigning: `resp.last_modified = datetime.datetime.combine(my_date, datetime.time.min)`.
  2. Convert to a Unix timestamp (int/float): `resp.last_modified = my_date.timestamp()` or `calendar.timegm(my_date.timetuple())`.
  3. Format as an RFC 7231 string yourself and assign the str form.
  4. If the model field is a pandas.Timestamp, call `.to_pydatetime()` to get a stdlib datetime.

Example fix

// before
resp.last_modified = record.updated_at.date()  # date -> 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))

Try / catch

try:
    resp.last_modified = value
except TypeError:
    resp.last_modified = str(value)  # or convert to datetime/timestamp

Prevention

When it happens

Trigger: Assigning `resp.last_modified = datetime.date.today()` (date, not datetime), a `pandas.Timestamp`, a `tuple`, a `time.struct_time`, or any custom object to the last_modified property. Passing such a value through a helper that forwards to the setter.

Common situations: Using `datetime.date` (no time component) instead of `datetime.datetime`; using ORM/model fields declared as `DateField` that return date objects; feeding a numpy/pandas timestamp from analytics code into a caching middleware that sets Last-Modified.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/f39cd71b4b15fbeb. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)