fohrloop / dash-uploader

The alternative upload component for python Dash applications.
MIT License
141 stars 29 forks source link

Using @app.callback for dash-uploader components #103

Open HyprValent opened 1 year ago

HyprValent commented 1 year ago

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?

fohrloop commented 1 year ago

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.

HyprValent commented 1 year ago

@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?

fohrloop commented 1 year ago

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.

HyprValent commented 1 year ago

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

HyprValent commented 1 year ago

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.

fohrloop commented 1 year ago

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?

HyprValent commented 1 year ago

@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

fohrloop commented 1 year ago

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 :)

HyprValent commented 1 year ago

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.

fohrloop commented 1 year ago

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

Option 1

STATE A:

Uploading data makes state transition A -> B

STATE B:

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

STATE C:

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):

Starting upload makes state transition from C -> D

STATE D:

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!

HyprValent commented 1 year ago

@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?