About The StreamingHttpResponse and Caching

StreamingHttpResponse doesn't support view caching by design. However, you can cache the individual chunks that you send to the response.

Moreover, to avoid those views to be cached by Nginx, Varnish, or Cloudflare, set the Cache-Control: no-store explicitly.

import asyncio
import json
from asgiref.sync import sync_to_async
from django.core.cache import cache
from django.http import StreamingHttpResponse
from .models import Trick

async def trick_list_json_stream(request):
    def serialize(trick):
        return {
            "title": trick.title,
            "url": request.build_absolute_uri(trick.get_url_path()),
            "categories": [cat.slug for cat in trick.categories.all()],
            "technologies": [tech.slug for tech in trick.technologies.all()],
        }

    async def generate():
        yield '{"results": ['
        first = True

        chunk_size = 50
        offset = 0

        def fetch_chunk(offset_val):
            cache_key = f'tricks_chunk_{offset_val}_{chunk_size}'
            cached = cache.get(cache_key)
            if cached is not None:
                return cached
            chunk = list(
                Trick.objects.filter(
                    publishing_status__in=(
                        Trick.PUBLISHING_STATUS_PUBLISHED,
                        Trick.PUBLISHING_STATUS_PUBLISHED_OUTDATED,
                    )
                )
                .prefetch_related("categories", "technologies")
                [offset_val:offset_val + chunk_size]
            )
            cache.set(cache_key, chunk, timeout=300)
            return chunk

        get_chunk = sync_to_async(fetch_chunk)

        while True:
            chunk = await get_chunk(offset)

            if not chunk:
                break

            for trick in chunk:
                if not first:
                    yield ','
                yield json.dumps(serialize(trick))
                first = False
                await asyncio.sleep(0)  # yield control between items

            del chunk
            offset += chunk_size

        yield ']}'

        response = StreamingHttpResponse(
            generate(), content_type="application/json"
        )
        response["Cache-Control"] = "no-store"
        return response

Tips and Tricks Programming Optimization Performance Django 6.x Django 5.2 Django 4.2 Memcached Redis ASGI Uvicorn Daphne HTTP streaming