aio-libs/aiohttp · error · ValueError

Value is not a valid etag. Maybe it contains '"'?

Error message

Value {value!r} is not a valid etag. Maybe it contains '"'?

What it means

Thrown by aiohttp.helpers.validate_etag_value when a string is not a legal ETag value per RFC 7232. Accepted forms are the wildcard '*' or any string matching the etag-char class [!\x23-\x7E\x80-\xff]+ (printable ASCII minus the double-quote 0x22, plus non-ASCII bytes). The value must NOT carry surrounding quotes or the 'W/' weak prefix; the caller supplies only the bare opaque tag.

Solutions

  1. Strip surrounding double-quotes and any 'W/' prefix before passing the value.
  2. Pass the bare opaque token only (e.g. 'abc123'), or '*' for the wildcard.
  3. If generating from a hash, restrict the alphabet to base64/hex and trim to etag-char range.

Example fix

# before
validate_etag_value('"abc123"')   # raises

# after
validate_etag_value('abc123')      # ok
Defensive patterns

Strategy: validation

Validate before calling

import re
_ETAGC_RE = re.compile(r'[!\x23-\x7E\x80-\xff]+')
def normalize_etag(v: str) -> str:
    # drop W/ prefix and surrounding quotes
    v = v.strip()
    if v.startswith('W/'):
        v = v[2:].lstrip()
    if len(v) >= 2 and v[0] == '"' and v[-1] == '"':
        v = v[1:-1]
    return v

def safe_etag(v: str) -> str | None:
    n = normalize_etag(v)
    if n == '*' or _ETAGC_RE.fullmatch(n):
        return n
    return None

Type guard

import re
_ETAGC_RE = re.compile(r'[!\x23-\x7E\x80-\xff]+')
def is_valid_etag_value(v: str) -> bool:
    return v == '*' or bool(_ETAGC_RE.fullmatch(v))

Try / catch

from aiohttp.helpers import validate_etag_value
try:
    validate_etag_value(candidate)
except ValueError as e:
    # log and use a fallback tag or skip the ETag header
    ...

Prevention

When it happens

Trigger: Calling validate_etag_value() directly, or setting StreamResponse.etag (web_response.py) with an ETag whose .value contains a '"', whitespace, or control char. The wildcard '*' alone is allowed.

Common situations: Passing a fully quoted ETag like '"abc123"' (with literal quotes) instead of the bare token; copying raw header values from another response and reusing them; hashes that accidentally include delimiters.

Related errors


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

Appendix: source

Thrown at aiohttp/helpers.py:1146

# https://tools.ietf.org/html/rfc7232#section-2.3
_ETAGC = r"[!\x23-\x7E\x80-\xff]+"
_ETAGC_RE = re.compile(_ETAGC)
_QUOTED_ETAG = rf'(W/)?"({_ETAGC})"'
QUOTED_ETAG_RE = re.compile(_QUOTED_ETAG)
LIST_QUOTED_ETAG_RE = re.compile(rf"({_QUOTED_ETAG})(?:\s*,\s*|$)|(.)")

ETAG_ANY = "*"


@frozen_dataclass_decorator
class ETag:
    value: str
    is_weak: bool = False


def validate_etag_value(value: str) -> None:
    if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value):
        raise ValueError(
            f"Value {value!r} is not a valid etag. Maybe it contains '\"'?"
        )


def parse_http_date(date_str: str | None) -> datetime.datetime | None:
    """Process a date string, return a datetime object"""
    if date_str is not None:
        timetuple = parsedate(date_str)
        if timetuple is not None:
            with suppress(ValueError):
                return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc)
    return None


@functools.lru_cache
def must_be_empty_body(method: str, code: int) -> bool:
    """Check if a request must return an empty body."""
    return (

View on GitHub (pinned to d041d4d0fd)