dcc.Clipboard is not working when app is deployed to PythonAnywhere

Hello Everyone!
I’m using the core component dcc.Clipboard. Clicking the clipboard icon triggers a callback to copy contents from a Dash DataTable to the clipboard and to open an alert that says “Copied!”. This works beautifully on the locally hosted site running through Spyder.

Simplified callback below:

@app.callback(
[Output(component_id=“copy_to_clipboard”, component_property=“content”),
Output(component_id=“alert_Copied”, component_property=“is_open”),],
Input(component_id=“copy_to_clipboard”, component_property=“n_clicks”),
[State(component_id=‘Final_List_datatable’, component_property=“data”),
State(component_id=‘alert_Copied’, component_property=“is_open”),],
prevent_initial_call=True,
)
def CopyFinalTableClipboard(n_clicks, final_table_data, is_open):

if final_table_data is None: # If “Final List” table is blank there will be no update by this callback
return dash.no_update, dash.no_update
finaltableDF = pd.DataFrame(final_table_data)

return clipContent, True

The Issue
When I deployed this version of my app to PythonAnywhere, the dcc.Clipboard no longer seems to be functioning. The Icon (little paper/copy button) is visible, but upon clicking, the callback does not seem to trigger. Nothing is copied to the clipboard and the “Copied” alert does not display.
There is a post on a similar issue: dcc.Clipboard is not working in my company network - #7 by alemedeiros
Unlike these folks, my icon isn’t missing. My site is secure https and my browser hasn’t changed.

I really appreciate any advice that you may have.
If it helps at all, the live site with the malfunction is here: https://www.genetolist.com/
Thanks!

Hi @Tipshishat

I have some apps on pythonanywhere, and I’ve seen things fail silently like this when I’m missing some dependencies.

Can you tell if the callback is firing on the site? What version of Dash are you using on pythonanywhere?

Hey @AnnMarieW
Thanks for the suggestion.
I compared the requirements.txt’s by pip freeze and found a few more dependencies in the local environment (pasted below) than in the PythonAnywhere environment. I think these all have something to do with Spyder.
coloredlogs==15.0.1
docopt==0.6.2
pywin32==302
pywinpty==0.5.7
terminado==0.9.4
yarg==0.1.9

It does not seem like the callback is firing. I’m not sure how I can be certain.
pip freeze On Python Anywhere:
dash==2.0.0
dash-bootstrap-components==1.0.2
dash-core-components==2.0.0
dash-extensions==0.0.67
dash-html-components==2.0.0
dash-table==5.0.0
These are all the same in the local environment.

Alright thanks to help from @AnnMarieW, I found the issue which turned out to be in a line of my callback that I didn’t share with you all :disappointed:

First, a simple, separate, clipboard was found to work on the live site:
adding to the layout:
dcc.Clipboard(id="test_copy"),
adding the callback:

@app.callback(
    Output("test_copy", "content"),
    Input("test_copy", "n_clicks"),
)
def custom_copy(n):
    return "test copy"

So, I simplified my own clipboard callback and began adding lines back to the function until it stopped working.
The culprit was this line:
clipContent = dataframe.to_clipboard(excel=True, sep=None, index=False, header=False)

Checking the PythonAnywhere error.log, I found:

pandas.io.clipboard.PyperclipException: 
    Pyperclip could not find a copy/paste mechanism for your system.
    For more information, please visit
    https://pyperclip.readthedocs.io/en/latest/#not-implemented-error

So, I tried replacing the line with:
clipContent = dataframe.to_csv(sep=',', index=False, header=True)
And now the clipboard works again!

It turns out that pandas.DataFrame.to_clipboard relies on Pyperclip which uses the server side clipboard instead of the clients and PythonAnywhere doesn’t have Pyperclip installed, so it throws an error.

Unfortunately the formatting of to_csv doesn’t fit my use-case as well as to_clipboard. I’m copying a whole table of data to the clipboard and would like the data to paste into their respective cells in excel instead of all pasting in to 1 cell.
BUT, the bigger issue is solved! :upside_down_face: :grinning:

As a reference to anyone else out there, the working callback is now:

@app.callback(
    [Output(component_id="copy_to_clipboard", component_property="content"),
    Output(component_id="alert_Copied", component_property="is_open"),], # From a dbc.Alert that says "Copied!"
    Input(component_id="copy_to_clipboard", component_property="n_clicks"),
    [State(component_id='Final_List_datatable', component_property="data"),
    State(component_id='alert_Copied', component_property="is_open"),],
    prevent_initial_call=True,
)
def CopyFinalTableClipboard(n_clicks, final_table_data, is_open):
    if final_table_data is None: # If "Final List" table is blank there will be no update to this callback
        return dash.no_update, dash.no_update
    
    finaltableDF = pd.DataFrame(final_table_data) # "Final List" table is made into a dataframe 
    clipContent = finaltableDF.to_excel(sep=',', index=False, header=True)
    return clipContent, True
1 Like