In our app, we have a
dcc.Upload component in the initial layout that allows users to drop a file to be processed. However, we’ve been asked to expose an endpoint that would allow them to pass this file through typical request machinery (likely, a POST call) and we would then present the updated app layout as if they had uploaded this file via the above component.
Is this possible?
I’ve seen other posts on the forum that are concerned with providing endpoints that when hit would cause an update in an application component, like a
Graph adding new data received from a POST. This attempt differs in that it’s only needed to provide some initial data for how to initialize the application. I just started looking into
flask-restful but haven’t been able to piece things together, though I feel like there’s a solution there.
Any help is appreciated.
I started looking at the
dispatch method on the main
Dash instance: https://github.com/plotly/dash/blob/dev/dash/dash.py#L1410
Using flask-restful, I can rig up a
Resource method that handles POST calls with access to the
app instance. It would be hacky, but I feel like there’s a way to pass in our file contents through
json and then construct whatever is needed to be in the JSON blob that
dispatch is expecting, namely the
Upload components input. Then hopefully it’s a matter of just calling that method to refresh the view. At this point, it’s all speculative , of course. Curious if anyone has reason to think this won’t work, or perhaps just has too much of a smell.
Just to make sure I understand your use case:
- the goal is to create something with the same UX as the
dcc.Upload component - in that upon dropping a file, the app responds by filling in the main portion of the page with processed results of that file and controls to interact with that processed data
- but something about the way that data is handled natively by the
dcc.Upload component doesn’t work in this case, and you need a more traditional upload endpoint where the file will arrive looking like a standard
Is that right? I’m curious what it is about doing this as a Dash callback that won’t work in this case, anything you can share about that? If this is a generic-enough situation I suppose we could imagine baking a mode for it into the
dcc.Upload component - if invoked, the file would actually be sent to a separate endpoint like a traditional form upload, and the ensuing callback would receive whatever that endpoint responded with rather than the file itself (a hash or UID of the file, or a locator to where the processed results were stored on the back end) and could then find that file and create the correct layout.
Anyway, to the short-term: TBH I’m not quite seeing how the flask-restful approach would work; all the callback requests hit the same
_dash-update-component endpoint, so to override this behavior you’d need to break out of that somehow, and anyway to get the renderer to process the response as a component update it seems like you’d need to allow it to create the request, which means you’d receive the data in the JSON format we use rather than a traditional form enclosure.
I’m tempted to suggest making a callback and creating a new request object from inside it, that looks like what this endpoint wants, and passing it along to the endpoint as if that were the original request… or even actually making a new request to that endpoint in the expected form, and using the result to generate the new layout.
Another option that occurs to me - if you can use our JSON format at this other endpoint - would be to have the callback throw a custom error and add an extra error handler akin to our
PreventUpdate handler that issues a temporary redirect (307) pointing to your other endpoint.
Just to make sure I understand your use case…
I think there’s a slight miscommunication, i.e., different workflow than you describe. Our current application uses a
dcc.Upload component (with an associated callback) that allows the user to do what you describe: drop or select a file where “the app responds by filling in the main portion of the page with processed results of that file and controls to interact with that processed data”. This is correct and it works as intended. Dropping a data file is the first action any user would normally take. The callback looks more or less like:
# and so on
def process_file_and_update_ui(contents, filename):
# do something fun with the file and update the UI
return output1, output2, etc
However, in addition to this manual usage, the client wants to also connect our application to some of their other services. They asked if we could expose an endpoint (e.g.,
/upload) that they could target programmatically with file data, which then would cause the application page to load in the same state as if the above callback fired. A concrete example is imagine a chat service where users can share files with one another, but now they can call up an embedded version of our app (
iframe) with the file of interest already loaded.
I will confess to being rusty on the ins and outs of request protocols and how endpoints are typically engineered, so I may be overlooking something. Now that I think about it more carefully, I’m not sure how possible it is to send this data and expect the page to load using said data… I’m realizing that’s not how POST works
I’ll try to think about this (and study up) a bit more. Hope that explains it better and curious if you have any insights.
Ah OK - that’s a lot simpler than I thought you were saying Then I think you can:
- add an
@app.server.route - similar to what what’s described in Allowing users to download CSV on click - but that accepts the file in some convenient format and stores it somewhere on the server (being careful about making it accessible to multiple processes if you’re expecting to run the app that way) - this is where the other services would post their file.
- give the
upload-filecallback another input - perhaps connected to a button that says “use latest saved file”, or perhaps even better connected to the page URL via
dcc.Location, if you want users to be able to go directly to this state, and then you can have a
dcc.Link next to the
dcc.Upload, that points to this URL.
- If the button or location is what triggered the callback (ie there’s no file data, or if
dash.callback_context.triggered lists the button/location) then you load the saved file; otherwise you use the uploaded file.
- If it’s not just the last file you’re interested in, you could even look up a whole list of saved files on the server, and put these into a dropdown (or a list of
dcc.Link elements if you think it would be useful to have a separate URL for each file) and use that as an additional input to this callback.