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

  1. Give each route a unique name; namespace with a prefix (e.g. 'admin.users', 'api.users').
  2. Before registering, check: if name in app.router.named_resources(): pick a different name or skip.
  3. 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

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


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)