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
- Strip surrounding double-quotes and any 'W/' prefix before passing the value.
- Pass the bare opaque token only (e.g. 'abc123'), or '*' for the wildcard.
- 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
- Never pass raw header values that may include quotes; normalize first.
- Treat ETag as an opaque token, not a quoted-string.
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
- Duplicate ' ' header found.
- Invalid HTTP header
- Transfer-Encoding can't be present with Content-Length
- bad content disposition parameter
- bad content disposition type
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)