Using an endpoint to initialize Dash app

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 data or 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 multipart/form-data post.

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:

@app.callback(
    [Output('store-1', 'data'),
     Output('id-2', 'children'),
     # and so on
     Output('dropdown-id', 'options')],

    [Input('upload-file', 'contents')],
    [State('upload-file', 'filename')]
)
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 :man_facepalming:

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 :slight_smile: 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.