Search code examples
flaskazure-blob-storageazure-storage

Stream (download) a file from azure blob storage to user through flask


I wrote this piece of code that downloads a file from the azure storage account and then sends it to the user. However it has to first fully download the file and then send the file to the user. would it be possible to "stream" the file so that the download (on the user side) basically starts immediately and no file has to be saved on the server.

Before someone says to use the azure link with the SAR token, let me tell you tha I am not allowed to user SAR tokens.

@forecasting_bp.route("/api/download/", methods=["GET"])
def download():
    """"""
    report = request.args.get("report")
    config_id = request.args.get("config_id")
    extension = request.args.get("extension", ".csv")
    filename = f"{report}{config_id}{extension}"
    blobpath = f"{config_id}/{filename}"
    with open(filename, "wb") as my_blob:
        download_stream = blob_client.download_blob()
        my_blob.write(download_stream.readall())
    return send_file(
        filename,
        as_attachment=True,
        download_name=filename,
    )

Solution

  • I converted my comments into an answer to help the community.

    I tried the below code to download a file from Azure blob storage to the user through Flask. Here is my Github repo for complete code.

    Code :

    views.py :

    import os
    from flask import Blueprint, request, Response
    from .azure_blob import get_blob_client
    
    forecasting_bp = Blueprint('forecasting_bp', __name__)
    
    def stream_blob(blob_client, chunk_size=1024*1024):
        """Generator function to stream the blob in chunks."""
        blob_size = blob_client.get_blob_properties().size
        start = 0
    
        while start < blob_size:
            end = min(start + chunk_size, blob_size)
            download_stream = blob_client.download_blob(offset=start, length=chunk_size)
            chunk = download_stream.readall()
            yield chunk
            start = end
    
    @forecasting_bp.route("/api/download/", methods=["GET"])
    def download():
        connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
        container_name = os.getenv("AZURE_STORAGE_CONTAINER_NAME")
        
        report = request.args.get("report")
        config_id = request.args.get("config_id")
        extension = request.args.get("extension", ".csv")
        filename = f"{report}{config_id}{extension}"
        blobpath = f"{config_id}/{filename}"
    
        blob_client = get_blob_client(connection_string, container_name)
        blob_client = blob_client.get_blob_client(blob=blobpath)
    
        response = Response(
            stream_blob(blob_client, chunk_size=1024*1024),
            content_type='application/octet-stream',
            headers={
                "Content-Disposition": f"attachment; filename={filename}"
            }
        )
    
        return response
    

    .env :

    AZURE_STORAGE_CONNECTION_STRING="<storage_conneString>"
    AZURE_STORAGE_CONTAINER_NAME="<container_name>"
    

    Output :

    enter image description here

    Browser output :

    http://127.0.0.1:5001/api/download/?report=sample_report&config_id=123&extension=.csv
    

    enter image description here