aio-libs/aiohttp · error · TypeError

value argument must be byte-ish, not

Error message

value argument must be byte-ish, not {type(value)!r}

What it means

Raised by BytesPayload.__init__ when the value argument is not bytes, bytearray, or memoryview. BytesPayload is the factory-selected Payload class for raw binary data, so any non-byte-ish object (most commonly a str) is rejected at construction rather than failing later during serialization. aiohttp will not implicitly encode text for you here.

Solutions

  1. Encode the string to bytes before passing: payload = BytesPayload(text.encode('utf-8')).
  2. For JSON, use session.post(url, json=obj) or JsonPayload(obj) instead of a raw bytes payload.
  3. For text payloads, use TextPayload or aiohttp's TextIOPayload which handle encoding via content-type charset.

Example fix

# before
payload = BytesPayload('{"k": 1}')

# after
from aiohttp.payload import JsonPayload
payload = JsonPayload({'k': 1})
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(value, str):
    value = value.encode('utf-8')
payload = BytesPayload(value)

Type guard

def is_byteish(v) -> bool:
    return isinstance(v, (bytes, bytearray, memoryview))

Try / catch

try:
    payload = BytesPayload(value)
except TypeError:
    payload = BytesPayload(str(value).encode('utf-8'))

Prevention

When it happens

Trigger: Passing a str to a request body that resolves to BytesPayload, e.g. session.post(url, data='plain text') in some code paths, or constructing BytesPayload('hello') directly; passing an int, list, or None as the value.

Common situations: Sending JSON or form text without encoding; assuming aiohttp auto-encodes str the same way the high-level client does for the data= argument (the low-level BytesPayload does not); feeding a value returned by json.dumps without encode().

Related errors


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

Appendix: source

Thrown at aiohttp/payload.py:358

class BytesPayload(Payload):
    _value: bytes
    # _consumed = False (inherited) - Bytes are immutable and can be reused
    _autoclose = True  # No file handle, just bytes in memory

    def __init__(
        self, value: bytes | bytearray | memoryview, *args: Any, **kwargs: Any
    ) -> None:
        if "content_type" not in kwargs:
            kwargs["content_type"] = "application/octet-stream"

        super().__init__(value, *args, **kwargs)

        if isinstance(value, memoryview):
            self._size = value.nbytes
        elif isinstance(value, (bytes, bytearray)):
            self._size = len(value)
        else:
            raise TypeError(f"value argument must be byte-ish, not {type(value)!r}")

        if self._size > TOO_LARGE_BYTES_BODY:
            warnings.warn(
                "Sending a large body directly with raw bytes might"
                " lock the event loop. You should probably pass an "
                "io.BytesIO object instead",
                ResourceWarning,
                source=self,
            )

    def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
        return self._value.decode(encoding, errors)

    async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
        """
        Return bytes representation of the value.

        This method returns the raw bytes content of the payload.

View on GitHub (pinned to d041d4d0fd)