aio-libs/aiohttp · error · ValueError
Bad pattern
Error message
Bad pattern '{pattern}': {exc} What it means
After assembling a regex pattern from the path template, aiohttp attempts re.compile(). If the user-supplied regex inside a {var:regex} variable is invalid Python regex, re.error is raised and aiohttp wraps it as ValueError with the offending pattern and the underlying error. This surfaces regex syntax errors in dynamic route definitions at registration time rather than at match time.
Solutions
- Test the inner regex in isolation with re.compile before putting it in the route: e.g. re.compile(r'\d+') should succeed.
- Fix the regex syntax (balance parentheses, use valid escapes).
- Simplify the regex or validate in the handler instead of the route template.
Example fix
// before
app.router.add_get('/users/{id:\d+(}', handler) # unbalanced group
// after
app.router.add_get('/users/{id:\d+}', handler) # valid regex Defensive patterns
Strategy: validation
Validate before calling
import re
def validate_route_regex(var_regex: str) -> None:
try:
re.compile(var_regex)
except re.error as e:
raise ValueError(f'Invalid route regex {var_regex!r}: {e}') from e Prevention
- Test custom regex with re.compile() in isolation before embedding it in a route template.
- Keep route regexes simple; prefer validating in the handler for complex rules.
When it happens
Trigger: Registering a route like app.router.add_get('/users/{id:\d+(}', handler) where the custom regex '\d+(' is malformed (unbalanced group). Any invalid regex construct — stray quantifiers, unbalanced parentheses, invalid escapes — inside a {name:regex} segment triggers this.
Common situations: Writing a custom regex for path validation with a syntax error; copy-pasting regex from a different regex flavor that Python rejects; overly clever regex that fails to compile; forgetting that the braces themselves are delimiters and the inner pattern must be valid on its own.
Related errors
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/7d1982d97ba51800.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_urldispatcher.py:435
continue
match = self.DYN_WITH_RE.fullmatch(part)
if match:
pattern += "(?P<{var}>{re})".format(**match.groupdict())
formatter += "{" + match.group("var") + "}"
continue
if "{" in part or "}" in part:
raise ValueError(f"Invalid path '{path}'['{part}']")
part = _requote_path(part)
formatter += part
pattern += re.escape(part)
try:
compiled = re.compile(pattern)
except re.error as exc:
raise ValueError(f"Bad pattern '{pattern}': {exc}") from None
assert compiled.pattern.startswith(PATH_SEP)
assert formatter.startswith("/")
self._pattern = compiled
self._formatter = formatter
@property
def canonical(self) -> str:
return self._formatter
def add_prefix(self, prefix: str) -> None:
assert prefix.startswith("/")
assert not prefix.endswith("/")
assert len(prefix) > 1
self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern)
self._formatter = prefix + self._formatter
def _match(self, path: str) -> dict[str, str] | None:
match = self._pattern.fullmatch(path)View on GitHub (pinned to d041d4d0fd)