{"id":"e48e64d33a003ae4","repo":"aio-libs/aiohttp","slug":"prefix-must-be-str","errorCode":null,"errorMessage":"Prefix must be str","messagePattern":"Prefix must be str","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_app.py","lineNumber":277,"sourceCode":"        return asyncio.get_running_loop().get_debug()\n\n    def _reg_subapp_signals(self, subapp: \"Application\") -> None:\n        def reg_handler(signame: str) -> None:\n            subsig = getattr(subapp, signame)\n\n            async def handler(app: \"Application\") -> None:\n                await subsig.send(subapp)\n\n            appsig = getattr(self, signame)\n            appsig.append(handler)\n\n        reg_handler(\"on_startup\")\n        reg_handler(\"on_shutdown\")\n        reg_handler(\"on_cleanup\")\n\n    def add_subapp(self, prefix: str, subapp: \"Application\") -> PrefixedSubAppResource:\n        if not isinstance(prefix, str):\n            raise TypeError(\"Prefix must be str\")\n        prefix = prefix.rstrip(\"/\")\n        if not prefix:\n            raise ValueError(\"Prefix cannot be empty\")\n        factory = partial(PrefixedSubAppResource, prefix, subapp)\n        return self._add_subapp(factory, subapp)\n\n    def _add_subapp(\n        self, resource_factory: Callable[[], _Resource], subapp: \"Application\"\n    ) -> _Resource:\n        if self.frozen:\n            raise RuntimeError(\"Cannot add sub application to frozen application\")\n        if subapp.frozen:\n            raise RuntimeError(\"Cannot add frozen application\")\n        resource = resource_factory()\n        self.router.register_resource(resource)\n        self._reg_subapp_signals(subapp)\n        self._subapps.append(subapp)\n        subapp.pre_freeze()","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_app.py#L259-L295","documentation":"Raised by Application.add_subapp when prefix is not a str instance. Subapps are mounted under a URL path prefix, and aiohttp both string-slices (rstrip('/')) and uses the prefix in URL matching, so a non-str type (Path, bytes, None) is rejected up front. Use add_subapp(str(prefix), subapp) if you have a pathlib.Path.","triggerScenarios":"Calling app.add_subapp(pathlib.Path('/admin'), subapp); passing bytes prefix b'/admin'; passing None or a custom path object.","commonSituations":"Building prefixes from pathlib.Path; reading prefix from config that yields a non-str type; refactoring that drops a str() conversion; templating that emits None on missing key.","solutions":["Convert to str: `app.add_subapp(str(prefix), subapp)`.","Keep prefixes as plain strings in config rather than Path objects.","Validate the type at config load time."],"exampleFix":"// before\napp.add_subapp(pathlib.Path('/admin'), admin_app)  # Path, not str\n// after\napp.add_subapp('/admin', admin_app)\n# or\napp.add_subapp(str(pathlib.Path('/admin')), admin_app)","handlingStrategy":"type-guard","validationCode":"def safe_add_subapp(app, prefix, subapp):\n    if not isinstance(prefix, str):\n        raise TypeError(f'prefix must be str, got {type(prefix).__name__}')\n    return app.add_subapp(prefix, subapp)","typeGuard":"from typing import Any, TypeGuard\n\ndef is_str_prefix(p: Any) -> TypeGuard[str]:\n    return isinstance(p, str)","tryCatchPattern":null,"preventionTips":["Keep prefixes as plain str in config.","Coerce pathlib.Path via str() at the boundary.","Validate prefix type at config load."],"tags":["web-app","subapp","typing","validation"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}