aio-libs/aiohttp · error · RuntimeError

The zstd decompression is not available. Please install…

Error message

The zstd decompression is not available. Please install `backports.zstd` module

What it means

Raised by ZSTDDecompressor.__init__ when the zstd backend is missing. On Python < 3.14 aiohttp imports backports.zstd; on 3.14+ it uses the stdlib compression.zstd module. If neither is importable (HAS_ZSTD False) and a Content-Encoding: zstd response arrives, construction of the decompressor fails immediately. The message still names backports.zstd because that is the install path for all current CPython releases.

Solutions

  1. Install the backend: pip install backports.zstd (or run on Python 3.14+ for the stdlib module).
  2. Remove 'zstd' from any custom Accept-Encoding header you set on requests.
  3. If zstd is unavailable, configure the upstream to advertise gzip/identity only.
  4. Rebuild the deployment image so the optional dep is present in the locked environment.

Example fix

# shell
pip install backports.zstd
# or, in request code, stop forcing zstd
headers={'Accept-Encoding': 'gzip, identity'}
Defensive patterns

Strategy: fallback

Validate before calling

try:
    from backports.zstd import ZstdDecompressor  # noqa: F401
    HAS_ZSTD = True
except ImportError:
    try:
        from compression.zstd import ZstdDecompressor  # noqa: F401  # py3.14+
        HAS_ZSTD = True
    except ImportError:
        HAS_ZSTD = False

enc = 'gzip, identity' if not HAS_ZSTD else 'gzip, zstd'
await session.get(url, headers={'Accept-Encoding': enc})

Type guard

null

Try / catch

try:
    data = await resp.read()
except RuntimeError as exc:
    if 'zstd' in str(exc).lower():
        raise RuntimeError('install backports.zstd or drop zstd from Accept-Encoding') from exc
    raise

Prevention

When it happens

Trigger: Server returns Content-Encoding: zstd but the optional zstd backend is not installed; Accept-Encoding was forced to include 'zstd' via custom headers; aiohttp upgraded to a version that started advertising zstd without the dep being present in the deployment image.

Common situations: Alpine/slim Docker images without the wheel; pinned old aiohttp in a project whose deployment env never had backports.zstd; server-side negotiation that prefers zstd before gzip.

Related errors


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

Appendix: source

Thrown at aiohttp/compression_utils.py:485

        if hasattr(self._obj, "flush"):
            return cast(bytes, self._obj.flush())
        return b""

    @property
    def data_available(self) -> bool:
        return not self._obj.is_finished() and not self._last_empty


class ZSTDDecompressor(ConcatDecompressionHandler["ZstdDecompressor"]):
    _unlimited = ZSTD_MAX_LENGTH_UNLIMITED

    def __init__(
        self,
        executor: Executor | None = None,
        max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE,
    ) -> None:
        if not HAS_ZSTD:
            raise RuntimeError(
                "The zstd decompression is not available. "
                "Please install `backports.zstd` module"
            )
        super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)
        self._decompressor = self._new_decompressor()

    def _new_decompressor(self) -> "ZstdDecompressor":
        return ZstdDecompressor()

    def decompress_sync(
        self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
    ) -> bytes:
        # zstd uses -1 for unlimited, while zlib uses 0 for unlimited
        # Convert the zlib convention (0=unlimited) to zstd convention (-1=unlimited)
        zstd_max_length = (
            ZSTD_MAX_LENGTH_UNLIMITED
            if max_length == ZLIB_MAX_LENGTH_UNLIMITED
            else max_length

View on GitHub (pinned to d041d4d0fd)