Skip to main content

multipart-response

Stream one HTTP response as multiple MIME parts. For Django, FastAPI, FastHTML, and Starlette.

API by adapter

Import from the adapter for your framework:

Adapter Public classes Value handling
multipart_response.django MIMEMultipart, MultipartResponse, Part, JsonPart Explicit parts. Part follows HttpResponse; JsonPart follows JsonResponse.
multipart_response.starlette MIMEMultipart, MultipartResponse, Part Explicit parts with Starlette response semantics.
multipart_response.fastapi MIMEMultipart, MultipartResponse, Part, HTMLMultipartResponse, JSONMultipartResponse Explicit parts, or homogeneous HTML and JSON values through specialized response classes.
multipart_response.fasthtml MIMEMultipart, MultipartResponse, Part FastHTML components and strings become HTML. Mappings become JSON.

The package root provides the framework-neutral MIMEMultipart, MIMEPart, and MIMEMultipartWriter classes.

Response input rules

Every MultipartResponse accepts one explicit part or a sequence, sync iterable, or async iterable of parts:

MultipartResponse(Part("one"))
MultipartResponse([
    Part("one"),
    Part("two"),
])

An iterable passed to MultipartResponse supplies several parts. An iterable passed to Part streams one part body:

MultipartResponse([Part("one"), Part("two")])  # Two parts
MultipartResponse((Part("one"), Part("two")))  # Two parts
MultipartResponse(Part(body_chunks()))           # One streamed part

Django and Starlette MultipartResponse accept only explicit Part, MIMEPart, or nested MIMEMultipart values. FastAPI's generic MultipartResponse follows the same rule.

FastAPI's specialized responses add homogeneous value conversion:

HTMLMultipartResponse(["<p>One</p>", "<p>Two</p>"])
JSONMultipartResponse([{"part": 1}, {"part": 2}])

(value, headers) is shorthand for one part in FastAPI's specialized responses and FastHTML's response:

("<p>Done</p>", {"HX-Target": "#status"})

A list or tuple passed to JSONMultipartResponse is a source of parts. Wrap a JSON array in the outer source to produce one array part:

JSONMultipartResponse([1, 2])      # Two JSON number parts
JSONMultipartResponse([[1, 2]])    # One JSON array part

content_type and media_type set metadata. They do not choose a serializer.

Django provides JsonPart for native JSON serialization. Serialize JSON before passing it to a generic Starlette or FastAPI Part:

# Django
JsonPart({"status": "done"})

# Starlette or FastAPI
Part(json.dumps({"status": "done"}), media_type="application/json")

FastHTML follows its native response conversion and renders mappings as JSON without json.dumps().

Integrations

Django

uv add "multipart-response[django]"

Stream parts from a Django view:

from multipart_response.django import JsonPart, 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",
            },
        )
        yield JsonPart(
            {"report_id": 42, "status": "done"},
            headers={"Content-ID": "result"},
        )

    return MultipartResponse(parts())
  • Part(...) follows Django's HttpResponse API for content, headers, and cookies. HTML is the default content type.
  • JsonPart(...) follows JsonResponse for encoder, safe, json_dumps_params, content type, charset, and headers.
  • MultipartResponse(...) follows StreamingHttpResponse and adds subtype and boundary.
  • content_type="application/json" on Part remains metadata. Use JsonPart to serialize a Python value.

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

Return FastHTML values as parts:

from fasthtml.common import Div, P, fast_app
from multipart_response.fasthtml import MultipartResponse, Part

app, rt = fast_app()


@rt
def updates():
    return MultipartResponse([
        P("Ready"),
        {"status": "working"},
        Part(Div("Done"), hx_target="#status"),
    ])

FastHTML components and strings become HTML parts. Mappings become JSON parts. Component rendering follows FastHTML's fh_cfg.indent setting.

The final part above includes:

Content-Type: text/html; charset=utf-8
HX-Target: #status

<div>Done</div>

Stream one component per part:

@rt
async def messages():
    return MultipartResponse(
        P(message) async for message in inbox()
    )

Each component produces one part and one htmx swap. Return MultipartResponse(...) explicitly. FastHTML does not infer it from a bare async-generator route.

Set htmx response headers on one part:

yield Part(
    P(message.text),
    hx_target="#feed",
    hx_swap="beforeend",
    hx_part_id=str(message.id),
)

Any hx_* keyword becomes an HX-* header. This includes future htmx response headers. Keyword values override matching entries in headers. A None value does not add or replace a header; an empty string is preserved.

Set envelope defaults for every part:

return MultipartResponse(
    (P(message) async for message in inbox()),
    hx_target="#feed",
    hx_swap="beforeend",
)

The response starts with:

Content-Type: multipart/mixed; boundary=...
HX-Target: #feed
HX-Swap: beforeend

Part headers override envelope defaults. MultipartResponse accepts the same open-ended hx_* keywords as Part.

Resume a persistent connection after its last completed part:

@rt
async def events(hx_last_part_id: str | None = None):
    async def parts():
        async for message in messages_after(hx_last_part_id):
            yield Part(
                P(message.text),
                hx_target="#feed",
                hx_swap="beforeend",
                hx_part_id=str(message.id),
            )

    return MultipartResponse(parts())

FastHTML injects the request's HX-Last-Part-ID header into the annotated parameter. An empty hx_part_id="" resets the reconnect cursor.

Stream several components inside one part:

@rt
async def transcript():
    return MultipartResponse(
        Part(P(message) async for message in inbox())
    )

Sync and async component iterables work. Their output forms one part body and one htmx swap.

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(...) follows Starlette's Response API for content, media type, headers, and cookies.
  • MultipartResponse(...) follows StreamingResponse and adds subtype and boundary.
  • media_type="application/json" does not serialize a Python value. Pass a JSON string or bytes.

MultipartResponse subclasses Starlette's native StreamingResponse.

Nested parts

MultipartResponse accepts Part, MIMEPart, and nested MIMEMultipart values:

from multipart_response.fastapi import MIMEMultipart, Part

alternative = MIMEMultipart(
    [
        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 swaps each part as it arrives.

The hx-multipart extension documentation

Use HX-Target, HX-Swap, and HX-Select to control each part.

It uses fetch-multipart to parse the stream.

Core

The dependency-free core exports MIMEMultipart, MIMEPart, and MIMEMultipartWriter.

  • 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

Metadata

Release files for multipart-response 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for multipart-response 0.6.0
File Size Uploaded
multipart_response-0.6.0.tar.gz 270.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for multipart-response 0.6.0
File Interpreter ABI Platform
multipart_response-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 290.0 kB

Release files / multipart_response-0.6.0.tar.gz

Download URL multipart_response-0.6.0.tar.gz
Size 270.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d1222bb0a13af9ce2252ff0b0183f0678803b6a67309a957d09a83d038a73a57
BLAKE2b-256 checksum
How to use checksums
d2ec21541f53e18a05d6c158e62d6a87eac91a20c2ec17132db7dbd701d1a295
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release files / multipart_response-0.6.0-py3-none-any.whl

Download URL multipart_response-0.6.0-py3-none-any.whl
Size 19.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b03aa21a49bc8d99e32ca38f74eb9b8124b8d24b770617438806dc53dc1711c5
BLAKE2b-256 checksum
How to use checksums
64656f97a1bba899f7f19e94e923a4cfa0a6f8e157e01d330c4a323f1b75e604
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.6.1

2 release files

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page