fohrloop / dash-uploader

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

Passing additional input to callback #104

Open salvocamiolo opened 1 year ago

salvocamiolo commented 1 year ago

Hi there, first of all, let me thank you for this very nice tool. It is exactly what I was looking for. I know that the callback does not allow the user to add another input but I am wondering if there is a workaround for the following scenario. I let the user upload their file which ends up in a folder named after the upload_id. I want then move these files into a specific user directory after the upload is completed. I have in my dash app a dropdown menu whose value is the folder name I would like to move the file to, and I was thinking about passing this value to the callback and then issue some "mv" command to move the files. Have you got any idea on how to achieve this without the additional input in the callback? Thanks a lot for your suggestions

fohrloop commented 1 year ago

Hi @salvocamiolo ,

I'm glad you like the dash-uploader. My suggestions would be:

Option A:

@app.callback(
    Output('callback-output', 'children'),
    [Input('dash-uploader', 'isCompleted')],
    [State('dash-uploader', 'fileNames'),
     State('dash-uploader', 'upload_id')],
)
def some_callback():
    # do stuff

Option B:

@du.callback(
    output=Output("callback-output", "children"),
    id="dash-uploader",
)
def callback_on_completion(status: du.UploadStatus):
    return html.Ul([html.Li(str(x)) for x in status.uploaded_files])

one could use

@du.callback(
    output=Output("callback-output", "children"),
    state=State('input-1-state', 'value'),
    id="dash-uploader",
)
def callback_on_completion(status: du.UploadStatus):
    return html.Ul([html.Li(str(x)) for x in status.uploaded_files])

The state should then accept State or list of State as inputs. Now the state could either be in status.state or as additional arguments to the function, like:

def callback_on_completion(status: du.UploadStatus, state):
    return html.Ul([html.Li(str(x)) for x in status.uploaded_files])

If you decide to contribute with a PR, I'm happy to help you with it.

fohrloop commented 1 year ago

and also,

Option C

salvo-camiolo commented 1 year ago

Thanks for your quick reply. I actually need something that is working long term, so the solution that adapts better to my (limited) knowledge of dash is the C. I have tried to add a second callback like the following (this should make an alert appears saying that the upload was successful after moving the files into the folder named after the project-id-dropdown-menu value :

@app.callback([Output("file-uploaded-alert","children"),
    Output("file-uploaded-alert","color")],
    Input("file-uploader","is_completed"),
    State('project-id-dropdown-menu','value')
    )
def move_files_to_user_folder(isCompleted, project_folder):
    if  isCompleted == True:
        print("This is it: "+project_folder)
        return "File uploaded!","success"

However, this second callback is never triggered after the file upload is completed. Am I doing anything wrong? Many thanks!

fohrloop commented 1 year ago

So this would be possible if there were a list of attributes for "file-uploader" component that could be listened. If I remember correctly, you can listen to any props of the React component that is created. I think it would make sense to list these props in the documentation. Or, at least a subset of the props that could be though to be part of the public API.

If you check the src\lib\components\Upload_ReactComponent.react.js, you can see a list of props and their defaults:

Upload_ReactComponent.defaultProps = {
    maxFiles: 1,
    maxFileSize: 1024 * 1024 * 10,
    chunkSize: 1024 * 1024,
    simultaneousUploads: 1,
    service: '/API/dash-uploader',
    className: 'dash-uploader-default',
    hoveredClass: 'dash-uploader-hovered',
    completedClass: 'dash-uploader-completed',
    disabledClass: 'dash-uploader-disabled',
    pausedClass: 'dash-uploader-paused',
    uploadingClass: 'dash-uploader-uploading',
    defaultStyle: {},
    uploadingStyle: {},
    completeStyle: {},
    text: 'Click Here to Select a File',
    completedMessage: 'Complete! ',
    uploadedFileNames: [],
    filetypes: undefined,
    startButton: true,
    pauseButton: true,
    cancelButton: true,
    disableDragAndDrop: false,
    id: 'default-dash-uploader-id',
    onUploadErrorCallback: undefined,
    dashAppCallbackBump: 0,
    upload_id: ''
}

The dashAppCallbackBump is interesting. It is actually used in the dash_uploader\callbacks.py to create the actual app.callback (when using du.callback):

        dash_callback = settings.app.callback(
            output,
            [Input(id, "dashAppCallbackBump")],
            [
                State(id, "uploadedFileNames"),
                State(id, "totalFilesCount"),
                State(id, "uploadedFilesSize"),
                State(id, "totalFilesSize"),
                State(id, "upload_id"),
            ],
            **kwargs
        )(dash_callback)

Also the uploadedFileNames and totalFilesCount seem both usable. The totalFilesCount should be the total number of files to be uploaded in that upload session (what was given to the React component). The uploadedFileNames is a list of filenames (strings) of the files that have been uploaded.

Maybe there should be some documentation about the following props:

and make them part of the public API. Or, alternatively, there should be possibility in du.callback to use additional State inputs -- maybe that would be even better.

But the reason why your code does not work is that the du.Upload component does not have is_completed property. It has, uploadedFileNames, totalFilesCount and dashAppCallbackBump which could be used instead (which are not at least yet part of the public API, but considered as implementation details).

salvo-camiolo commented 1 year ago

Thanks! I have tried the properties uploadedFileNames and totalFilesCount but they trigger the chained callback as soon as the upload begins, and I need an action to be performed after the upload is complete. I'll keep on working on it to see if I can find a reliable workaround. Thanks a lot for you help!

mapix commented 9 months ago

This issue is very important. In my scenario, I use some dcc.Store objects to store user session and some important user Tokens. Adding State can help verify the validity of the data in this scenario. I will add a state parameter that is compatible with app.callback for du.callback to keep the implementation as simple and easy to understand as possible. I have been using this modification in several of my production applications for a long time, and I will try to merge it into the main branch later.