dash-uploader icon indicating copy to clipboard operation
dash-uploader copied to clipboard

Using @app.callback for dash-uploader components

Open HyprValent opened this issue 3 years ago • 11 comments

Without going into too much detail, I want to use @app.callback in order to update three different outputs at once the uploading is completely done. This is the structure of the callback so far:

@app.callback([Output('callback-output', 'children'),
               Output("example-graph2", "figure"),
               Output("example-graph3", "figure")],
              [Input('dash-uploader', 'is_completed'),
               Input('dash-uploader', 'n_uploaded')])
def callback_on_completion(is_completed, filecount):
    if is_completed and filecount is not None:
        # code if the upload is completed

But if I try to run the app, I get the following errors: image

How can I use the is_completed and n_uploaded properties in order to run the callback?

HyprValent avatar Oct 05 '22 00:10 HyprValent

Which version of dash-uploader you are using? Have you tried using the du.callback instead of the app.callback?

If you are using version >= 0.7.0, then see this note.

fohrloop avatar Oct 05 '22 06:10 fohrloop

@np-8 Oh I didn't realize the support for @app.callback was removed. I am using a version >= 0.7.0 May I ask how the syntax works for a @du.callback for multiple outputs, similar to the example I gave for @app.callback?

HyprValent avatar Oct 05 '22 12:10 HyprValent

Could you please try:

@du.callback(
    output=[Output('callback-output', 'children'),
               Output("example-graph2", "figure"),
               Output("example-graph3", "figure")],
    id="dash-uploader",
)
def callback_on_completion(status: du.UploadStatus):
    # your code here

in place of

@app.callback([Output('callback-output', 'children'),
               Output("example-graph2", "figure"),
               Output("example-graph3", "figure")],
              [Input('dash-uploader', 'is_completed'),
               Input('dash-uploader', 'n_uploaded')])
def callback_on_completion(is_completed, filecount):
    if is_completed and filecount is not None:
        # code if the upload is completed

The status is du.UploadStatus object, where you can find for example status.is_completed, status.n_uploaded and other attributes.

fohrloop avatar Oct 05 '22 12:10 fohrloop

It seems to be working, thank you so much! I will close the issue since my problem has now been fixed :)

HyprValent avatar Oct 05 '22 15:10 HyprValent

Reopened the issue to ask how I would go about using multiple inputs in @du.callback, if that's possible?

I'm trying to set up a trigger div so that a specific button component is disabled while files are being uploaded.

HyprValent avatar Oct 07 '22 16:10 HyprValent

Hi @HyprValent, I am not sure if I understand your problem correctly. Could you try to explain a bit more what you are trying to do?

fohrloop avatar Oct 07 '22 21:10 fohrloop

@np-8 In a @du.callback, I have it run some code on the uploaded files. In the app, however, I have an html.Button component that I want only enabled after the @du.callback is finished. So basically, while the callback is executing its code, I want the 'disabled' property of the html.Button to be True and then turn to False when the whole @du.callback is finished.

image

HyprValent avatar Oct 07 '22 22:10 HyprValent

I'm terribly sorry but I still could not understand the need for the multiple inputs. Anyway, the @du.callback has always just one input, which is the id of the du.Upload component. Then, you may have multiple outputs as in regular dash callbacks. Here is what I used to create what you described (as I understood it):

import uuid

import dash_uploader as du
import dash

if du.utils.dash_version_is_at_least("2.0.0"):
    from dash import html  # if dash <= 2.0.0, use: import dash_html_components as html
else:
    import dash_html_components as html

from dash.dependencies import Output, Input

app = dash.Dash(__name__)

UPLOAD_FOLDER_ROOT = r"C:\tmp\Uploads"
du.configure_upload(app, UPLOAD_FOLDER_ROOT)


def get_upload_component(id):
    return du.Upload(
        id=id,
        text="Drag and Drop files here",
        text_completed="Completed: ",
        cancel_button=True,
        pause_button=True,
        # max_file_size=130,  # 130 Mb
        # max_total_size=350,
        # filetypes=["csv", "zip"],
        upload_id=uuid.uuid1(),  # Unique session id
        max_files=10,
    )


def get_app_layout():

    return html.Div(
        [
            html.H1("Demo"),
            html.Div(
                [
                    get_upload_component(id="dash-uploader"),
                    html.Div(id="callback-output", children="no data"),
                    html.Button("Create Graphs", id="button", disabled=True),
                ],
                style={  # wrapper div style
                    "textAlign": "center",
                    "width": "600px",
                    "padding": "10px",
                    "display": "inline-block",
                },
            ),
        ],
        style={
            "textAlign": "center",
        },
    )


# get_app_layout is a function
# This way we can use unique session id's as upload_id's
app.layout = get_app_layout


# 3) Create a callback
@du.callback(
    output=[Output("callback-output", "children"), Output("button", "disabled")],
    id="dash-uploader",
)
def callback_on_completion(status: du.UploadStatus):
    print(status)
    files = html.Ul([html.Li(str(x)) for x in status.uploaded_files])
    return files, False


if __name__ == "__main__":
    app.run_server(debug=True)

and here is the output when uploading

image

and here is the output after upload is finished:

image

I don't know if this is what were after, but maybe we can iterate to find a solution for you :)

fohrloop avatar Oct 08 '22 07:10 fohrloop

Okay I think you've almost got what I wanted because that's the code structure I had previously. But say if in the same browser session, I wanted to upload another new file. I would want the button to disable itself again, but there isn't a way to detect the start of another upload to re-disable the button. I'm sorry if I'm not explaining this properly! Let me know if there is a specific part of the question you do not understand.

HyprValent avatar Oct 08 '22 09:10 HyprValent

So did I understand that there are different states of your app:

Option 1

STATE A:

  • Create graphs button is disabled
  • No data to be used in graph

Uploading data makes state transition A -> B

STATE B:

  • Create graphs button is enabled
  • Server has data to be used in graph

Clicking the create graphs button makes state transition B -> C

STATE C:

  • There is a graph (somewhere)
  • There is no data anymore (for creating graphs)
  • The create graphs button is disabled

If this is how you want it to work, could there be logic in the callback associated with the "Create graphs" button which would disable the button after it has been clicked?

Option 2

same as option 1 until here

Clicking the create graphs button makes state transition B -> C

STATE C (Option 2):

  • There is a graph (somewhere)
  • There is no data anymore (for creating graphs)
  • The create graphs button is still enabled

Starting upload makes state transition from C -> D

STATE D:

  • The create graphs button is disabled

Maybe in this Option 2, you could have in the outputs of the du.callback also the "disabled" (True or False) property for the button? You could utilize the status.is_completed for this.

Let me know if this helps you forward!

fohrloop avatar Oct 08 '22 09:10 fohrloop

@np-8 Sorry for the late reply! Yes my implementation in mind is exactly as described in Option 1. The issue is re-disabling the button after the visualization is complete, as you cannot use the same component as an output in two different callbacks. If it isn't too much of an inconvenience, do you have any ideas on how I could go about doing so using @du.callback?

HyprValent avatar Oct 11 '22 16:10 HyprValent