pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/fastapi/fastapi/pull/15191

sets/global-81a5f61ff87ac6f0.css" /> ✨ Make ServerSentEvent generic with typed data field by benmosher · Pull Request #15191 · fastapi/fastapi · GitHub
Skip to content

✨ Make ServerSentEvent generic with typed data field#15191

Open
benmosher wants to merge 6 commits intofastapi:masterfrom
benmosher:feature/generic-sse-data
Open

✨ Make ServerSentEvent generic with typed data field#15191
benmosher wants to merge 6 commits intofastapi:masterfrom
benmosher:feature/generic-sse-data

Conversation

@benmosher
Copy link
Copy Markdown

Summary

  • Make ServerSentEvent generic — ServerSentEvent[Data] now validates that data is an instance of Data and emits a contentSchema for that type in the OpenAPI spec
  • Bare ServerSentEvent (no type parameter) continues to accept any value including None, preserving full backward compatibility
  • Add get_sse_data_type() helper used by the routing layer to detect parameterized ServerSentEvent[Data] annotations and extract Data for schema generation
  • Add docs_src/server_sent_events/tutorial006_py310.py example and corresponding test
  • Add documentation section "Typed ServerSentEvent" to the SSE tutorial

Motivation

Before this change, yielding ServerSentEvent objects from an SSE endpoint meant no data validation and no type information in the generated OpenAPI schema. Callers had to rely on documentation or convention to know what shape the data field would carry.

With ServerSentEvent[Item]:

@app.get("/items/stream", response_class=EventSourceResponse)
async def stream_items() -> AsyncIterable[ServerSentEvent[Item]]:
    for i, item in enumerate(items):
        yield ServerSentEvent[Item](data=item, event="item_update", id=str(i + 1))
  • Pydantic validates that every event's data really is an Item (or raises ValidationError at construction time)
  • The generated OpenAPI schema includes a contentSchema referencing Item, so clients and SDK generators know the exact payload shape

How it works

Pydantic's generic BaseModel creates a concrete subclass (not a _GenericAlias) when parameterized, so get_origen() returns None. The routing layer calls get_sse_data_type() which inspects __pydantic_generic_metadata__["args"] to extract Data. The routing logic then branches:

  • ServerSentEvent[Data] → use Data as stream_item_type (feeds contentSchema)
  • Bare ServerSentEvent → skip (no contentSchema, same as before)
  • Any other type → use as-is (existing JSONL / plain-model path)

model_config = ConfigDict(validate_default=True) ensures that omitting data on a parameterized event raises a ValidationError rather than silently storing None.

Example output

"text/event-stream": {
  "itemSchema": {
    "type": "object",
    "properties": {
      "data": {
        "type": "string",
        "contentMediaType": "application/json",
        "contentSchema": { "$ref": "#/components/schemas/Item" }
      },
      "event": { "type": "string" },
      "id":    { "type": "string" },
      "retry": { "type": "integer", "minimum": 0 }
    },
    "required": ["data"]
  }
}

Checklist

  • New tests cover the generic construction, validation, streaming, and OpenAPI schema
  • Tutorial example (tutorial006_py310.py) and tutorial test added
  • Backward compatibility: bare ServerSentEvent behaves identically to before
  • ruff check and ruff format pass with no changes
  • All 46 SSE tests pass

benmosher and others added 2 commits March 21, 2026 09:38
Introduce `ServerSentEvent[Data]` so endpoints that yield typed SSE
events get a `contentSchema` in the OpenAPI spec reflecting the data
payload type, while retaining full control over SSE fields (`event`,
`id`, `retry`, `comment`).

- `ServerSentEvent` now inherits from `Generic[Data]` with `data: Data`
- `validate_default=True` ensures `ServerSentEvent[Item]()` raises a
  ValidationError (data is effectively required when Data is concrete)
- `ServerSentEvent[Item | None]` allows optional data; bare
  `ServerSentEvent` is fully backward compatible (`Data=Any`)
- Added `get_sse_data_type()` helper (uses Pydantic's
  `__pydantic_generic_metadata__`) to extract `Data` from a
  parameterized `ServerSentEvent[Data]` annotation
- Routing layer now extracts `Data` from `ServerSentEvent[Data]` and
  uses it as `stream_item_type`, feeding it into the existing OpenAPI
  `contentSchema` pipeline
- Added tutorial006 and corresponding snapshot test
- Extended test_sse.py with generic SSE unit and app-level tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous commit added ServerSentEvent[Data] generic support and
tutorial006 but did not add a corresponding section to the SSE docs.
This adds the missing "Typed ServerSentEvent" section explaining the
generic syntax, validation behavior, and OpenAPI contentSchema benefit.

https://claude.ai/code/session_01GRv2sCmACvBQFX7fh7anWm
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 22, 2026

Merging this PR will not alter performance

✅ 20 untouched benchmarks


Comparing benmosher:feature/generic-sse-data (f6d54bf) with master (12bbd94)1

Open in CodSpeed

Footnotes

  1. No successful run was found on master (64feaec) during the generation of this report, so 12bbd94 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 22, 2026

benmosher and others added 4 commits March 22, 2026 12:33
Bare `ServerSentEvent` (without a type parameter) now silently resolves
to `ServerSentEvent[Any]`, so existing code and the `_check_data_exclusive`
return annotation require no changes and pass mypy without `[type-arg]`
errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e test

Pydantic BaseModel subclasses always have __pydantic_generic_metadata__, so
the `if not meta` early-return was dead code. Collapse it into a ternary and
add a test for a plain (non-parameterized) ServerSentEvent subclass to reach
the `not args` branch, bringing fastapi/sse.py to 100% coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test only fetched /openapi.json, leaving the generator body unreachable.
Add a /stream request inside the same TestClient context so the yield
executes, bringing tests/test_sse.py to 100% coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@graipher
Copy link
Copy Markdown

graipher commented Apr 2, 2026

There is a related discussion: #15102

That discussion goes one step further and wants to have a way to also constrain the event field at the same time, so that one can say that e.g.:

  1. event="item_update" will only ever emit a Item
  2. event="error" will only ever emit an Error

instead of just being able to say that the stream will contain Item | Error.

@benmosher
Copy link
Copy Markdown
Author

@graipher nice -- I want that too. I have further modifications that make that kind of event-based data type discrimination possible, but I wanted to start with this narrower, smaller step first, to gauge appetite for these enhancements and understand any constraints on the implementation.

That said -- I can put up the next steps that achieve that as a stacked draft PR so we can discuss the next steps also.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy