aio-libs/aiohttp · error · TypeError

Domain must be str

Error message

Domain must be str

What it means

DomainValidator (used by DomainMatchRule / the Host matcher) requires its domain argument to be a str. The validation method checks isinstance(domain, str) and raises TypeError if not, because all subsequent processing (rstrip, lower, URL parsing, regex matching) assumes string operations.

Solutions

  1. Pass the domain as a str, e.g. 'example.com' or 'example.com:8080'.
  2. If you have a URL object, extract url.raw_host (and url.port if non-default) and pass the host string.
  3. Decode bytes to str before passing.

Example fix

// before
rule = Domain(b'example.com')   # bytes
// after
rule = Domain('example.com')    # str
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_domain(domain):
    if not isinstance(domain, str):
        raise TypeError(f'Domain must be str, got {type(domain).__name__}')
    return domain

Type guard

def is_domain_str(domain) -> bool:
    return isinstance(domain, str)

Prevention

When it happens

Trigger: Constructing a domain-based matcher with a non-string: e.g. passing bytes (b'example.com'), an integer (port number), None, or a yarl.URL object instead of the host string. This typically happens when building a host-based routing rule (app.router.add_resource with host constraints) or the Domain middleware.

Common situations: Passing a URL object where the host string is expected; passing bytes from a low-level parser; configuration loaded from a binary source; off-by-one extraction that grabs the wrong field.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/47cb43fc3ac6fe16. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_urldispatcher.py:781

    @abc.abstractmethod  # pragma: no branch
    def canonical(self) -> str:
        """Return a str"""


class Domain(AbstractRuleMatching):
    re_part = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)")

    def __init__(self, domain: str) -> None:
        super().__init__()
        self._domain = self.validation(domain)

    @property
    def canonical(self) -> str:
        return self._domain

    def validation(self, domain: str) -> str:
        if not isinstance(domain, str):
            raise TypeError("Domain must be str")
        domain = domain.rstrip(".").lower()
        if not domain:
            raise ValueError("Domain cannot be empty")
        elif "://" in domain:
            raise ValueError("Scheme not supported")
        url = URL("http://" + domain)
        assert url.raw_host is not None
        if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(".")):
            raise ValueError("Domain not valid")
        if url.port == 80:
            return url.raw_host
        return f"{url.raw_host}:{url.port}"

    async def match(self, request: Request) -> bool:
        host = request.headers.get(hdrs.HOST)
        if not host:
            return False
        return self.match_domain(host)

View on GitHub (pinned to d041d4d0fd)