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 possibleView on GitHub (pinned to d041d4d0fd)
Solutions
- Use .get(key, default) instead of __getitem__ to avoid the exception: value = request.app.get('key', default_value)
- Verify the key was set in the correct lifecycle phase (on_startup, middleware, etc.)
- Ensure you use the same key type for storage and retrieval (AppKey vs str)
- 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
- Prefer .get(key, default) over __getitem__ for optional storage values
- Validate required keys in on_startup hooks
- Use AppKey consistently — do not mix str and AppKey for the same logical key
- Document all keys your middleware sets and handlers expect
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
- Inheritance class {cls.__name__} from ChainMapProxy is forbi
- Method cannot contain non-token characters {method!r} (found
- InvalidURL: {url}
- Invalid Content-Length header: {content_length_hdr!r}
- compress can not be set if Content-Encoding header is set
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/094df830b42ce5f3.
Report an issue: GitHub.