{"record":{"id":"af805677600af67b","repo":"aio-libs/aiohttp","slug":"duplicate-name-r-already-handled-by-self-name","errorCode":null,"errorMessage":"Duplicate {name!r}, already handled by {self._named_resources[name]!r}","messagePattern":"Duplicate (.+?), already handled by (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":1066,"sourceCode":"\n        if name is not None:\n            parts = self.NAME_SPLIT_RE.split(name)\n            for part in parts:\n                if keyword.iskeyword(part):\n                    raise ValueError(\n                        f\"Incorrect route name {name!r}, \"\n                        \"python keywords cannot be used \"\n                        \"for route name\"\n                    )\n                if not part.isidentifier():\n                    raise ValueError(\n                        f\"Incorrect route name {name!r}, \"\n                        \"the name should be a sequence of \"\n                        \"python identifiers separated \"\n                        \"by dash, dot or column\"\n                    )\n            if name in self._named_resources:\n                raise ValueError(\n                    f\"Duplicate {name!r}, \"\n                    f\"already handled by {self._named_resources[name]!r}\"\n                )\n            self._named_resources[name] = resource\n        self._resources.append(resource)\n\n        if isinstance(resource, MatchedSubAppResource):\n            # We cannot index match sub-app resources because they have match rules\n            self._matched_sub_app_resources.append(resource)\n        else:\n            self.index_resource(resource)\n\n    def _get_resource_index_key(self, resource: AbstractResource) -> str:\n        \"\"\"Return a key to index the resource in the resource index.\"\"\"\n        if \"{\" in (index_key := resource.canonical):\n            # strip at the first { to allow for variables, and than\n            # rpartition at / to allow for variable parts in the path\n            # For example if the canonical path is `/core/locations{tail:.*}`","sourceCodeStart":1048,"sourceCodeEnd":1084,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L1048-L1084","documentation":"register_resource() refuses to register two resources with the same name. The named-resources map (_named_resources) is keyed by name and used by url_for(name, ...) for reverse URL generation, so a collision would make url_for ambiguous. The error message names the offending name and the existing resource. This check runs after the keyword and identifier checks.","triggerScenarios":"Calling app.router.add_get('/a', h1, name='users') and later app.router.add_get('/b', h2, name='users'); looping over handlers and assigning the same constant name to each; refactoring that accidentally duplicates a name across two blueprints/sub-apps merged into one router.","commonSituations":"Copy-pasting a route definition and forgetting to change the name; merging multiple sub-apps whose route names were not namespaced; CMS/plugin systems registering the same logical name twice.","solutions":["Give each route a unique name; namespace with a prefix (e.g. 'admin.users', 'api.users').","Before registering, check: if name in app.router.named_resources(): pick a different name or skip.","Use add_resource()/add_route() with no name when reverse-URL lookup is not needed."],"exampleFix":"// before\napp.router.add_get('/api/users', api_users, name='users')\napp.router.add_get('/admin/users', admin_users, name='users')  # Duplicate\n// after\napp.router.add_get('/api/users', api_users, name='api.users')\napp.router.add_get('/admin/users', admin_users, name='admin.users')","handlingStrategy":"validation","validationCode":"def unique_name(app, base: str) -> str:\n    names = set(app.router.named_resources())\n    if base not in names:\n        return base\n    i = 2\n    while f'{base}.{i}' in names:\n        i += 1\n    return f'{base}.{i}'\n\napp.router.add_get('/x', h, name=unique_name(app, 'users'))","typeGuard":"def name_is_unique(app, name: str) -> bool:\n    return name not in app.router.named_resources()","tryCatchPattern":"try:\n    app.router.add_get('/x', h, name=name)\nexcept ValueError as e:\n    if 'Duplicate' in str(e):\n        # pick a unique name or skip registration\n        log.warning('duplicate route name %r — skipping', name)\n    else:\n        raise","preventionTips":["Namespace route names by module/feature (e.g. 'users.list', 'admin.users.list').","Check app.router.named_resources() membership before registering.","Omit the name entirely if you don't need url_for reverse lookup."],"tags":["url-dispatcher","route-naming","duplicate","url-building"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}