Error using dash_extensions

Hello,

I’m trying to read a .pptx file from an AWS s3 bucket, change the underlying data for the charts in the slides and then enable a user to be able to download the file. For some reason, I keep getting the error string argument expected, got 'bytes' when trying to save the presentation to the io.StringIO object. Could you please guide me on what I’m doing wrong in this? My code is below:

import pandas as pd
import boto3
import io
from pptx import Presentation
from pptx.chart.data import CategoryChartData, ChartData
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
from dash_extensions import Download
from dash_extensions.snippets import send_file


## Display Settings
pd.set_option("max_columns", 20)
pd.set_option("max_colwidth", 50)
pd.set_option("display.width", 2000)
pd.set_option("max_rows", 100)




S3_BUCKET_UNIVERSE = 'template-presentation'
S3_UNIVERSES_FILENAME = 'report_v1.pptx'
#
# need to pass mfa credentials locally
session = boto3.Session(profile_name='mfa')
s3 = session.client('s3')

#get the object and put into a dataframe
obj = s3.get_object(Bucket=S3_BUCKET_UNIVERSE, Key=S3_UNIVERSES_FILENAME)
presentation_path = obj['Body'].read()

prs = Presentation(io.BytesIO(presentation_path))

print(type(prs))
output = io.StringIO()
print(type(output))

prs.save(output)


app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Download", id="btn"), Download(id="download")])

@app.callback(Output("download", "data"), [Input("btn", "n_clicks")])
def func(n_clicks):
    return send_file(output)

if __name__ == '__main__':
    app.run_server()

I would recommend using the dedicated util functions. I guess in your case (i can’t test it as i don’t have access to your bucket) it would be,

@app.callback(Output("download", "data"), [Input("btn", "n_clicks")])
def func(n_clicks):
    prs = Presentation(io.BytesIO(presentation_path))
    return send_string(lambda string_io: prs.save(string_io))

Hi Emil,

Thank you for your response. It still doesn’t seem to work; I’m still receiving the error TypeError: string argument expected, got 'bytes'

I guess instead of s3 right now, what if we just test the path with a local path for now and output the file when the button is clicked.

This is what I have right now:

obj = s3.get_object(Bucket=S3_BUCKET_UNIVERSE, Key=S3_UNIVERSES_FILENAME)


presentation_path = obj['Body'].read()
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Download", id="btn"), Download(id="download")])

@app.callback(Output("download", "data"), [Input("btn", "n_clicks")])
def func(n_clicks):
    prs = Presentation(io.BytesIO(presentation_path))
    return send_string(lambda string_io: prs.save(string_io), filename='testing.pptx')

if __name__ == '__main__':
    app.run_server()

ERROR MESSAGE: TypeError: string argument expected, got 'bytes'

Also, I wasn’t able to find the documentation, would you be kind to explain what’s happening if you have the time?

Thanks

Hey @Emil ,

I got this to work, I just made a very slight change, essentially, send_string to send_bytes

def func(n_clicks):
    prs = Presentation(io.BytesIO(presentation_path))
    out = io.BytesIO()
    return send_bytes(lambda out: prs.save(out), filename='testing.pptx')

However, I would love to understand what the dedicated util function is doing and if you could point me to the documentation.

Thank you!

That’s great. Since I couldn’t run your code, I wasn’t sure which one to use. I wrote some documentation in the readme,

and also some notes in the source code,

@Emil - That’s amazing, just what I was looking for, thanks a bunch!

1 Like