aio-libs/aiohttp · error · TypeError
Domain must be str
Error message
Domain must be str
What it means
Application.add_domain(domain, subapp) requires its first argument to be a Python str. It performs an isinstance check up front because the subsequent MaskDomain/Domain rule construction and wildcard ('*') detection depend on str semantics. Any non-str value (bytes, int, yarl.URL, None) raises TypeError before any routing work is done.
Solutions
- Pass a plain str for domain, e.g. app.add_domain('api.example.com', subapp).
- If the value originates from config/env/URL parsing, coerce it first: app.add_domain(str(domain), subapp).
- For wildcard subdomains pass a string containing '*', e.g. '*.example.com'.
Example fix
// before app.add_domain(cfg['HOST'], subapp) # cfg['HOST'] is bytes // after app.add_domain(str(cfg['HOST']), subapp)
Defensive patterns
Strategy: type-guard
Validate before calling
if not isinstance(domain, str):
domain = str(domain)
app.add_domain(domain, subapp) Type guard
def is_valid_domain(d: object) -> TypeGuard[str]:
return isinstance(d, str) Prevention
- Always pass a literal str to add_domain.
- Coerce values from config/env/URL parsing with str() before the call.
- Use mypy/pyright to type the domain parameter as str at the call site.
When it happens
Trigger: Calling app.add_domain(123, subapp), app.add_domain(b'example.com', subapp), app.add_domain(url.host, subapp) where url.host is bytes, or passing None read from config.
Common situations: Reading a host from environment/config as bytes; passing a yarl.URL object or its .raw_host (which is bytes); dynamically building domains from parsed URLs without str() coercion.
Related errors
- Prefix must be str
- Domain cannot be empty
- Domain must be str
- Prefix cannot be empty
- Added route will never be executed, method
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/bd1d5e4d0e8de741.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_app.py:300
return self._add_subapp(factory, subapp)
def _add_subapp(
self, resource_factory: Callable[[], _Resource], subapp: "Application"
) -> _Resource:
if self.frozen:
raise RuntimeError("Cannot add sub application to frozen application")
if subapp.frozen:
raise RuntimeError("Cannot add frozen application")
resource = resource_factory()
self.router.register_resource(resource)
self._reg_subapp_signals(subapp)
self._subapps.append(subapp)
subapp.pre_freeze()
return resource
def add_domain(self, domain: str, subapp: "Application") -> MatchedSubAppResource:
if not isinstance(domain, str):
raise TypeError("Domain must be str")
elif "*" in domain:
rule: Domain = MaskDomain(domain)
else:
rule = Domain(domain)
factory = partial(MatchedSubAppResource, rule, subapp)
return self._add_subapp(factory, subapp)
def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:
return self.router.add_routes(routes)
@property
def on_response_prepare(self) -> _RespPrepareSignal:
return self._on_response_prepare
@property
def on_startup(self) -> _AppSignal:
return self._on_startup
View on GitHub (pinned to d041d4d0fd)