{"record":{"id":"22b35b53d8e66f1c","repo":"aio-libs/aiohttp","slug":"incorrect-route-name-name-r-python-keywords-can","errorCode":null,"errorMessage":"Incorrect route name {name!r}, python keywords cannot be used for route name","messagePattern":"Incorrect route name (.+?), python keywords cannot be used for route name","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":1053,"sourceCode":"        return RoutesView(self._resources)\n\n    def named_resources(self) -> Mapping[str, AbstractResource]:\n        return MappingProxyType(self._named_resources)\n\n    def register_resource(self, resource: AbstractResource) -> None:\n        assert isinstance(\n            resource, AbstractResource\n        ), f\"Instance of AbstractResource class is required, got {resource!r}\"\n        if self.frozen:\n            raise RuntimeError(\"Cannot register a resource into frozen router.\")\n\n        name = resource.name\n\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)","sourceCodeStart":1035,"sourceCodeEnd":1071,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L1035-L1071","documentation":"register_resource() splits the route name on '.', ':', '-' (NAME_SPLIT_RE) and rejects the resource if any segment is a Python keyword (class, def, return, async, etc.). Route names are used by app.router.named_resources and by url_for(), and aiohttp wants each segment to be a valid Python identifier so generated code or attribute-style lookups stay legal. This runs after the frozen check but before the duplicate check.","triggerScenarios":"Calling app.router.add_get('class', ...) or add_route('GET', '/x', h, name='class'); using a multi-segment name like 'api.return' or 'auth.class' (each segment is checked); programmatically generating names from user input that happens to be a keyword.","commonSituations":"Auto-generating route names from resource nouns that collide with keywords ('class', 'import', 'global', 'lambda'); REST naming conventions that produce segments like 'import' or 'return'.","solutions":["Rename the route so no dot/dash/colon-separated segment is a Python keyword (e.g. 'klass', 'item-import').","When generating names from data, sanitize against keyword.iskeyword() before passing to add_route.","Avoid names that match the full keyword list (keyword.kwlist)."],"exampleFix":"// before\napp.router.add_get('/class/{id}', handler, name='class')\n// after\napp.router.add_get('/class/{id}', handler, name='klass')","handlingStrategy":"validation","validationCode":"import keyword\n\ndef safe_route_name(name: str) -> str:\n    for part in re.split(r'[.:-]', name):\n        if keyword.iskeyword(part):\n            raise ValueError(f'route name segment {part!r} is a python keyword')\n    return name\n\nname = safe_route_name(generated_name)\napp.router.add_get('/x', h, name=name)","typeGuard":"import keyword, re\n\ndef is_valid_route_name(name: str) -> bool:\n    return (\n        isinstance(name, str)\n        and all(\n            not keyword.iskeyword(p) and p.isidentifier()\n            for p in re.split(r'[.:-]', name)\n        )\n    )","tryCatchPattern":"try:\n    app.router.add_get('/x', h, name=name)\nexcept ValueError as e:\n    if 'keyword' in str(e):\n        name = name + '_route'  # disambiguate and retry\n        app.router.add_get('/x', h, name=name)\n    else:\n        raise","preventionTips":["Avoid Python keywords as name segments; keep a project-wide allow-list.","When generating names from data, filter out keyword.iskeyword matches.","Namespace names with a prefix/suffix to avoid collisions with keywords."],"tags":["url-dispatcher","route-naming","validation","identifiers"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}