{"record":{"id":"774c527150d169ba","repo":"aio-libs/aiohttp","slug":"emptystreamreader-on-chunk-received-is-read-only","errorCode":null,"errorMessage":"EmptyStreamReader._on_chunk_received is read-only","messagePattern":"EmptyStreamReader\\._on_chunk_received is read-only","errorType":"validation","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"aiohttp/streams.py","lineNumber":617,"sourceCode":"\n    __slots__ = (\"_read_eof_chunk\",)\n\n    def __init__(self) -> None:\n        self._read_eof_chunk = False\n        self.total_bytes = 0\n\n    # Shadow the inherited slot with a property so the EMPTY_PAYLOAD singleton\n    # can't be polluted with a per-response hook that would leak across\n    # requests. EmptyStreamReader never delivers a chunk anyway.\n    @property\n    def _on_chunk_received(self) -> None:\n        return None\n\n    @_on_chunk_received.setter\n    def _on_chunk_received(\n        self, value: Callable[[bytes], Coroutine[None, None, None]] | None\n    ) -> None:\n        raise AttributeError(\"EmptyStreamReader._on_chunk_received is read-only\")\n\n    def __repr__(self) -> str:\n        return \"<%s>\" % self.__class__.__name__\n\n    def exception(self) -> BaseException | None:\n        return None\n\n    def set_exception(\n        self,\n        exc: type[BaseException] | BaseException,\n        exc_cause: BaseException = _EXC_SENTINEL,\n    ) -> None:\n        pass\n\n    def on_eof(self, callback: Callable[[], None]) -> None:\n        try:\n            callback()\n        except Exception:","sourceCodeStart":599,"sourceCodeEnd":635,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/streams.py#L599-L635","documentation":"Raised by the _on_chunk_received setter on EmptyStreamReader (the EMPTY_PAYLOAD singleton) when something tries to assign a chunk-received hook to it. Because the empty reader is a shared singleton that never delivers chunks, attaching a per-response hook would leak across requests, so the attribute is shadowed as a read-only property. This is a defensive guard, not normal control flow.","triggerScenarios":"Calling response.content._on_chunk_received = cb (or aiohttp assigning it internally) when response.content is the EMPTY_PAYLOAD singleton; instrumenting a stream that turned out to be empty (e.g. a 204 No Content or empty body).","commonSituations":"Observability/tracing middleware that unconditionally sets a chunk hook on every response body; libraries that wrap content for metrics; tests that set hooks on synthetic empty responses.","solutions":["Guard the assignment: if not isinstance(response.content, EmptyStreamReader): response.content._on_chunk_received = cb.","Set the hook via the public trace_config / response API rather than poking the private attribute.","Skip instrumentation for responses with no body (status 204, 304, or Content-Length 0)."],"exampleFix":"# before\nresponse.content._on_chunk_received = on_chunk\n\n# after\nfrom aiohttp.streams import EmptyStreamReader\nif not isinstance(response.content, EmptyStreamReader):\n    response.content._on_chunk_received = on_chunk","handlingStrategy":"type-guard","validationCode":"from aiohttp.streams import EmptyStreamReader\nif not isinstance(response.content, EmptyStreamReader):\n    response.content._on_chunk_received = on_chunk","typeGuard":"from aiohttp.streams import EmptyStreamReader\ndef is_empty_stream(stream) -> bool:\n    return isinstance(stream, EmptyStreamReader)","tryCatchPattern":"try:\n    response.content._on_chunk_received = on_chunk\nexcept AttributeError:\n    pass  # empty singleton; nothing to observe","preventionTips":["Guard chunk-hook assignment with an EmptyStreamReader isinstance check.","Use trace_config for instrumentation rather than private attributes.","Skip body instrumentation for 204/304/empty responses."],"tags":["streams","singleton","observability","api-misuse"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}