Problems downloading a binary dynamically generated file

Hello!

I have a dynamically generated file created on backend that I want to download when clicking a button. File is verified as being well generated and downloadable by other means.

On Dash I have the following for the button:

html.Div([
    dbc.Button(
        Generate pdf file",
        id=f"{PAGE_ID}_export_button",
        color='success',
        disabled=False,
        outline=True
    ),
    dcc.Download(id="download-pdf")
)

and the callback:

@app.callback(
    Output("download-pdf", "data"),
    Input(f"{PAGE_ID}_export_button", "n_clicks"),
    [State(f"{PAGE_ID}_labelsperserial", "value"),
    State(f"{PAGE_ID}_offsetlines", "value"),
    State(f"{PAGE_ID}_filename", "value"),
    State(f"{PAGE_ID}_start", "value"),
    State(f"{PAGE_ID}_devicetype", "value"),
    State(f"{PAGE_ID}_batchname", "value"),
    State(f"{PAGE_ID}_batchsize", "value"),
    State(f"{PAGE_ID}_revision", "value")],
    prevent_initial_call=True)
def download_pdf(n_clicks, labelsperserial, offsetlines, filename, start, devicetype, batchname, batchsize, revision):
    fn = filename
    if fn is not None and not fn.endswith(".pdf"):
        fn = fn + ".pdf"
    response = requests.get(
        base_url + f"/labels/generate-microscopes-serial-sheet?labelsperserial={labelsperserial}&offsetlines={offsetlines}" +
            f"&filename={filename}&start={start}&devicetype={devicetype}&batchname={batchname}&batchsize={batchsize}&revision={revision}", 
    )
    return dict(content=response.content, filename=fn)

And I’m getting the following error message for it:

Traceback (most recent call last):
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\dash\_callback.py", line 191, in add_context
    jsonResponse = to_json(response)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\dash\_utils.py", line 21, in to_json
    return to_json_plotly(value)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\plotly\io\_json.py", line 124, in to_json_plotly
    return json.dumps(plotly_object, cls=PlotlyJSONEncoder, **opts)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\json\__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\_plotly_utils\utils.py", line 59, in encode
    encoded_o = super(PlotlyJSONEncoder, self).encode(o)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\_plotly_utils\utils.py", line 136, in default
    return _json.JSONEncoder.default(self, obj)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\json\encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'bytes' is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\dash\dash.py", line 1336, in dispatch
    response.set_data(func(*args, outputs_list=outputs_list))
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\dash\_callback.py", line 193, in add_context
    _validate.fail_callback_output(output_value, output)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\dash\_validate.py", line 310, in fail_callback_output
    property=output.component_property, id=output.component_id
TypeError: unsupported format string passed to dict.__format__

I already tried to use return send_bytes(response.content, fn) instead of return dict(content=response.content, filename=fn) in order to download the file and I got the following error :

Traceback (most recent call last):
  File "C:\Users\Lucas\Projects\ovizio-dashboard\snackyApp\snacky_lib\snacky\apps\generate_labels\generate_microscopes_serial_sheet.py", line 179, in download_pdf
    return send_bytes(response.content, fn)
  File "C:\Users\Lucas\anaconda3\envs\dashApp2\lib\site-packages\dash_extensions\snippets.py", line 43, in send_bytes
    writer(data_io, **kwargs)
TypeError: 'bytes' object is not callable

I’m pretty stuck. Could anybody help me on that?
Thanks in advance!

You missed this dcc.send_file or this,

base64 (boolean; optional): Set to True, when data is base64 encoded.

And you may like this below.

1 Like

Encoding to base64 didn’t work and, dcc.send_file is not an option because the file is remote and I’m getting the stream, unless I save the stream in a file and send it again, which is counterproductive. But I’ll think this as a last possibility.

Sorry, I haven’t used requests in a while, how about io.BytesIO module?

Did you try send_bytes? :slight_smile:

I already tried and wrote it in the question. The error I get is TypeError: 'bytes' object is not callable.

The send_bytes function expects a function that writes to BytesIO (as shown in the SO example), not a byte array.

Well, what happened is very strange.
Even using prevent_initial_call=True) on callback decorator, the function is being called on page load, and this call was breaking things inside. I couldn’t investigate more, and I think it could worth a bug report.

What I did is to add following lines at the beginning of the function:

    changed_id = [p['prop_id'] for p in callback_context.triggered][0]
    if changed_id != f"{PAGE_ID}_export_button.n_clicks":
        return no_update

    if n_clicks is None:
        return no_update
    

And then the rest of the function. Then dcc.send_bytes began working, because before it was failing silently.

Are some of the input/output components generated dynamically?