aio-libs/aiohttp · error · RuntimeError

Setting charset for application/octet-stream doesn't make…

Error message

Setting charset for application/octet-stream doesn't make sense, setup content_type first

What it means

Raised by Response.charset setter when the current content_type is the default 'application/octet-stream'. aiohttp refuses to attach a charset to a binary mime type because the Content-Type header would become semantically meaningless (e.g. 'application/octet-stream; charset=utf-8'). You must first set a text-oriented content_type so the charset is meaningful.

Solutions

  1. Set `resp.content_type = 'text/html'` (or another text/* or application/json type) BEFORE assigning `resp.charset`.
  2. Construct the response with the content_type up front: `Response(text=body, content_type='text/html; charset=utf-8')` or pass `text=` which auto-sets both content_type and charset.
  3. If you genuinely have binary data, do not set a charset at all — leave the default octet-stream.
  4. Avoid string-concatenating '; charset=...' into content_type manually; use the dedicated charset property after a valid text content_type.

Example fix

// before
resp = StreamResponse()
resp.charset = 'utf-8'  # RuntimeError

// after
resp = StreamResponse()
resp.content_type = 'text/plain'
resp.charset = 'utf-8'
Defensive patterns

Strategy: validation

Validate before calling

def set_charset(resp, charset):
    if resp.content_type in ('application/octet-stream', None):
        resp.content_type = 'text/plain'  # or another text type
    resp.charset = charset

Type guard

def is_text_content_type(ctype: str) -> bool:
    return ctype.startswith('text/') or ctype in (
        'application/json', 'application/javascript',
        'application/xml', 'application/xhtml+xml',
    )

Prevention

When it happens

Trigger: Calling `resp.charset = 'utf-8'` on a freshly constructed `Response()` (or StreamResponse) without having set `resp.content_type` to a text type first. Also triggered by `Response(content_type='application/octet-stream', charset='utf-8')` if the content_type setter leaves the default, or by mutating charset after resetting content_type back to the default.

Common situations: Building a generic Response and forgetting to pass `content_type='text/plain'` (or `text/html`, `application/json`) before assigning charset. Migrating code from explicitly-typed responses to a base StreamResponse and dropping the content_type line. The error is a deliberate guard because octet-stream bodies are bytes and charset is meaningless there.

Related errors


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

Appendix: source

Thrown at aiohttp/web_response.py:240

        # Just a placeholder for adding setter
        return super().content_type

    @content_type.setter
    def content_type(self, value: str) -> None:
        self.content_type  # read header values if needed
        self._content_type = str(value)
        self._generate_content_type_header()

    @property
    def charset(self) -> str | None:
        # Just a placeholder for adding setter
        return super().charset

    @charset.setter
    def charset(self, value: str | None) -> None:
        ctype = self.content_type  # read header values if needed
        if ctype == "application/octet-stream":
            raise RuntimeError(
                "Setting charset for application/octet-stream "
                "doesn't make sense, setup content_type first"
            )
        assert self._content_dict is not None
        if value is None:
            self._content_dict.pop("charset", None)
        else:
            self._content_dict["charset"] = str(value).lower()
        self._generate_content_type_header()

    @property
    def last_modified(self) -> datetime.datetime | None:
        """The value of Last-Modified HTTP header, or None.

        This header is represented as a `datetime` object.
        """
        return parse_http_date(self._headers.get(hdrs.LAST_MODIFIED))

View on GitHub (pinned to d041d4d0fd)