Download data frame from DASH App

Hi Dash,

Is there a way to download the data when clicked on the submit button. I have written the code like below but don’t know why data is not downloading as we download from the platform and located in download folders.

@app.callback(Output('output_div-fb', 'children'),
              [Input('submit-button', 'n_clicks')],
              [State('ad_account_id', 'value'),
               State('app_id', 'value'),
               State('access_token', 'value'),
               State('app_secret', 'value'),
               State('metrics', 'value'),
               State('breakdown', 'value'),
               State('start-date', 'date'),
               State('end-date', 'date'),
               ],
              )
def facebook_output(clicks, ad_account_id, app_id, access_token, app_secret, metrics, breakdown,
                    start_date, end_date):
    if clicks is not None:
        my_ad_account = ad_account_id
        my_app_id = app_id
        my_access_token = access_token
        my_app_secret = app_secret
        my_metrics = metrics
        my_breakdown = breakdown
        my_start_date = start_date
        my_end_date = end_date
        my_action_type = 'action_type'
        my_level = 'ad'
        my_time_increment = 1
        FacebookAdsApi.init(my_app_id, my_app_secret, my_access_token,
                            api_version='v5.0')
        me = User(fbid="me")
        new_col_list = my_metrics
        # print(new_col_list)
        act = AdAccount(my_ad_account)
        async_job = act.get_insights(params={'time_range': {'since': my_start_date, 'until': my_end_date},
                                             'breakdowns': list(my_breakdown),
                                             'action_breakdowns': my_action_type, 'level': my_level,
                                             'time_increment': my_time_increment},
                                     fields=list(new_col_list))
        df = pd.DataFrame(async_job)
        dff = df[new_col_list]
        html.Br()
        report_name = 'facebook_{}.csv'.format(datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S"))
        csv_string = dff.to_csv(report_name, index=False, encoding='utf-8')
        csv_string = "data:text/csv;charset=utf-8,%EF%BB%BF" + urllib.parse.quote(csv_string)
        return csv_string

When I want to download a file with dash, the app saves it in a dedicated folder under static/ with a uuid generated name, and a html.A() link receives the appropriate path as href.
A task cleans also regularly the folder which contains all generated files.
I don’t know if there is a better solution :slight_smile:
Francis

The solution of @fran6wol is perfectly valid. If you prefer to send the data directly without writing it to disk, you could try out my Download component,

1 Like

@Emil thanks for sharing this however I am using this https://stackoverflow.com/questions/61784556/download-csv-file-in-dash shared by same author.
but I am getting this error

Error: An object was provided as `children` instead of a component, string, or number (or list of those). Check the children property that looks something like:

any idea why I am getting this error.

here is the updated call back.

@app.callback(Output('output_div-fb', 'children'),
              [Input('submit-button', 'n_clicks')],
              [State('ad_account_id', 'value'),
               State('app_id', 'value'),
               State('access_token', 'value'),
               State('app_secret', 'value'),
               State('metrics', 'value'),
               State('breakdown', 'value'),
               State('start-date', 'date'),
               State('end-date', 'date'),
               ],
              )
def facebook_output(clicks, ad_account_id, app_id, access_token, app_secret, metrics, breakdown,
                    start_date, end_date):
    if clicks is not None:
        my_ad_account = ad_account_id
        my_app_id = app_id
        my_access_token = access_token
        my_app_secret = app_secret
        my_metrics = metrics
        my_breakdown = breakdown
        my_start_date = start_date
        my_end_date = end_date
        my_action_type = 'action_type'
        my_level = 'ad'
        my_time_increment = 1
        FacebookAdsApi.init(my_app_id, my_app_secret, my_access_token,
                            api_version='v5.0')
        me = User(fbid="me")
        new_col_list = my_metrics
        # print(new_col_list)
        act = AdAccount(my_ad_account)
        async_job = act.get_insights(params={'time_range': {'since': my_start_date, 'until': my_end_date},
                                             'breakdowns': list(my_breakdown),
                                             'action_breakdowns': my_action_type, 'level': my_level,
                                             'time_increment': my_time_increment},
                                     fields=list(new_col_list))
        df = pd.DataFrame(async_job)
        dff = df[new_col_list]
        html.Br()
        report_name = 'facebook_{}.csv'.format(datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S"))
        s = io.StringIO()
        dff.to_csv(s, index=False)
        content = s.getvalue()
        return dict(filename=report_name, content=content, type="text/csv")

Did you use the Download component? It is not clear from your posted code, as it contains only the callback.

row13 = html.Div([
    dbc.Row([
        dbc.Col([
            html.Button(id='submit-button-get', type='submit', children='Submit', style={'width': '300px'}),

        ], width={"order": "first"}, style={'margin-top': 20, 'margin-left': -235, 'display': 'table-cell',
                                            'verticalAlign': 'middle'}),
        dbc.Col([
            html.Div(id='output_div-get'),
        ])
    ])
])

tab_1_layout = dbc.Container(children=[
    row13
]
)

Could you suggest where to put the download component?

You can put it anywhere, it does not matter (it’s invisible). It just have to be in the layout for the callback to be invoked. I typically put it in the end of the most outer div of the app layout.

So i have just put the Download component with call back and layout.

row13 = html.Div([
    dbc.Row([
        dbc.Col([
            html.Button(id='submit-button-get', type='submit', children='Submit', style={'width': '300px'}),

        ], width={"order": "first"}, style={'margin-top': 20, 'margin-left': -235, 'display': 'table-cell',
                                            'verticalAlign': 'middle'}),
        dbc.Col([
                 html.Div(id='output_div-get'),
                 ])
        dbc.Col([html.Div(Download(id="download"))])
    ])
])


tab_1_layout = dbc.Container(children=[row13
]
)


@app.callback([Output('output_div-fb', 'children'), Output('download', 'children')],
              [Input('submit-button', 'n_clicks')],
              [State('ad_account_id', 'value'),
               State('app_id', 'value'),
               State('access_token', 'value'),
               State('app_secret', 'value'),
               State('metrics', 'value'),
               State('breakdown', 'value'),
               State('start-date', 'date'),
               State('end-date', 'date'),
               ],
              )
def facebook_output(clicks, ad_account_id, app_id, access_token, app_secret, metrics, breakdown,
                    start_date, end_date):
    if clicks is not None:
        my_ad_account = ad_account_id
        my_app_id = app_id
        my_access_token = access_token
        my_app_secret = app_secret
        my_metrics = metrics
        my_breakdown = breakdown
        my_start_date = start_date
        my_end_date = end_date
        my_action_type = 'action_type'
        my_level = 'ad'
        my_time_increment = 1
        FacebookAdsApi.init(my_app_id, my_app_secret, my_access_token,
                            api_version='v5.0')
        me = User(fbid="me")
        new_col_list = my_metrics
        # print(new_col_list)
        act = AdAccount(my_ad_account)
        async_job = act.get_insights(params={'time_range': {'since': my_start_date, 'until': my_end_date},
                                             'breakdowns': list(my_breakdown),
                                             'action_breakdowns': my_action_type, 'level': my_level,
                                             'time_increment': my_time_increment},
                                     fields=list(new_col_list))
        df = pd.DataFrame(async_job)
        dff = df[new_col_list]
        html.Br()
        report_name = 'facebook_{}.csv'.format(datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S"))
        str_io = io.StringIO()
        dff.to_csv(str_io, index=False)
        content = str_io.getvalue().encode('utf-8')
        return dict(filename=report_name, content=content, type="text/csv")

I am getting this error

dash.exceptions.InvalidCallbackReturnValue: The callback ..output_div-fb.children...download.children.. is a multi-output.
Expected the output type to be a list or tuple but got:

Why do you have two outputs? Shouldn’t it just be one? Also, the target property of the download component is ‘data’, not children, i.e.

@app.callback(Output('download', 'data'),
              [Input('submit-button', 'n_clicks')],

I did the change still getting this error

dash.exceptions.InvalidCallbackReturnValue: The callback for property `data` of component `download`
returned a value which is not JSON serializable.

In general, Dash properties can only be dash components, strings,
dictionaries, numbers, None, or lists of those.

here is the updated call back

@app.callback(Output('download', 'data'),
              [Input('submit-button', 'n_clicks')],
              [State('ad_account_id', 'value'),
               State('app_id', 'value'),
               State('access_token', 'value'),
               State('app_secret', 'value'),
               State('metrics', 'value'),
               State('breakdown', 'value'),
               State('start-date', 'date'),
               State('end-date', 'date'),
               ],
              )
def facebook_output(clicks, ad_account_id, app_id, access_token, app_secret, metrics, breakdown,
                    start_date, end_date):
    if clicks is not None:
        my_ad_account = ad_account_id
        my_app_id = app_id
        my_access_token = access_token
        my_app_secret = app_secret
        my_metrics = metrics
        my_breakdown = breakdown
        my_start_date = start_date
        my_end_date = end_date
        my_action_type = 'action_type'
        my_level = 'ad'
        my_time_increment = 1
        FacebookAdsApi.init(my_app_id, my_app_secret, my_access_token,
                            api_version='v5.0')
        me = User(fbid="me")
        new_col_list = my_metrics
        # print(new_col_list)
        act = AdAccount(my_ad_account)
        async_job = act.get_insights(params={'time_range': {'since': my_start_date, 'until': my_end_date},
                                             'breakdowns': list(my_breakdown),
                                             'action_breakdowns': my_action_type, 'level': my_level,
                                             'time_increment': my_time_increment},
                                     fields=list(new_col_list))
        df = pd.DataFrame(async_job)
        dff = df[new_col_list]
        html.Br()
        report_name = 'facebook_{}.csv'.format(datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S"))
        str_io = io.StringIO()
        dff.to_csv(str_io, index=False)
        content = str_io.getvalue().encode('utf-8')
        return dict(filename=report_name, content=content, type="text/csv")

It looks like something is wrong with the way you are formatting the data. I just release a new version (0.0.15) of the dash-extensions, which contains performance improvements and syntax sugar,

For your particular case, with the new version, i guess you could do something like,

...
from dash_extensions.download import send_data_frame
df = pd.DataFrame(async_job)
dff = df[new_col_list]
report_name = 'facebook_{}.csv'.format(datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S"))
return send_data_frame(dff.to_csv, report_name)

are you saying with download component i should call the send_data_frame? also i tried with “pip install dash-extensions==0.0.15” its installed but while importing its not working.

If you look at the link, you can see a full example. Your code should be adopted accordingly, i.e.

from dash_extensions.download import send_data_frame

@app.callback(Output('download', 'data'),
              [Input('submit-button', 'n_clicks')],
              [State('ad_account_id', 'value'),
               State('app_id', 'value'),
               State('access_token', 'value'),
               State('app_secret', 'value'),
               State('metrics', 'value'),
               State('breakdown', 'value'),
               State('start-date', 'date'),
               State('end-date', 'date'),
               ],
              )
def facebook_output(clicks, ad_account_id, app_id, access_token, app_secret, metrics, breakdown,
                    start_date, end_date):
    if clicks is not None:
        my_ad_account = ad_account_id
        my_app_id = app_id
        my_access_token = access_token
        my_app_secret = app_secret
        my_metrics = metrics
        my_breakdown = breakdown
        my_start_date = start_date
        my_end_date = end_date
        my_action_type = 'action_type'
        my_level = 'ad'
        my_time_increment = 1
        FacebookAdsApi.init(my_app_id, my_app_secret, my_access_token,
                            api_version='v5.0')
        me = User(fbid="me")
        new_col_list = my_metrics
        # print(new_col_list)
        act = AdAccount(my_ad_account)
        async_job = act.get_insights(params={'time_range': {'since': my_start_date, 'until': my_end_date},
                                             'breakdowns': list(my_breakdown),
                                             'action_breakdowns': my_action_type, 'level': my_level,
                                             'time_increment': my_time_increment},
                                     fields=list(new_col_list))
        df = pd.DataFrame(async_job)
        dff = df[new_col_list]
        report_name = 'facebook_{}.csv'.format(datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S"))
        return send_data_frame(dff.to_csv, report_name)

I guess something is wrong with the package i tried to run your example from here “https://pypi.org/project/dash-extensions/” seems not working its giving me Download package error, thought i already have installed 0.0.15.

error:

Traceback (most recent call last):
  File "C:/Dash_App/tabs/test.py", line 6, in <module>
    from dash_extensions import Download
  File "C:\myvenv\lib\site-packages\dash_extensions\__init__.py", line 10, in <module>
    from ._imports_ import *
  File "C:\myvenv\lib\site-packages\dash_extensions\_imports_.py", line 1, in <module>
    from .Download import Download
ImportError: cannot import name 'Download' from 'dash_extensions.Download' (C:\myvenv\lib\site-packages\dash_extensions\Download.py)

also in your examples it should be

from dash_extensions.Download import send_data_frame 

not
from dash_extensions.download import send_data_frame

i guess on your imports this Download package is not available.

from .Download import Download
from .Lottie import Lottie

__all__ = [
    "Download",
    "Lottie"
]

Please help me how can I get rid of this import error.

Hmm, i don’t see the import error on my system (Linux). Maybe it’s due to case sensitivity on Windows (i have a file called “download.py” and one called “Download.py” in the same folder). I have tried moving the util functions to snippets.py instead in version 0.0.16 (should be on pypi now), i.e. the imports would be

from dash_extensions import Download
from dash_extensions.snippets import send_data_frame

Does that make any difference on your side?

let me install version 0.0.16 first. is it pip install dash_extensions == 0.0.16?

Almost, in pip it is with a dash rather than underscore,

pip install dash-extensions==0.0.16

In imports, it is with an underscore (it is a little confusing, i know…). Also make sure pip is up to date,

pip install pip --upgrade

Otherwise, it might not find the new package version (at least not om my system).

its working fine now. thank you for your help.

1 Like

Can i get rid of the index part when I am doing this?

return send_data_frame(dff.to_csv, report_name)

Yes, then it would be,

return send_data_frame(lambda x: df.to_csv(x, index=False), report_name)
1 Like