aio-libs/aiohttp · error · KeyError

KeyError: {key}

Error message

KeyError: {key}

What it means

ChainMapProxy.__getitem__ raises KeyError when the requested key (a string or AppKey) is not found in any of the underlying mappings (request, route, app storage layers). ChainMapProxy searches each mapping in order and raises a plain KeyError if none contains the key. This is the standard key-not-found error for aiohttp Application/request storage.

Source

Thrown at aiohttp/helpers.py:983

    def __init_subclass__(cls) -> None:
        raise TypeError(
            f"Inheritance class {cls.__name__} from ChainMapProxy is forbidden"
        )

    @overload  # type: ignore[override]
    def __getitem__(self, key: AppKey[_T]) -> _T: ...

    @overload
    def __getitem__(self, key: str) -> Any: ...

    def __getitem__(self, key: str | AppKey[_T]) -> Any:
        for mapping in self._maps:
            try:
                return mapping[key]
            except KeyError:
                pass
        raise KeyError(key)

    @overload  # type: ignore[override]
    def get(self, key: AppKey[_T], default: _S) -> _T | _S: ...

    @overload
    def get(self, key: AppKey[_T], default: None = ...) -> _T | None: ...

    @overload
    def get(self, key: str, default: Any = ...) -> Any: ...

    def get(self, key: str | AppKey[_T], default: Any = None) -> Any:
        try:
            return self[key]
        except KeyError:
            return default

    def __len__(self) -> int:
        # reuses stored hash values if possible

View on GitHub (pinned to d041d4d0fd)

Solutions

  1. Use .get(key, default) instead of __getitem__ to avoid the exception: value = request.app.get('key', default_value)
  2. Verify the key was set in the correct lifecycle phase (on_startup, middleware, etc.)
  3. Ensure you use the same key type for storage and retrieval (AppKey vs str)
  4. Register the key in on_startup or middleware before any handler accesses it

Example fix

# before
value = request.app['config']  # KeyError if 'config' not stored

# after
value = request.app.get('config')
if value is None:
    value = default_config
    request.app['config'] = value

# or with AppKey
from aiohttp.web import AppKey
config_key = AppKey('config', dict)
value = request.app.get(config_key, {})
Defensive patterns

Strategy: try-catch

Validate before calling

# Use .get() instead of [] for optional keys
value = request.app.get(key)
if value is None:
    logger.debug('Key %r not found in app storage', key)
    value = default_value

# For required keys, validate at startup
async def on_startup(app):
    for required_key in REQUIRED_KEYS:
        if required_key not in app:
            raise KeyError(f'Required key {required_key!r} missing from app storage')

Type guard

def key_exists(storage, key) -> bool:
    try:
        storage[key]
        return True
    except KeyError:
        return False

Try / catch

try:
    value = request.app[key]
except KeyError:
    log.warning('Key %r not in app storage, using default', key)
    value = default_value

Prevention

When it happens

Trigger: Accessing request['missing_key'] or app['missing_key'] where the key was never stored. Also triggered by using a string key when the data was stored under an AppKey, or vice versa. The key can be either a str or an AppKey[T] — they are not interchangeable lookups.

Common situations: Accessing a key set in a different middleware lifecycle phase that hasn't run yet; typo in the key name; mixing string keys with AppKey objects (storing with AppKey but retrieving with str); accessing cleanup context data before on_startup has run.

Related errors


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