aio-libs/aiohttp · error · ValueError
Prefix cannot be empty
Error message
Prefix cannot be empty
What it means
Raised by Application.add_subapp when the prefix, after rstrip('/'), is empty. A subapp must mount under a non-empty path so the router can distinguish its routes from the parent's; an empty prefix would make the subapp shadow the entire parent application. Both '' and '/' (which strips to '') are rejected.
Solutions
- Provide a non-empty, non-slash prefix, e.g. app.add_subapp('/api', subapp).
- Validate at config load: if not prefix.strip('/'): raise ValueError('prefix required').
- If you want the subapp at root, merge its routes into the parent application directly instead of using add_subapp.
Example fix
# before
app.add_subapp(mount_path, subapp) # mount_path == '/'
# after
mount = mount_path.rstrip('/') or '/api'
app.add_subapp(mount, subapp) Defensive patterns
Strategy: validation
Validate before calling
if not prefix.strip('/'):
raise ValueError('prefix must not be empty')
app.add_subapp(prefix, subapp) Type guard
def nonempty_prefix(p: str) -> bool:
return isinstance(p, str) and p.strip('/') != '' Try / catch
try:
app.add_subapp(prefix, subapp)
except ValueError as e:
if 'empty' in str(e):
app.add_subapp('/api', subapp)
else:
raise Prevention
- Validate prefix has non-slash content before mounting.
- Reject blank prefix values in configuration.
- Mount at root by merging routes, not via add_subapp.
When it happens
Trigger: Calling app.add_subapp('', subapp) or app.add_subapp('/', subapp); a prefix built from an empty config value; constructing the prefix by stripping a slash that was the only character.
Common situations: Config-driven mounting where the prefix field was left blank; attempting to mount a subapp at the root (use the parent's own routes instead); off-by-one in path joining that drops the prefix.
Related errors
- Prefix must be str
- Bad pattern
- Cannot add frozen application
- Cannot add sub application to frozen application
- Domain cannot be empty
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/db434ee0c26fe2b5.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_app.py:280
def reg_handler(signame: str) -> None:
subsig = getattr(subapp, signame)
async def handler(app: "Application") -> None:
await subsig.send(subapp)
appsig = getattr(self, signame)
appsig.append(handler)
reg_handler("on_startup")
reg_handler("on_shutdown")
reg_handler("on_cleanup")
def add_subapp(self, prefix: str, subapp: "Application") -> PrefixedSubAppResource:
if not isinstance(prefix, str):
raise TypeError("Prefix must be str")
prefix = prefix.rstrip("/")
if not prefix:
raise ValueError("Prefix cannot be empty")
factory = partial(PrefixedSubAppResource, prefix, subapp)
return self._add_subapp(factory, subapp)
def _add_subapp(
self, resource_factory: Callable[[], _Resource], subapp: "Application"
) -> _Resource:
if self.frozen:
raise RuntimeError("Cannot add sub application to frozen application")
if subapp.frozen:
raise RuntimeError("Cannot add frozen application")
resource = resource_factory()
self.router.register_resource(resource)
self._reg_subapp_signals(subapp)
self._subapps.append(subapp)
subapp.pre_freeze()
return resource
def add_domain(self, domain: str, subapp: "Application") -> MatchedSubAppResource:View on GitHub (pinned to d041d4d0fd)