dash-uploader
dash-uploader copied to clipboard
Passing additional input to callback
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
Hi @salvocamiolo ,
I'm glad you like the dash-uploader. My suggestions would be:
Option A:
- Use a hack with
@app.callback
. This is purely a hack and depends on implementation details of dash-uploader. - So, something like:
@app.callback(
Output('callback-output', 'children'),
[Input('dash-uploader', 'isCompleted')],
[State('dash-uploader', 'fileNames'),
State('dash-uploader', 'upload_id')],
)
def some_callback():
# do stuff
- Just search for
app.callback
in the source code and try to figure out how it all works. Although, I do not recommend this way to anything which needs to be working long term (something in dash-uploader implementation details might change), this might be the fastest and easiest route.
Option B:
- Create pull request for
du.callback
, which would use dash States. These are inputs which do not trigger callback. They could actually be a nice addition. So, instead of
@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.
and also,
Option C
- Chained callbacks: Use a separate
@app.callback
after the@du.callback
which could read the folder from the input and the actual file from@du.callback
. Did not have time to think now if this is even possible and how it would work.
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!
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:
- dashAppCallbackBump
- uploadedFileNames
- totalFilesCount
- uploadedFilesSize
- totalFilesSize
- upload_id
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).
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!
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.