tiangolo/fastapi · error · ValueError
Cannot set both 'data' and 'raw_data' on the same ServerSent
Error message
Cannot set both 'data' and 'raw_data' on the same ServerSentEvent. Use 'data' for JSON-serialized payloads or 'raw_data' for pre-formatted strings.
What it means
`ServerSentEvent` has a pydantic `model_validator(mode="after")`, `_check_data_exclusive` (sse.py:148-156), that raises `ValueError` if both `data` and `raw_data` are set (both not None). `data` is JSON-serialized; `raw_data` is sent verbatim. They are mutually exclusive because the encoder must pick one serialization strategy.
Source
Thrown at fastapi/sse.py:151
),
] = None
comment: Annotated[
str | None,
Doc(
"""
Optional comment line(s).
Comment lines start with `:` in the SSE wire format and are ignored by
`EventSource` clients. Useful for keep-alive pings to prevent
proxy/load-balancer timeouts.
"""
),
] = None
@model_validator(mode="after")
def _check_data_exclusive(self) -> "ServerSentEvent":
if self.data is not None and self.raw_data is not None:
raise ValueError(
"Cannot set both 'data' and 'raw_data' on the same "
"ServerSentEvent. Use 'data' for JSON-serialized payloads "
"or 'raw_data' for pre-formatted strings."
)
return self
def _split_sse_lines(value: str) -> list[str]:
# Split on SSE-spec line terminators only (\n, \r\n, \r), preserving
# trailing empty strings.
return value.replace("\r\n", "\n").replace("\r", "\n").split("\n")
def format_sse_event(
*,
data_str: Annotated[
str | None,
Doc(View on GitHub (pinned to 42a41db11f)
Solutions
- Set exactly one of `data` or `raw_data` per event.
- When building events programmatically, pop the unused key: `evt = {"raw_data": text}; evt.pop("data", None)`.
- Use `data` for JSON payloads (dicts, models, even strings get quoted) and `raw_data` only for pre-formatted non-JSON text.
Example fix
# before
yield ServerSentEvent(data={"x": 1}, raw_data="custom")
# after
yield ServerSentEvent(raw_data="custom")
# or, for JSON:
yield ServerSentEvent(data={"x": 1}) Defensive patterns
Strategy: type-guard
Validate before calling
from typing import Any
from fastapi.sse import ServerSentEvent
def make_event(*, data: Any = None, raw_data: str | None = None, **kw):
if data is not None and raw_data is not None:
raise ValueError("set only one of data or raw_data")
return ServerSentEvent(data=data, raw_data=raw_data, **kw) Type guard
def is_exclusive_payload(data, raw_data) -> bool:
return (data is None) or (raw_data is None) Try / catch
from fastapi.sse import ServerSentEvent
try:
yield ServerSentEvent(data=d, raw_data=rd, event="e")
except ValueError as exc:
# pick a deterministic fallback strategy
payload = rd if rd is not None else d
if isinstance(payload, str):
yield ServerSentEvent(raw_data=payload, event="e")
else:
yield ServerSentEvent(data=payload, event="e") Prevention
- Build SSE events through a small factory that enforces exclusivity.
- When spreading defaults, pop the unused key explicitly.
- Use `raw_data` only for pre-formatted non-JSON text; otherwise prefer `data`.
When it happens
Trigger: Constructing `ServerSentEvent(data={"x": 1}, raw_data="custom")`, or `ServerSentEvent(data="txt", raw_data="other")`. Often happens when forwarding an existing dict into `data` while a default `raw_data` is also supplied, or vice versa.
Common situations: Branching code that sometimes sets `data` and sometimes `raw_data` but doesn't clear the other; spreading defaults via `{**base, "data": ...}` while `raw_data` is already in `base`; refactoring from one field to the other and leaving both populated.
Related errors
- SSE '{field_name}' must be a single line
- SSE 'id' must not contain null characters
- {"errors": exc.errors(), "body": body.decode()}
- {"errors": exc.errors(), "body": body.decode()}
- Invalid YAML
AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04).
Data as JSON: /data/errors/0aa9b7ff7955b8f0.json.
Report an issue: GitHub.