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

  1. Provide a non-empty, non-slash prefix, e.g. app.add_subapp('/api', subapp).
  2. Validate at config load: if not prefix.strip('/'): raise ValueError('prefix required').
  3. 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

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


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)