How to use render.download() to save a docx

I have created the following Word template:

I am feeling {{ field1 }}
I am having a {{ field2 }}
I think that you should {{ field3 }}

which I save as template.docx and I have the following Shiny app.

from shiny import App, Inputs, Outputs, Session, reactive, render, ui
import pandas as pd
from docxtpl import DocxTemplate

df = pd.DataFrame({
    'field1': ['nice', 'mean'],
    'field2': ['great day', 'terrible day!'],
    'field3': ['hang out', 'bugger off']
})

doc = DocxTemplate("template.docx")

app_ui = ui.page_fluid(
    ui.input_radio_buttons("mood",
              "What's your mood?",
              choices=list(pd.Series(df['field1'])),
              selected="nice"),
    ui.download_button(
        "download_report",
        "Download Report")
)

def server(input, output, session):

    @render.download()
    def download_report():
        inp = input.mood()
        tmp = df.loc[df['field1'].eq(inp),]
        context = {
            'field1': tmp['field1'].iloc[0],
            'field2': tmp['field2'].iloc[0],
            'field3': tmp['field3'].iloc[0]}
        doc.render(context)
        yield doc.save("report.docx")
          
app = App(app_ui, server)

My issue is that when the dialog box pops up asking me where to save the file after I click the download_report button, the file is already saved in my current working directory (CWD) and what I want is to have my users be able to save the file where ever they want locally (e.g., Desktop). How can I achieve this?

EDIT:
It looks like doc.save("report.docx") is writing the file to the CWD and if I add this:

path = os.path.join(os.path.dirname(__file__), "report.docx")
return path

Then I can return the file and save it where I want and what I want it to be called. My only issue then is how do I delete the temporary file created in my CWD.

I solved it. Here's what I did based on this SO answer.

from shiny import App, Inputs, Outputs, Session, reactive, render, ui
import pandas as pd
from docxtpl import DocxTemplate
import io
df = pd.DataFrame({
    'field1': ['nice', 'mean'],
    'field2': ['great day', 'terrible day!'],
    'field3': ['hang out', 'bugger off']
})

doc = DocxTemplate("template.docx")

app_ui = ui.page_fluid(
    ui.input_radio_buttons("mood",
              "What's your mood?",
              choices=list(pd.Series(df['field1'])),
              selected="nice"),
    ui.download_button(
        "download_report",
        "Download Report")
)

def server(input, output, session):

    @render.download(filename="test.docx")
    def download_report():
        inp = input.mood()
        tmp = df.loc[df['field1'].eq(inp),]
        context = {
            'field1': tmp['field1'].iloc[0],
            'field2': tmp['field2'].iloc[0],
            'field3': tmp['field3'].iloc[0]}
        doc.render(context)

        # Create in-memory buffer
        file_stream = io.BytesIO()

        # Save the .docx to the buffer
        doc.save(file_stream)

        # Reset the buffer's file-pointer to the beginning of the file
        file_stream.seek(0)

        return file_stream
        
          
app = App(app_ui, server)
1 Like