AsyncResponse

Base class containing the core async content streaming logic.

class AsyncResponse[source]

AsyncResponse(*args, **kwargs) :: HttpResponseBase

Provides the is_async_fileresponse attribute checked by fileresponse.handlers.AsyncFileASGIHandler.

Contains the core content streaming logic which is the same for files and other file like streams.

Usage

class YourSpecialServiceResponse(AsyncResponse):
    ...

Tests

Assert expected content is streamed by AsyncResponse

chunks = []


async def send(chunk):
    chunks.append(chunk)


class Stream:
    def __init__(self, data):
        self.index = 0
        self.data = data
        self.data_len = len(data)

    async def read(self, chunk_size):
        if self.index + chunk_size >= self.data_len:
            chunk = self.data[self.index :]
        else:
            chunk = self.data[self.index : self.index + chunk_size]
        self.index += chunk_size
        return chunk


content = "1234567890"
stream = Stream(content)
response = AsyncResponse(chunk_size=3)
await response.send_stream_to_client(stream, send)

sent_body = "".join([c["body"] for c in chunks if "body" in c])
assert content == sent_body

Make sure response_headers are set correctly

response = AsyncResponse(chunk_size=3)
assert len(response.response_headers) > 0

AiofileFileResponse

Serve a file asynchronously using aiofiles.

class AiofileFileResponse[source]

AiofileFileResponse(path, chunk_size=4096) :: AsyncResponse

Async response class for serving files from the filesystem. It's only used to open a stream and to hand it over to AsyncResponse which then handles all of the content streaming.

It uses aiofiles to get the stream object from which chunks of data could be read asynchronously. The aiofiles library uses a threadpool to provide async filesystem access.

Usage

from fileresponse.http import AiofileFileResponse


async def aget_file(request, num=None):
    file_path = Path(__file__).parent.parent / "data" / str(num)
    return AiofileFileResponse(file_path, chunk_size=4096)

Tests

Complete FileResponse Testcase

  • Create file with random content
  • Stream file content via AsyncFileResponse
  • Assert streamed content equals original content
import os

from pathlib import Path

path = Path("testdata.bin")

with path.open("wb") as f:
    f.write(os.urandom(10000))

with path.open("rb") as f:
    content = f.read()

chunk_datas = []


async def send(chunk_data):
    chunk_datas.append(chunk_data)


response = AiofileFileResponse(path)
await response.stream(send)
received_content = b"".join([cd.get("body", b"") for cd in chunk_datas])

assert content == received_content
sent size:  10000

test repr

response = AiofileFileResponse("foobar")
assert "<AiofileFileResponse status_code=200>" == repr(response)

AiobotocoreFileResponse

Serve a file asynchronously using aiobotocore.

class AiobotocoreFileResponse[source]

AiobotocoreFileResponse(bucket, key, chunk_size=4096, **kwargs) :: AsyncResponse

Async response class for serving files from an s3 compatible object store like MinIO. It's only about opening / configuring the stream and then using AsyncResponse to handle the actual streaming of content to the client.

It uses aiobotocore to get the stream object from which chunks of data could be read asynchronously.

Tests

import django


try:
    settings.configure()
except RuntimeError:
    pass


def configure_settings():
    settings.FILERESPONSE_S3_ENDPOINT_URL = "asdf"
    settings.FILERESPONSE_S3_REGION = "asdf"
    settings.FILERESPONSE_S3_ACCESS_KEY_ID = "asdf"
    settings.FILERESPONSE_S3_SECRET_ACCESS_KEY = "asdf"

Just test repr

configure_settings()
response = AiobotocoreFileResponse("bucket", "key")
assert "<AiobotocoreFileResponse status_code=200>" == repr(response)

Test endpoint url config

configure_settings()

expected_endpoint_url = "http://foobar:9000/"

settings.FILERESPONSE_S3_ENDPOINT_URL = "asdf"
response = AiobotocoreFileResponse("bucket", "key", endpoint_url=expected_endpoint_url)

assert expected_endpoint_url == response.client_config["endpoint_url"]