Stream one HTTP response as multiple parts. For Django, FastAPI, FastHTML, and Starlette.
Project description
multipart-response
Stream one HTTP response as multiple MIME parts. For Django, FastAPI, FastHTML, and Starlette.
Integrations
Use the adapter for your web framework:
Django
uv add "multipart-response[django]"
Stream parts from a Django view:
from multipart_response.django import MultipartResponse, Part
def generate_report(request):
def parts():
yield Part(
"<p>Generating report...</p>",
headers={"HX-Target": "#status"},
)
yield Part(
'<li><a href="/reports/42">Quarterly report</a></li>',
headers={
"HX-Target": "#reports",
"HX-Swap": "beforeend",
},
)
return MultipartResponse(parts())
Part(...)- Body:
content,content_type, andcharset. HTML is the default content type. - Headers:
headers,has_header(),get(), andsetdefault(). - Cookies:
set_cookie(),set_signed_cookie(), anddelete_cookie().
- Body:
MultipartResponse(...)
Use an async part source under ASGI:
async def updates(request):
async def parts():
yield Part("<p>Ready</p>")
yield Part("<p>Done</p>")
return MultipartResponse(parts())
Django 4.2 or later is required.
MultipartResponse subclasses Django's native StreamingHttpResponse.
FastAPI
uv add "multipart-response[fastapi]"
Stream multiple content types from one path operation:
import json
from fastapi import FastAPI
from multipart_response.fastapi import MultipartResponse, Part
app = FastAPI()
@app.get("/updates", response_class=MultipartResponse)
async def updates():
yield Part("<p>Ready</p>", media_type="text/html")
yield Part(
json.dumps({"status": "ready"}),
media_type="application/json",
headers={"HX-Target": "#status"},
)
yield Part(chunk_stream(), media_type="video/mp4")
Part(...)- Body:
contentandmedia_type. - Headers:
headers. - Cookies:
set_cookie()anddelete_cookie().
- Body:
MultipartResponse(...)- Response:
status_code,headers, andbackground. - Multipart:
subtypeandboundary.
- Response:
- Routing
- Use
response_classto wrap yielded parts. - Return
MultipartResponse(...)directly to set outer response options.
- Use
Stream HTML or JSON values directly when every part has one content type:
from multipart_response.fastapi import HTMLMultipartResponse, JSONMultipartResponse
@app.get("/html-updates", response_class=HTMLMultipartResponse)
async def html_updates():
yield "<p>Ready</p>"
yield "<p>Done</p>", {"HX-Target": "#status"}
@app.get("/json-updates", response_class=JSONMultipartResponse)
async def json_updates():
yield {"status": "ready"}, {"HX-Target": "#status"}
yield "done"
HTMLMultipartResponse renders strings as HTML. JSONMultipartResponse uses FastAPI's jsonable_encoder() and sets each part to application/json. The final part above contains the JSON string "done", including its quotes.
MultipartResponse subclasses Starlette's native StreamingResponse, which FastAPI uses for streamed responses.
FastHTML
uv add "multipart-response[fasthtml]"
Stream FastHTML components from a route:
from fasthtml.common import Div, P, fast_app
from multipart_response.fasthtml import MultipartResponse, Part
app, rt = fast_app()
@rt("/updates")
def get():
def parts():
yield Part(P("Ready"))
yield Part(Div("Done"), headers={"HX-Target": "#status"})
return MultipartResponse(parts())
Part(...)- Body: FastHTML
FTcomponents,content, andmedia_type. HTML is the default content type. - Headers:
headers. - Cookies:
set_cookie()anddelete_cookie().
- Body: FastHTML
MultipartResponse(...)- Response:
status_code,headers, andbackground. - Multipart:
subtypeandboundary.
- Response:
- Routing
- Return the response directly so FastHTML does not buffer a sync part source.
Stream components directly when every part is HTML:
from multipart_response.fasthtml import HTMLMultipartResponse
def get():
def parts():
yield P("Ready")
yield Div("Done"), {"HX-Target": "#status"}
return HTMLMultipartResponse(parts())
MultipartResponse subclasses Starlette's native StreamingResponse, which FastHTML uses for streamed responses.
Starlette
uv add "multipart-response[starlette]"
Return a Starlette response from an endpoint:
from multipart_response.starlette import MultipartResponse, Part
async def updates(request):
part = Part("Ready", media_type="text/plain", headers={"Content-ID": "status"})
part.headers["HX-Target"] = "#status"
part.set_cookie("seen", "yes")
return MultipartResponse(
[part],
status_code=200,
headers={"X-Stream": "updates"},
)
Part(...)- Body:
contentandmedia_type. - Headers:
headers. - Cookies:
set_cookie()anddelete_cookie().
- Body:
MultipartResponse(...)- Response:
status_code,headers, andbackground. - Multipart:
subtypeandboundary.
- Response:
Use HTMLMultipartResponse to return HTML strings without wrapping each one in Part:
from multipart_response.starlette import HTMLMultipartResponse
async def html_updates(request):
return HTMLMultipartResponse(["<p>Ready</p>", "<p>Done</p>"])
MultipartResponse subclasses Starlette's native StreamingResponse.
Nested parts
MultipartResponse accepts Part, MultipartPart, and nested Multipart values:
from multipart_response.fastapi import Multipart, Part
alternative = Multipart(
[
Part("Plain text", media_type="text/plain"),
Part("<p>HTML</p>", media_type="text/html"),
],
subtype="alternative",
)
A sequence is buffered. A sync or async iterable streams.
htmx
The hx-multipart extension lets one htmx request process a streaming multipart/mixed response. As each part arrives, its body and HX-* headers can swap HTML, fire events, or run other htmx response actions.
The extension vendors fetch-multipart, which adds Response.prototype.parts() and exposes each MIME part as a streaming Fetch-style BodyPart. multipart-response writes the parts on the server; hx-multipart parses and handles them in the browser.
Set HX-Target and HX-Swap on a part to control its swap.
FastHTML loads htmx 2 by default. Pass htmx=False to fast_app(), then load htmx 4 and hx-multipart as shown in the extension install guide.
Core
The dependency-free core exports Multipart, MultipartPart, and MultipartWriter.
- Boundaries and MIME headers are validated against RFC 2046 limits.
- Body chunks are checked for boundary collisions.
- Static, streamed, and nested multipart content is supported.
License
BSD-3-Clause
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file multipart_response-0.5.0.tar.gz.
File metadata
- Download URL: multipart_response-0.5.0.tar.gz
- Upload date:
- Size: 266.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5cdfcd0206aae2196f7a64dc648a7e3f6013ef3366916a1471c204226cdb0972
|
|
| MD5 |
a9545575fcf2ed1f2a3fb966c0755e9b
|
|
| BLAKE2b-256 |
fa55b87bfd411f59670086b7f79ad349da59c934e53ca490ff547d6d3f2690d7
|
Provenance
The following attestation bundles were made for multipart_response-0.5.0.tar.gz:
Publisher:
release.yml on scriptogre/multipart-response
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
multipart_response-0.5.0.tar.gz -
Subject digest:
5cdfcd0206aae2196f7a64dc648a7e3f6013ef3366916a1471c204226cdb0972 - Sigstore transparency entry: 2231715460
- Sigstore integration time:
-
Permalink:
scriptogre/multipart-response@9d0651c579f017dcc5cc82ed7075a438cf47d118 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/scriptogre
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9d0651c579f017dcc5cc82ed7075a438cf47d118 -
Trigger Event:
push
-
Statement type:
File details
Details for the file multipart_response-0.5.0-py3-none-any.whl.
File metadata
- Download URL: multipart_response-0.5.0-py3-none-any.whl
- Upload date:
- Size: 15.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec779402cc329a43544355a93b9246b7d7142bf8e164c1cd3b6f3f7bb9e69051
|
|
| MD5 |
cd005b041a863d72ff33dbb5d0eabb84
|
|
| BLAKE2b-256 |
4e5de154d4dbe65379f0b17f3b9cd9d51de01bad870fb031d3c65dc9e415a25d
|
Provenance
The following attestation bundles were made for multipart_response-0.5.0-py3-none-any.whl:
Publisher:
release.yml on scriptogre/multipart-response
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
multipart_response-0.5.0-py3-none-any.whl -
Subject digest:
ec779402cc329a43544355a93b9246b7d7142bf8e164c1cd3b6f3f7bb9e69051 - Sigstore transparency entry: 2231715569
- Sigstore integration time:
-
Permalink:
scriptogre/multipart-response@9d0651c579f017dcc5cc82ed7075a438cf47d118 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/scriptogre
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9d0651c579f017dcc5cc82ed7075a438cf47d118 -
Trigger Event:
push
-
Statement type: