aio-libs/aiohttp · error · ValueError
Duplicate , already handled by
Error message
Duplicate {name!r}, already handled by {self._named_resources[name]!r} What it means
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.
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.
Example fix
// before
app.router.add_get('/api/users', api_users, name='users')
app.router.add_get('/admin/users', admin_users, name='users') # Duplicate
// after
app.router.add_get('/api/users', api_users, name='api.users')
app.router.add_get('/admin/users', admin_users, name='admin.users') Defensive patterns
Strategy: validation
Validate before calling
def unique_name(app, base: str) -> str:
names = set(app.router.named_resources())
if base not in names:
return base
i = 2
while f'{base}.{i}' in names:
i += 1
return f'{base}.{i}'
app.router.add_get('/x', h, name=unique_name(app, 'users')) Type guard
def name_is_unique(app, name: str) -> bool:
return name not in app.router.named_resources() Try / catch
try:
app.router.add_get('/x', h, name=name)
except ValueError as e:
if 'Duplicate' in str(e):
# pick a unique name or skip registration
log.warning('duplicate route name %r — skipping', name)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Incorrect route name
- Incorrect route name
- Added route will never be executed, method
- Cannot register a resource into frozen router.
- Domain not valid
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/af805677600af67b.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_urldispatcher.py:1066
if name is not None:
parts = self.NAME_SPLIT_RE.split(name)
for part in parts:
if keyword.iskeyword(part):
raise ValueError(
f"Incorrect route name {name!r}, "
"python keywords cannot be used "
"for route name"
)
if not part.isidentifier():
raise ValueError(
f"Incorrect route name {name!r}, "
"the name should be a sequence of "
"python identifiers separated "
"by dash, dot or column"
)
if name in self._named_resources:
raise ValueError(
f"Duplicate {name!r}, "
f"already handled by {self._named_resources[name]!r}"
)
self._named_resources[name] = resource
self._resources.append(resource)
if isinstance(resource, MatchedSubAppResource):
# We cannot index match sub-app resources because they have match rules
self._matched_sub_app_resources.append(resource)
else:
self.index_resource(resource)
def _get_resource_index_key(self, resource: AbstractResource) -> str:
"""Return a key to index the resource in the resource index."""
if "{" in (index_key := resource.canonical):
# strip at the first { to allow for variables, and than
# rpartition at / to allow for variable parts in the path
# For example if the canonical path is `/core/locations{tail:.*}`View on GitHub (pinned to d041d4d0fd)