{"record":{"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/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_app.py#L259-L295","documentation":"Raised by Application.add_subapp (and the same check is mirrored in add_domain) when the prefix argument is not a str. add_subapp requires a URL path prefix as a string because it builds a PrefixedSubAppResource that matches incoming paths against it. Passing None, bytes, or any non-string type is rejected immediately.","triggerScenarios":"Calling app.add_subapp(None, subapp); passing a prefix loaded from config that was not converted to str; passing a pathlib.Path or bytes object as the prefix.","commonSituations":"Conditionally adding a subapp where prefix may be unset (None); config-driven prefix loaded as bytes; refactoring that introduced an Optional[str] without handling None.","solutions":["Ensure prefix is a str before calling: app.add_subapp(str(prefix), subapp).","Guard against None: if prefix is not None: app.add_subapp(prefix, subapp).","Validate config values at load time so prefix is always a non-empty string."],"exampleFix":"# before\napp.add_subapp(config_prefix, subapp)  # config_prefix may be None\n\n# after\nif isinstance(config_prefix, str):\n    app.add_subapp(config_prefix, subapp)\nelse:\n    raise TypeError('subapp prefix must be a str from config')","handlingStrategy":"type-guard","validationCode":"if not isinstance(prefix, str):\n    raise TypeError('prefix must be str')\napp.add_subapp(prefix, subapp)","typeGuard":"def valid_prefix(p) -> bool:\n    return isinstance(p, str) and p.strip('/') != ''","tryCatchPattern":null,"preventionTips":["Coerce prefix to str and validate non-emptiness before add_subapp.","Reject None/Optional prefixes from config at load time.","Unit-test subapp mounting with config-derived prefixes."],"tags":["web","subapp","routing","type-error"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}