aio-libs/aiohttp · error · AttributeError

module {__name__} has no attribute {name}

Error message

module {__name__} has no attribute {name}

What it means

Raised by the module-level __getattr__ in aiohttp/__init__.py when code accesses an attribute that is not part of the public API (__all__) and is not one of the lazily-imported gunicorn workers (GunicornUVLoopWebWorker, GunicornWebWorker). It is aiohttp's PEP 562 lazy-import fallback for unknown names. It almost always means a typo, a removed/renamed symbol, or code written against a different aiohttp version.

Source

Thrown at aiohttp/__init__.py:258

def __dir__() -> tuple[str, ...]:
    return __all__ + ("__doc__",)


def __getattr__(name: str) -> object:
    global GunicornUVLoopWebWorker, GunicornWebWorker

    # Importing gunicorn takes a long time (>100ms), so only import if actually needed.
    if name in ("GunicornUVLoopWebWorker", "GunicornWebWorker"):
        try:
            from .worker import GunicornUVLoopWebWorker as guv, GunicornWebWorker as gw
        except ImportError:
            return None

        GunicornUVLoopWebWorker = guv  # type: ignore[misc]
        GunicornWebWorker = gw  # type: ignore[misc]
        return guv if name == "GunicornUVLoopWebWorker" else gw

    raise AttributeError(f"module {__name__} has no attribute {name}")

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Check aiohttp.__all__ (or dir(aiohttp)) for the exact public name you intended and fix the typo/rename.
  2. Import submodules directly from their real location, e.g. 'from aiohttp.http_websocket import WebSocketWriter' or 'from aiohttp._websocket.models import WSCloseCode', rather than reaching through the top-level package.
  3. If upgrading, consult the CHANGES/ folder and migration notes for renamed/removed symbols and update call sites.
  4. If you need the lazily-loaded gunicorn workers, ensure gunicorn is installed (the __getattr__ returns None on ImportError for those two names, so a different error appears).

Example fix

# before
import aiohttp
worker = aiohttp.GunicornSteamWorker  # typo -> AttributeError

# after
import aiohttp
worker = aiohttp.GunicornWebWorker  # correct lazily-loaded name
Defensive patterns

Strategy: validation

Validate before calling

import aiohttp
name = 'GunicornWebWorker'
if name not in aiohttp.__all__ and name not in {'GunicornUVLoopWebWorker', 'GunicornWebWorker'}:
    raise AttributeError(f'{name!r} is not a public aiohttp attribute')
obj = getattr(aiohttp, name)

Prevention

When it happens

Trigger: Any expression of the form aiohttp.<name> where <name> is not exported in aiohttp.__all__ and is not 'GunicornUVLoopWebWorker'/'GunicornWebWorker'. Common triggers: accessing a name that existed in aiohttp 3.x but was removed/moved in 4.x (e.g. relocated websocket helpers, renamed exceptions), or a typo like aiohttp.ClinetSession.

Common situations: Upgrading aiohttp across a major version (3.x -> 4.x) where symbols were reorganized; copy-pasting examples that import from the wrong path; relying on private helpers that live under aiohttp._websocket or aiohttp.http_websocket instead of the top-level package; static-analysis/IDE auto-complete picking a non-exported name.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/7c24bf80a055006a.json. Report an issue: GitHub.