Skip to main content

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())

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")

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())

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"},
    )

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 hx-multipart extension documentation

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

multipart_response-0.5.0.tar.gz (266.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

multipart_response-0.5.0-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

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

Hashes for multipart_response-0.5.0.tar.gz
Algorithm Hash digest
SHA256 5cdfcd0206aae2196f7a64dc648a7e3f6013ef3366916a1471c204226cdb0972
MD5 a9545575fcf2ed1f2a3fb966c0755e9b
BLAKE2b-256 fa55b87bfd411f59670086b7f79ad349da59c934e53ca490ff547d6d3f2690d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for multipart_response-0.5.0.tar.gz:

Publisher: release.yml on scriptogre/multipart-response

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file multipart_response-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for multipart_response-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ec779402cc329a43544355a93b9246b7d7142bf8e164c1cd3b6f3f7bb9e69051
MD5 cd005b041a863d72ff33dbb5d0eabb84
BLAKE2b-256 4e5de154d4dbe65379f0b17f3b9cd9d51de01bad870fb031d3c65dc9e415a25d

See more details on using hashes here.

Provenance

The following attestation bundles were made for multipart_response-0.5.0-py3-none-any.whl:

Publisher: release.yml on scriptogre/multipart-response

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page